@rness/cli 0.20.0 → 0.21.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/README.md CHANGED
@@ -31,6 +31,7 @@ Inside a workspace, every `rness` delegates to the copy pinned in
31
31
  rness status [<tab>] [--json]
32
32
  rness pulse create [<collection>] [-y]
33
33
  rness pulse sync
34
+ rness pulse note [--plan <path>] [--kind approach|deviation|blocker|done] [<text>]
34
35
  rness validate
35
36
  rness mcp
36
37
 
@@ -65,7 +66,7 @@ when dropped, red `?` when missing.
65
66
  `yellow` for `In progress`; `red` without a status) and its item there
66
67
  (`link`, the board filtered on its path; null without a pulse) — the
67
68
  board (`pulse`), and what a Claude Code session shows of the workspace,
68
- worded by the CLI — the session-start banner, the status line entry, the
69
+ worded by the CLI — the session-start banner, the footer's label, the
69
70
  scope's plans `In progress` and the notes of the safety net (see
70
71
  "Hooks"). The plugin's mod reads it (see
71
72
  "The mod").
@@ -176,9 +177,11 @@ the pinned copy, and computes nothing itself:
176
177
  installed copy does not match, problems `validate` would report): those
177
178
  notes. The banner is not repeated there; the session-start line says it
178
179
  once, in the transcript.
179
- - **The status line**: `<scope> · <n> in progress`, with `⚠ <n>` when
180
- there are notes. Claude Code draws it after the plugin's name:
181
- `⚠ rness: web · 1 in progress` (2.1.289).
180
+ - **A label in the prompt footer**, among the session modes (`focus`,
181
+ `memory paused`), dim: `rness · <scope> · <n> in progress`, with `⚠ <n>`
182
+ when there are notes. Not the pinned status line a plugin can set:
183
+ Claude Code draws every pinned line after a `⚠` in its warning colour
184
+ (2.1.291), the wrong sign for a line that informs.
182
185
  - **`/rness:status [tab]` as a pane** beside the conversation, a tab per
183
186
  collection, a row per document, its status in the colour Agent Pulse
184
187
  gives it (yellow in progress, green done, red blocked, gray draft or
@@ -205,13 +208,15 @@ It reads the snapshot again at session start, after an `Edit` or `Write`
205
208
  of a file under `.rness/`, and at the end of each turn, off the turn's
206
209
  path. The module runs the pinned copy at the one path `sync` writes into it
207
210
  for its place (`.rness` at the root, `../../.rness` in a clone), as the
208
- settings hooks do: never a copy a clone's own tree could hold. It loads
211
+ settings hooks do: never a copy a clone's own tree could hold. Like them,
212
+ it never installs: a pin that drifts from the installed copy is a note in
213
+ the band, and `npm install` in `.rness` is the developer's. It loads
209
214
  where the plugin loads — a trusted folder, `claude -p` included — on a
210
215
  Claude Code that has mods: run by 2.1.280 and later, ignored without an
211
216
  error by 2.1.240 and 2.1.199, where the skills work as before (verified on
212
217
  2026-10-04). An older Claude Code is told so once per version and machine,
213
218
  in the session-start line: `rness: Claude Code 2.1.240 shows no rness band,
214
- status line or pane; 2.1.280 or later does — claude update`. The version
219
+ footer label or pane; 2.1.280 or later does — claude update`. The version
215
220
  comes from `AI_AGENT`, which Claude Code sets (2.1.240 does); one that does
216
221
  not set it, or not in that form, is told nothing. The versions told are kept
217
222
  in `claude-code.json` beside the login (`~/.config/rness/` by default). Not in
@@ -240,7 +245,9 @@ workspace root's carry four Claude Code hooks, all run by the pinned copy:
240
245
  repository or nothing installed there, a `rness.json` rness refuses, a pin
241
246
  the installed copy does not match, the problems `rness validate` reports.
242
247
  A Claude Code too old for the plugin's mod gets one more line, once (see
243
- "The mod"). After a compaction, the context only.
248
+ "The mod"). When a board keeps a journal and the scope has a plan in
249
+ progress, the model gets one more line: how and when to post a note (see
250
+ "Pulse"). After a compaction, the context only.
244
251
  - **Before an edit** (`PreToolUse` on `Edit|Write`): refused, with the
245
252
  reason for the model, when it would change what `sync` generates — a
246
253
  line of the block of an `AGENTS.md` or `CLAUDE.md` (the reason names the
@@ -254,10 +261,13 @@ workspace root's carry four Claude Code hooks, all run by the pinned copy:
254
261
  - **After an edit** (`PostToolUse` on `Edit|Write`): when the file is under
255
262
  `.rness/`, its front-matter problems — or those of `rness.json` — go back
256
263
  to the model, which fixes them in the same turn. Any other edit costs one
257
- path check. Stale blocks are left to `sync`. With a pulse declared, the
258
- edit is then marked on the board, broken or not; see "Pulse".
259
- - **At session end** (`SessionEnd`): with a pulse declared, clears the marks
260
- this session set and syncs the board; see "Pulse". Without one, nothing.
264
+ path check. Stale blocks are left to `sync`. When a board declares
265
+ `mark` and holds the document, the edit is then marked on it, broken or
266
+ not; see "Pulse".
267
+ - **At session end** (`SessionEnd`): when a board declares `clear-marks`,
268
+ clears the marks this session set and syncs; when one declares
269
+ `journal-summary`, posts the session's summary; see "Pulse". Without
270
+ either, nothing.
261
271
 
262
272
  Each hook is one fixed `sh` line: it runs
263
273
  `<.rness>/node_modules/@rness/cli/dist/bin/rness.js hook <event>` when that
@@ -269,7 +279,8 @@ own hooks stay where they are.
269
279
 
270
280
  Claude Code runs hooks from a committed settings file without asking each
271
281
  developer, including in `claude -p`. These four read `.rness/`, write
272
- nothing in the workspace and run no install. Only with a pulse declared do
282
+ no file of the workspace and run no install; with a journal, the session
283
+ start records the clone's `HEAD` in its git directory. Only with a pulse declared do
273
284
  they reach the network, through a detached `rness` process that uses your
274
285
  login; when that process fails, it writes why to `pulse.json` in rness's
275
286
  configuration directory (`~/.config/rness/` by default), which the next
@@ -284,10 +295,70 @@ status, it received the problem. The `sh` line is not verified on Windows.
284
295
  rness pulse create marketing # a collection's own project (0.17.0)
285
296
  rness pulse sync # every declared project, as often as wanted
286
297
 
287
- A projection of rness's state on the organization's GitHub Projects: a
288
- project named **Agent Pulse**, where the team sees — and reads — every
289
- document of `.rness/`, and sees when an agent is at work on one. rness
290
- writes the board; if it is not in rness, it does not belong there.
298
+ A projection of rness's state on the organization's GitHub Projects,
299
+ each declared in `rness.json`: first **Agent Pulse**, where the team sees —
300
+ and reads — every document of `.rness/`, and sees when an agent is at work
301
+ on one. rness writes the boards; if it is not in rness, it does not belong
302
+ there.
303
+
304
+ - **Boards declared in `rness.json`** (0.21.0): `projects` maps a name to
305
+ a board, written whole — its `number`, `collections`, `colors`,
306
+ `fields`, `labels`, `views`, and optionally `title`, `description`,
307
+ `readme` and `updates` (files of `.rness/`). What `rness.json` says is
308
+ the board; what it does not declare, the board does not have, but the
309
+ engine's own: one issue of `.rness` per document, found by its `Path`,
310
+ its `Status`, the label `rness`. Nothing knows Agent Pulse by name: it
311
+ is a board like any other, made from the preset `agent-pulse`, and the
312
+ example a team copies for another.
313
+ - `collections`: `"all"` (ADR, specs and plans with the contract's
314
+ statuses, then every other directory `rness status` finds, but those
315
+ another board holds), or directories, each with `statuses` —
316
+ `"contract"`, `"found"` or a list in order — a `label` and an optional
317
+ `field`, a select of its statuses alone.
318
+ - `fields`: each a `type` (`text`, `date`, `select`, `number`) and a
319
+ `from`: a front-matter key, or what rness knows — `$collection`,
320
+ `$status`, `$agent` (`working` while a session marks the card),
321
+ `$session`, `$sessions`, `$id` — or a list, the first present winning.
322
+ `Path`, `Status` and GitHub's own fields cannot be declared.
323
+ - `views`: each a `name` and a `layout` (`board`, `table`, `roadmap`),
324
+ optionally `collection` and `filter`; a board's `columns`, a table's
325
+ `fields`, a roadmap's `date`.
326
+ - `colors`: option → `gray`, `blue`, `green`, `yellow`, `orange`, `red`,
327
+ `pink` or `purple`; an option not named takes its kind's colour (a
328
+ status its tone, a collection pink). A colour changed is set at the
329
+ next sync, every card keeping its value.
330
+ - A board that `rness.json` declares wrongly is refused alone, by its
331
+ full key (`"projects.marketing.views[2].date" must name a date
332
+ field`); the others sync. `validate` reports it, and also a collection
333
+ that is no directory of `.rness/`, one on two boards, a `readme` or
334
+ `updates` not there, a README that still declares (below). `readme`
335
+ and `updates` are paths within `.rness/`: a path out of it, or a file
336
+ that is a link leading out, is refused before anything is read.
337
+ - A view declared and missing is made; one whose filter or fields differ
338
+ is updated in place, keeping its id, sort and widths (GitHub returns a
339
+ filter as given and the fields in the project's order, read on
340
+ 2026-10-06); one whose layout or columns differ is made again. A view
341
+ rness made and no longer declared is deleted; one the team made is
342
+ left, and said once per clone (`note view Roadmap is not declared:
343
+ delete it on GitHub if unwanted`), as is a field no longer declared and
344
+ views out of the declared order (GitHub reorders none). A roadmap's
345
+ date is chosen in its settings: GitHub's API does not set it.
346
+ - **Presets and their revisions.** `pulse create` writes a board from a
347
+ preset, `agent-pulse` or `collection`, recording it (`"preset":
348
+ "agent-pulse/1"`). When a later rness ships a newer revision, `rness
349
+ sync` merges it into the board value by value: a value the team never
350
+ changed takes the new one, one it changed is kept, and said when the
351
+ revision changed it too (`kept projects.pulse.colors.Draft: kept orange,
352
+ agent-pulse/2 gives blue`). Removing `preset` takes a board off the
353
+ merge. An upgrade commits the result, for the team to read before
354
+ pushing.
355
+ - **From 0.20 and before.** A board declared by its number
356
+ (`"pulse": 4`) is read as its preset, and `rness sync` — which `rness
357
+ upgrade` runs — writes it whole; nothing changes on GitHub. A
358
+ collection's `README.md` that declared `description`, `statuses`,
359
+ `fields` or `labels` has them moved into its board and out of its front
360
+ matter (a value declared otherwise in both is refused, naming both).
361
+ Until then, `pulse sync` skips a board whose README still declares.
291
362
 
292
363
  - **Items**: one per document of `rness status`, an issue of
293
364
  `<org>/.rness` added to the project. GitHub shows it only to people who
@@ -377,8 +448,9 @@ Session → Working session`). A board per directory, named as
377
448
  that restricts OAuth apps must approve "Rness", as for its private
378
449
  repositories.
379
450
  - **`pulse create`** needs an `org` in `rness.json` (a blank workspace is
380
- refused) and no pulse yet. It creates the project and at once writes
381
- `"projects": { "pulse": <number> }` (and the `provider`) into `rness.json`,
451
+ refused) and no pulse yet. It creates the project and at once writes the
452
+ board whole, from the preset `agent-pulse`, into `projects.pulse` (and
453
+ the `provider`) in `rness.json`,
382
454
  then adds the fields, options and views, and runs a first sync; its
383
455
  `created` lines say what it added. If a step after the project fails, it
384
456
  exits 1 with the pulse declared, and `rness pulse sync` completes the
@@ -409,13 +481,17 @@ added to project`) may change a field of rness's items; the next sync
409
481
  writes it back.
410
482
  - **A collection's own project** (0.17.0): `rness pulse create <collection>`
411
483
  gives a directory of `.rness/` whose documents carry a status — a tab of
412
- `rness status` — a project of its own, named after it, declared as
413
- `"projects": { "pulse": 4, "marketing": 5 }`. Agent Pulse keeps every
484
+ `rness status` — a project of its own, named after it, written whole
485
+ from the preset `collection` into `projects.<collection>` (0.21.0;
486
+ before, `"projects": { "pulse": 4, "marketing": 5 }`). Agent Pulse keeps every
414
487
  other collection and the `Working` table; the collection's items leave
415
488
  it (`deleteProjectV2Item`, their issues untouched) once its project
416
489
  holds them. `pulse sync` syncs the collections' projects, then Agent
417
490
  Pulse. The project is written from the collection's files, one way:
418
- - `<collection>/README.md` is its README; its front matter may give
491
+ - `<collection>/README.md` is its README (`readme` in its board since
492
+ 0.21.0, where `description`, `statuses`, `fields` and `labels` are
493
+ declared too: what follows is how a README declared them before,
494
+ which `rness sync` moves); its front matter may give
419
495
  `description:` (the short description), `statuses:` (the columns, in
420
496
  order, each there even when empty), `fields:` (each a name, a `type` —
421
497
  `text`, `date`, `select`, `number` — and `from`, the front-matter key
@@ -434,9 +510,25 @@ added to project`) may change a field of rness's items; the next sync
434
510
  none of this reaches Agent Pulse: a workspace that declares no collection
435
511
  keeps its board as 0.16 made it.
436
512
 
437
- - **Hooks**, with `claude` in `agents` and a pulse declared: session start
438
- marks the `In progress` plans of the session's scope `Agent: working`, with
439
- the session; an edit of a document of `.rness/` marks it — one the agent has just written, with no item yet, gets its issue first
513
+ - **Hooks**, with `claude` in `agents`: what a session does to a board is
514
+ declared in its `hooks` (spec 0032, 0.21.0), each event — `session-start`,
515
+ `edit`, `session-end` — listing actions from a fixed catalogue:
516
+ `mark-in-progress` (the documents of the session's scope in the given
517
+ `collections` and `statuses`, `plans` and `In progress` by default),
518
+ `mark` (the document edited, on a board that holds it) and `clear-marks`
519
+ (the session's marks, then a sync). Marks are written through the
520
+ board's select from `$agent` and its text from `$session`, whatever their
521
+ names. A hook never runs a command `rness.json` names: the file is the
522
+ organization's and the hooks run on every developer's machine. The
523
+ preset `agent-pulse` declares all three, which is 0.20's behaviour;
524
+ removing `hooks` from a board turns its marking off, and a board without
525
+ them is only what `pulse sync` makes of the documents. For each event,
526
+ one detached `rness pulse run <event>` does every board's actions, in
527
+ the order of `projects`; a failure is recorded naming the board and the
528
+ action (`mark on Marketing failed: …`). `rness pulse mark`, which 0.20's
529
+ hooks started, stays, hidden, through 0.21.x. With Agent Pulse's preset:
530
+ session start marks the `In progress` plans of the session's scope
531
+ `Agent: working`, with the session; an edit of a document of `.rness/` marks it — one the agent has just written, with no item yet, gets its issue first
440
532
  through a sync, and so does one whose status the edit changed, so its
441
533
  card moves within seconds rather than at the session's end; an edit that
442
534
  leaves the status alone, or of any other file of `.rness/`, costs no sync;
@@ -453,6 +545,40 @@ added to project`) may change a field of rness's items; the next sync
453
545
  `the pulse needs the project scope: run rness login` or
454
546
  `cannot reach GitHub: …`. Two sessions marking one plan both write; the
455
547
  last wins.
548
+ - **Agent journal** (spec 0030, 0.21.0): how a plan was implemented, in
549
+ the agent's words. A board that holds `plans` declares
550
+ `{ "action": "journal", "to": "plan" | "repo", "limit": 5 }` at
551
+ `session-start`, and `journal-summary` at `session-end`. With a plan
552
+ `In progress` in the session's scope, the session start tells the model
553
+ to post a note — `rness pulse note`, or `rness_note` — when it chooses an
554
+ approach, deviates from the plan, is blocked, and when done: decisions
555
+ and their reasons, not steps. A note is a comment headed
556
+ `**Approach** · claude · 1a2b3c4d · \`feat/limits\` @ \`9f8e7d6\``
557
+ (`--kind`titles it; the branch and commit are those of the session's
558
+ repository), then the text, from the argument or stdin. With`"plan"`it goes to the plan's issue in`.rness`. With `"repo"`, to the plan's
559
+ implementation issue in the repository the session works in: labelled
560
+ `rness:plan`, found again by the last line of its body, made by the
561
+ first note and made a sub-issue of the plan's. The note prints where it
562
+ went (`acme/api#87`), for the pull request that completes the plan there
563
+ to carry `Closes acme/api#87`and an earlier one`Refs acme/api#87`.
564
+ GitHub closes the issue when a person merges that pull request into the
565
+ default branch; rness closes nothing. A repository with its issues off,
566
+ that refuses this login's write, or not under the organization on GitHub
567
+ sends the note to the plan's issue, its first line saying why, and the
568
+ next session start says so once. A session in `.rness/`or at the root
569
+ writes on the plan's issue. With several plans in progress,`--plan`names one. Each note holds 1 to 4,000 characters; a session posts at most`limit` per plan (`journal full for this session: put the rest in the
570
+ pull request`); a note that looks like it holds a credential (`ghp_`,
571
+ `gho_`, `github_pat_`, `AKIA…`, a private key, `xox…-`, `sk-…`) is
572
+ refused, never redacted. The counts stay in the `.rness` clone's git
573
+ directory (`rness/journal`), never pushed. At the session's end,
574
+ `journal-summary`posts`**Session summary** · claude · 1a2b3c4d ·
575
+ 47 min`, the branch and its commits since the session started (its
576
+ `HEAD`, recorded in the clone's git directory, `rness/sessions/<id>`)
577
+ and the branch's pull request: to each issue the notes went to, else to
578
+ the one plan in progress when there is a commit; with neither, nothing.
579
+ It counts toward no limit. Not verified against GitHub yet (2026-10-06):
580
+ a sub-issue across repositories, and `Closes` on a pull request merged
581
+ into the default branch.
456
582
  - **One issue per document**: before making an issue, `pulse sync` reads
457
583
  the open issues of `.rness` labelled `rness` (one request per 100, and
458
584
  only when it has one to make). One whose body starts with the document's
@@ -496,7 +622,9 @@ repository, the board).
496
622
  detected provider this version cannot talk to. The order of keys is
497
623
  `contract`, `provider`, `org`, `agents`, `projects`, `repos`, `scopes`.
498
624
  The former `"pulse": { "project": <number> }` still reads, as
499
- `"projects": { "pulse": <number> }`; both at once are refused.
625
+ `"projects": { "pulse": <number> }`; both at once are refused. Since
626
+ 0.21.0 a board in `projects` is written whole, on several lines; a
627
+ number still reads, as its preset, until `rness sync` writes it whole.
500
628
  - A provider _written_ in `rness.json` that this version cannot talk to
501
629
  (`gitlab`) is refused by `validate`, `create`, `login` and `pulse`:
502
630
  `provider "gitlab" is not supported by @rness/cli 0.17.1 (supported:
@@ -507,22 +635,26 @@ github)`. `add`, `sync` and the local commands never refuse. `null` is not a
507
635
 
508
636
  rness mcp # started by the agent over stdio, not by hand
509
637
 
510
- A local, read-only MCP server over the workspace's `.rness/`, for an agent to
511
- find what applies to the repository it works in and where a subject was
512
- decided:
638
+ A local MCP server over the workspace's `.rness/`, for an agent to find what
639
+ applies to the repository it works in and where a subject was decided. It
640
+ is read-only but for `rness_note`, listed only when a board keeps a journal
641
+ as the server starts (Claude Code starts it with the session):
513
642
 
514
- | Tool | Returns |
515
- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
516
- | `rness_context({ scope? })` | the scope of the working directory (or the one named) and what applies to it: standards, decisions, specifications, plans — id, status, title, path |
517
- | `rness_list({ collection, status? })` | every document of a collection, across scopes, optionally of one status |
518
- | `rness_read({ path })` | one file of `.rness/`, 256 KiB at most; nothing outside it |
519
- | `rness_search({ query, collection? })` | the documents that match, most matching first, with the matching lines |
643
+ | Tool | Returns |
644
+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
645
+ | `rness_context({ scope? })` | the scope of the working directory (or the one named) and what applies to it: standards, decisions, specifications, plans — id, status, title, path |
646
+ | `rness_list({ collection, status? })` | every document of a collection, across scopes, optionally of one status |
647
+ | `rness_read({ path })` | one file of `.rness/`, 256 KiB at most; nothing outside it |
648
+ | `rness_search({ query, collection? })` | the documents that match, most matching first, with the matching lines |
649
+ | `rness_note({ text, session, kind?, plan? })` | a note of the agent's journal posted to GitHub, as `rness pulse note` posts it; the issue it went to (see "Pulse") |
520
650
 
521
651
  - MCP over stdio, one message per line: the `2026-07-28` revision and the
522
652
  earlier ones that open with `initialize` (`2025-11-25` back to
523
653
  `2024-11-05`). No MCP SDK, no added dependency.
524
654
  - `.rness/` is read again on each call, so an edit shows at once. Nothing is
525
- written.
655
+ written, but by `rness_note`, which posts with your login. It takes the
656
+ session as an argument (`claude · 1a2b3c4d`, as the session start spells
657
+ it): the server's environment names none.
526
658
  - With `claude` in `agents`, `sync` registers it in each clone:
527
659
  `.mcp.json` gets `mcpServers.rness`, which runs the pinned copy
528
660
  (`node ../../.rness/node_modules/@rness/cli/dist/bin/rness.js mcp`). A
@@ -691,6 +823,60 @@ per repository, the files to commit there.
691
823
  Exit codes: 0 success, 1 failure, 2 usage — or a refusal without a TTY.
692
824
  `RNESS_DEBUG=1` adds stack traces; `RNESS_NO_DELEGATE=1` skips the delegation.
693
825
 
826
+ ## 0.21.0 — boards declared in rness.json; what a session does to a board, declared; the agent's journal
827
+
828
+ - The agent journal (spec 0030, see "Pulse"): a board declares `journal`
829
+ at `session-start` and `journal-summary` at `session-end`. The agent
830
+ posts notes on the plan it implements — `rness pulse note`, or
831
+ `rness_note` of `rness mcp` — to the plan's issue in `.rness`, or to an
832
+ implementation issue in the repository it works in, a sub-issue of the
833
+ plan's, which the pull request closes. The session's end posts a summary:
834
+ its commits, its pull request, its duration. The preset `agent-pulse`
835
+ declares neither: nothing changes until a board does.
836
+ - Boards are declared in `rness.json` (spec 0031, see "Pulse"): each one's
837
+ collections, statuses, colours, fields, labels and views, written whole.
838
+ Agent Pulse is the preset `agent-pulse`, no longer a special case of the
839
+ code. `pulse create` writes the board it makes; `rness sync` (which
840
+ `rness upgrade` runs) writes a board declared by its number whole, moves
841
+ a collection's README declarations into its board, and merges a newer
842
+ revision of a preset value by value. Views are updated in place where
843
+ GitHub allows; a view rness made and no longer declared is deleted, the
844
+ team's are said once. Nothing changes on GitHub for a board 0.20 made.
845
+ For the package's API: `Projects` is `Record<string, number |
846
+ BoardDeclaration>`; `BoardDeclaration` and `parseBoard` are exported.
847
+ - A board's `readme` and `updates` stay within `.rness/`: a path out of
848
+ it, or a link leading out, is refused before anything is read.
849
+ - What a session does to a board is declared in its `hooks` (spec 0032):
850
+ `mark-in-progress`, `mark` and `clear-marks`, from a fixed catalogue; a
851
+ hook never runs a command `rness.json` names. The preset `agent-pulse`
852
+ declares today's three, so nothing changes on GitHub. The hooks start
853
+ `rness pulse run <event>`; `rness pulse mark` stays, hidden, through
854
+ 0.21.x, for sessions started before an upgrade.
855
+ - `upgrade` run by a workspace's pinned copy installed with pnpm no longer
856
+ ends on `mod directory not found next to the @rness/cli package`. After
857
+ its commit it lists the files to commit in the clones, and that list read
858
+ the plugin's mod from the folder of the copy running it, which the install
859
+ had just removed. The list now takes the paths alone. The upgrade itself
860
+ was complete: 0.20.0 to 0.20.1 printed the error after its commit. An
861
+ upgrade from 0.20.1 still prints it, since the copy that runs it is the
862
+ old one; `pnpm dlx @rness/cli upgrade` runs the new one.
863
+
864
+ ## 0.20.1 — the workspace's line in the prompt footer, without a warning sign; the mod never installs the pin
865
+
866
+ - The workspace's line in Claude Code moves from the pinned status line to
867
+ the prompt footer, among the session modes, dim and without a sign:
868
+ `rness · web · 1 in progress`, `⚠ <n>` at its end when notes need action.
869
+ Claude Code draws every pinned line a plugin sets after a `⚠` in its
870
+ warning colour, the wrong sign for a line that informs. `rness status
871
+ --json`'s `statusLine` carries the leading `rness` again. VS Code and
872
+ mobile surfaces, which draw no session modes, show no line; the
873
+ `/rness:status` pane stays.
874
+ - The mod no longer installs the pin from inside Claude Code. It ran
875
+ `status --json` with catch-up on, so in a workspace whose pin drifts each
876
+ refresh ran `npm install` in `.rness`, without asking, and replaced a
877
+ local copy. It now runs with `RNESS_NO_DELEGATE=1`: the drift is a note
878
+ in the band, as the settings hooks report it.
879
+
694
880
  ## 0.20.0 — the status pane opens a document; ↑/↓ select, Tab changes the tab; Agent Pulse's colours and links
695
881
 
696
882
  - `/rness:status` in Claude Code: each row's status in Agent Pulse's colour;
package/dist/bin/rness.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  OWN_COMMANDS,
4
- VERSION,
5
4
  catchUp,
6
5
  findDelegate,
7
- reportError,
8
6
  run
9
- } from "../chunk-WIZHU4WT.js";
7
+ } from "../chunk-T5PINUCP.js";
8
+ import {
9
+ VERSION,
10
+ reportError
11
+ } from "../chunk-D3D2WCGW.js";
10
12
 
11
13
  // src/bin/rness.ts
12
14
  import { pathToFileURL } from "url";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bin/rness.ts","../../src/core/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { pathToFileURL } from 'node:url'\n\nimport { run } from '../cli.ts'\nimport { OWN_COMMANDS, catchUp } from '../core/catch-up.ts'\nimport { findDelegate } from '../core/delegate.ts'\nimport { MIN_NODE_LABEL, isSupportedNode } from '../core/node-version.ts'\nimport { reportError } from '../report.ts'\nimport { VERSION } from '../version.ts'\n\n// `rness context | head`: once the reader has its lines it closes the pipe and\n// every further write emits EPIPE on stdout. Nothing is left to deliver, so end\n// quietly with success. This is the one process.exit() in the code base — it\n// runs only when there is no output left to drain (see main() below).\nprocess.stdout.on('error', (e: NodeJS.ErrnoException) => {\n if (e.code === 'EPIPE') process.exit(0)\n throw e\n})\n\n// Set process.exitCode rather than calling process.exit(): process.exit()\n// terminates before Node drains an async stdout pipe, truncating piped output\n// at the ~64 KiB pipe buffer (breaks `rness context --json | jq`, `$(...)`).\nasync function main(): Promise<number> {\n if (!isSupportedNode(process.versions.node)) {\n process.stderr.write(\n `rness needs Node ${MIN_NODE_LABEL} or newer (running ${process.version})\\n`\n )\n return 1\n }\n const argv = process.argv.slice(2)\n const skipDelegation =\n OWN_COMMANDS.includes(argv[0] ?? '') ||\n process.env['RNESS_NO_DELEGATE'] === '1'\n // A pin that moved is installed first, and the fresh copy runs the command\n // as its own process (spec 0008 §4).\n const handedOff = await catchUp(argv)\n if (handedOff !== null) return handedOff\n const delegate = skipDelegation\n ? null\n : await findDelegate(process.cwd(), VERSION)\n if (delegate !== null) {\n const broken = (reason: string): Error =>\n new Error(\n `@rness/cli ${delegate.version} pinned in ${delegate.root}/.rness is not installed correctly (${reason}); reinstall in .rness/`\n )\n if (process.env['RNESS_DEBUG'] === '1') {\n process.stderr.write(\n `rness: delegating to @rness/cli ${delegate.version} (${delegate.entry})\\n`\n )\n }\n let mod: Record<string, unknown>\n try {\n mod = (await import(pathToFileURL(delegate.entry).href)) as Record<\n string,\n unknown\n >\n } catch (e) {\n throw broken(e instanceof Error ? e.message : String(e))\n }\n if (typeof mod['run'] !== 'function')\n throw broken('dist/index.js does not export run')\n const delegateRun = mod['run'] as (argv: string[]) => Promise<unknown>\n const code = await delegateRun(argv)\n return typeof code === 'number' ? code : 1\n }\n return run(argv)\n}\n\nmain()\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n process.exitCode = reportError(e)\n })\n","/** The runtime floor of the published CLI (ADR 0010): Node 22.17, the line where `fs.glob` is stable. */\nexport const MIN_NODE = { major: 22, minor: 17 } as const\nexport const MIN_NODE_LABEL = `${MIN_NODE.major}.${MIN_NODE.minor}`\n\nfunction component(version: string, index: number): number {\n const n = Number.parseInt(version.split('.')[index] ?? '', 10)\n return Number.isNaN(n) ? -1 : n\n}\n\nexport function nodeMajor(version: string): number {\n return Math.max(component(version, 0), 0)\n}\n\n/** True from `MIN_NODE` on: a newer major, or the same major from its minor. */\nexport function isSupportedNode(version: string): boolean {\n const major = component(version, 0)\n const minor = component(version, 1)\n if (major < 0 || minor < 0) return false\n if (major !== MIN_NODE.major) return major > MIN_NODE.major\n return minor >= MIN_NODE.minor\n}\n"],"mappings":";;;;;;;;;;;AACA,SAAS,qBAAqB;;;ACAvB,IAAM,WAAW,EAAE,OAAO,IAAI,OAAO,GAAG;AACxC,IAAM,iBAAiB,GAAG,SAAS,KAAK,IAAI,SAAS,KAAK;AAEjE,SAAS,UAAU,SAAiB,OAAuB;AACzD,QAAM,IAAI,OAAO,SAAS,QAAQ,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE;AAC7D,SAAO,OAAO,MAAM,CAAC,IAAI,KAAK;AAChC;AAOO,SAAS,gBAAgB,SAA0B;AACxD,QAAM,QAAQ,UAAU,SAAS,CAAC;AAClC,QAAM,QAAQ,UAAU,SAAS,CAAC;AAClC,MAAI,QAAQ,KAAK,QAAQ,EAAG,QAAO;AACnC,MAAI,UAAU,SAAS,MAAO,QAAO,QAAQ,SAAS;AACtD,SAAO,SAAS,SAAS;AAC3B;;;ADNA,QAAQ,OAAO,GAAG,SAAS,CAAC,MAA6B;AACvD,MAAI,EAAE,SAAS,QAAS,SAAQ,KAAK,CAAC;AACtC,QAAM;AACR,CAAC;AAKD,eAAe,OAAwB;AACrC,MAAI,CAAC,gBAAgB,QAAQ,SAAS,IAAI,GAAG;AAC3C,YAAQ,OAAO;AAAA,MACb,oBAAoB,cAAc,sBAAsB,QAAQ,OAAO;AAAA;AAAA,IACzE;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,iBACJ,aAAa,SAAS,KAAK,CAAC,KAAK,EAAE,KACnC,QAAQ,IAAI,mBAAmB,MAAM;AAGvC,QAAM,YAAY,MAAM,QAAQ,IAAI;AACpC,MAAI,cAAc,KAAM,QAAO;AAC/B,QAAM,WAAW,iBACb,OACA,MAAM,aAAa,QAAQ,IAAI,GAAG,OAAO;AAC7C,MAAI,aAAa,MAAM;AACrB,UAAM,SAAS,CAAC,WACd,IAAI;AAAA,MACF,cAAc,SAAS,OAAO,cAAc,SAAS,IAAI,uCAAuC,MAAM;AAAA,IACxG;AACF,QAAI,QAAQ,IAAI,aAAa,MAAM,KAAK;AACtC,cAAQ,OAAO;AAAA,QACb,mCAAmC,SAAS,OAAO,KAAK,SAAS,KAAK;AAAA;AAAA,MACxE;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,YAAO,MAAM,OAAO,cAAc,SAAS,KAAK,EAAE;AAAA,IAIpD,SAAS,GAAG;AACV,YAAM,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACzD;AACA,QAAI,OAAO,IAAI,KAAK,MAAM;AACxB,YAAM,OAAO,mCAAmC;AAClD,UAAM,cAAc,IAAI,KAAK;AAC7B,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,WAAO,OAAO,SAAS,WAAW,OAAO;AAAA,EAC3C;AACA,SAAO,IAAI,IAAI;AACjB;AAEA,KAAK,EACF,KAAK,CAAC,SAAS;AACd,UAAQ,WAAW;AACrB,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,UAAQ,WAAW,YAAY,CAAC;AAClC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/bin/rness.ts","../../src/core/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { pathToFileURL } from 'node:url'\n\nimport { run } from '../cli.ts'\nimport { OWN_COMMANDS, catchUp } from '../core/catch-up.ts'\nimport { findDelegate } from '../core/delegate.ts'\nimport { MIN_NODE_LABEL, isSupportedNode } from '../core/node-version.ts'\nimport { reportError } from '../report.ts'\nimport { VERSION } from '../version.ts'\n\n// `rness context | head`: once the reader has its lines it closes the pipe and\n// every further write emits EPIPE on stdout. Nothing is left to deliver, so end\n// quietly with success. This is the one process.exit() in the code base — it\n// runs only when there is no output left to drain (see main() below).\nprocess.stdout.on('error', (e: NodeJS.ErrnoException) => {\n if (e.code === 'EPIPE') process.exit(0)\n throw e\n})\n\n// Set process.exitCode rather than calling process.exit(): process.exit()\n// terminates before Node drains an async stdout pipe, truncating piped output\n// at the ~64 KiB pipe buffer (breaks `rness context --json | jq`, `$(...)`).\nasync function main(): Promise<number> {\n if (!isSupportedNode(process.versions.node)) {\n process.stderr.write(\n `rness needs Node ${MIN_NODE_LABEL} or newer (running ${process.version})\\n`\n )\n return 1\n }\n const argv = process.argv.slice(2)\n const skipDelegation =\n OWN_COMMANDS.includes(argv[0] ?? '') ||\n process.env['RNESS_NO_DELEGATE'] === '1'\n // A pin that moved is installed first, and the fresh copy runs the command\n // as its own process (spec 0008 §4).\n const handedOff = await catchUp(argv)\n if (handedOff !== null) return handedOff\n const delegate = skipDelegation\n ? null\n : await findDelegate(process.cwd(), VERSION)\n if (delegate !== null) {\n const broken = (reason: string): Error =>\n new Error(\n `@rness/cli ${delegate.version} pinned in ${delegate.root}/.rness is not installed correctly (${reason}); reinstall in .rness/`\n )\n if (process.env['RNESS_DEBUG'] === '1') {\n process.stderr.write(\n `rness: delegating to @rness/cli ${delegate.version} (${delegate.entry})\\n`\n )\n }\n let mod: Record<string, unknown>\n try {\n mod = (await import(pathToFileURL(delegate.entry).href)) as Record<\n string,\n unknown\n >\n } catch (e) {\n throw broken(e instanceof Error ? e.message : String(e))\n }\n if (typeof mod['run'] !== 'function')\n throw broken('dist/index.js does not export run')\n const delegateRun = mod['run'] as (argv: string[]) => Promise<unknown>\n const code = await delegateRun(argv)\n return typeof code === 'number' ? code : 1\n }\n return run(argv)\n}\n\nmain()\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n process.exitCode = reportError(e)\n })\n","/** The runtime floor of the published CLI (ADR 0010): Node 22.17, the line where `fs.glob` is stable. */\nexport const MIN_NODE = { major: 22, minor: 17 } as const\nexport const MIN_NODE_LABEL = `${MIN_NODE.major}.${MIN_NODE.minor}`\n\nfunction component(version: string, index: number): number {\n const n = Number.parseInt(version.split('.')[index] ?? '', 10)\n return Number.isNaN(n) ? -1 : n\n}\n\nexport function nodeMajor(version: string): number {\n return Math.max(component(version, 0), 0)\n}\n\n/** True from `MIN_NODE` on: a newer major, or the same major from its minor. */\nexport function isSupportedNode(version: string): boolean {\n const major = component(version, 0)\n const minor = component(version, 1)\n if (major < 0 || minor < 0) return false\n if (major !== MIN_NODE.major) return major > MIN_NODE.major\n return minor >= MIN_NODE.minor\n}\n"],"mappings":";;;;;;;;;;;;;AACA,SAAS,qBAAqB;;;ACAvB,IAAM,WAAW,EAAE,OAAO,IAAI,OAAO,GAAG;AACxC,IAAM,iBAAiB,GAAG,SAAS,KAAK,IAAI,SAAS,KAAK;AAEjE,SAAS,UAAU,SAAiB,OAAuB;AACzD,QAAM,IAAI,OAAO,SAAS,QAAQ,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE;AAC7D,SAAO,OAAO,MAAM,CAAC,IAAI,KAAK;AAChC;AAOO,SAAS,gBAAgB,SAA0B;AACxD,QAAM,QAAQ,UAAU,SAAS,CAAC;AAClC,QAAM,QAAQ,UAAU,SAAS,CAAC;AAClC,MAAI,QAAQ,KAAK,QAAQ,EAAG,QAAO;AACnC,MAAI,UAAU,SAAS,MAAO,QAAO,QAAQ,SAAS;AACtD,SAAO,SAAS,SAAS;AAC3B;;;ADNA,QAAQ,OAAO,GAAG,SAAS,CAAC,MAA6B;AACvD,MAAI,EAAE,SAAS,QAAS,SAAQ,KAAK,CAAC;AACtC,QAAM;AACR,CAAC;AAKD,eAAe,OAAwB;AACrC,MAAI,CAAC,gBAAgB,QAAQ,SAAS,IAAI,GAAG;AAC3C,YAAQ,OAAO;AAAA,MACb,oBAAoB,cAAc,sBAAsB,QAAQ,OAAO;AAAA;AAAA,IACzE;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,iBACJ,aAAa,SAAS,KAAK,CAAC,KAAK,EAAE,KACnC,QAAQ,IAAI,mBAAmB,MAAM;AAGvC,QAAM,YAAY,MAAM,QAAQ,IAAI;AACpC,MAAI,cAAc,KAAM,QAAO;AAC/B,QAAM,WAAW,iBACb,OACA,MAAM,aAAa,QAAQ,IAAI,GAAG,OAAO;AAC7C,MAAI,aAAa,MAAM;AACrB,UAAM,SAAS,CAAC,WACd,IAAI;AAAA,MACF,cAAc,SAAS,OAAO,cAAc,SAAS,IAAI,uCAAuC,MAAM;AAAA,IACxG;AACF,QAAI,QAAQ,IAAI,aAAa,MAAM,KAAK;AACtC,cAAQ,OAAO;AAAA,QACb,mCAAmC,SAAS,OAAO,KAAK,SAAS,KAAK;AAAA;AAAA,MACxE;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,YAAO,MAAM,OAAO,cAAc,SAAS,KAAK,EAAE;AAAA,IAIpD,SAAS,GAAG;AACV,YAAM,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACzD;AACA,QAAI,OAAO,IAAI,KAAK,MAAM;AACxB,YAAM,OAAO,mCAAmC;AAClD,UAAM,cAAc,IAAI,KAAK;AAC7B,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,WAAO,OAAO,SAAS,WAAW,OAAO;AAAA,EAC3C;AACA,SAAO,IAAI,IAAI;AACjB;AAEA,KAAK,EACF,KAAK,CAAC,SAAS;AACd,UAAQ,WAAW;AACrB,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,UAAQ,WAAW,YAAY,CAAC;AAClC,CAAC;","names":[]}