@rness/cli 0.20.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/README.md CHANGED
@@ -29,8 +29,8 @@ Inside a workspace, every `rness` delegates to the copy pinned in
29
29
  rness logout
30
30
  rness context [--scope <name>] [--json]
31
31
  rness status [<tab>] [--json]
32
- rness pulse create [<collection>] [-y]
33
- rness pulse sync
32
+ rness board push [-y]
33
+ rness note [--plan <path>] [--kind approach|deviation|blocker|done] [<text>]
34
34
  rness validate
35
35
  rness mcp
36
36
 
@@ -63,8 +63,8 @@ when dropped, red `?` when missing.
63
63
  - `--json`, terminal or not: the tabs and their rows — each row with the
64
64
  colour of its status on Agent Pulse (`color`, the board's name for it:
65
65
  `yellow` for `In progress`; `red` without a status) and its item there
66
- (`link`, the board filtered on its path; null without a pulse) — the
67
- board (`pulse`), and what a Claude Code session shows of the workspace,
66
+ (`link`, the board filtered on its path; null without a board, or before
67
+ `board push` creates it) — the board (`pulse`), and what a Claude Code session shows of the workspace,
68
68
  worded by the CLI — the session-start banner, the footer's label, the
69
69
  scope's plans `In progress` and the notes of the safety net (see
70
70
  "Hooks"). The plugin's mod reads it (see
@@ -197,7 +197,7 @@ the pinned copy, and computes nothing itself:
197
197
  document is read from `.rness/` without its front matter, as the
198
198
  board's card shows it; `q` brings the list back, Esc still closes. An
199
199
  edit of the document shows at the next refresh. **Agent
200
- Pulse is a link** when the workspace declares a pulse: the board from the
200
+ Pulse is a link** when the workspace has a board: the board from the
201
201
  list, the document's item (the board filtered on its path) from the
202
202
  document. The terminal opens it as it opens any hyperlink (cmd+click on
203
203
  macOS, as a rule), a desktop surface on a click. In `claude -p`, or where
@@ -244,7 +244,9 @@ workspace root's carry four Claude Code hooks, all run by the pinned copy:
244
244
  repository or nothing installed there, a `rness.json` rness refuses, a pin
245
245
  the installed copy does not match, the problems `rness validate` reports.
246
246
  A Claude Code too old for the plugin's mod gets one more line, once (see
247
- "The mod"). After a compaction, the context only.
247
+ "The mod"). When a board keeps a journal and the scope has a plan in
248
+ progress, the model gets one more line: how and when to post a note (see
249
+ "Boards"). After a compaction, the context only.
248
250
  - **Before an edit** (`PreToolUse` on `Edit|Write`): refused, with the
249
251
  reason for the model, when it would change what `sync` generates — a
250
252
  line of the block of an `AGENTS.md` or `CLAUDE.md` (the reason names the
@@ -258,10 +260,13 @@ workspace root's carry four Claude Code hooks, all run by the pinned copy:
258
260
  - **After an edit** (`PostToolUse` on `Edit|Write`): when the file is under
259
261
  `.rness/`, its front-matter problems — or those of `rness.json` — go back
260
262
  to the model, which fixes them in the same turn. Any other edit costs one
261
- path check. Stale blocks are left to `sync`. With a pulse declared, the
262
- edit is then marked on the board, broken or not; see "Pulse".
263
- - **At session end** (`SessionEnd`): with a pulse declared, clears the marks
264
- this session set and syncs the board; see "Pulse". Without one, nothing.
263
+ path check. Stale blocks are left to `sync`. When a board declares
264
+ `mark` and holds the document, the edit is then marked on it, broken or
265
+ not; see "Boards".
266
+ - **At session end** (`SessionEnd`): when a board declares `clear-marks`,
267
+ clears the marks this session set and syncs; when one declares
268
+ `journal-summary`, posts the session's summary; see "Boards". Without
269
+ either, nothing.
265
270
 
266
271
  Each hook is one fixed `sh` line: it runs
267
272
  `<.rness>/node_modules/@rness/cli/dist/bin/rness.js hook <event>` when that
@@ -273,25 +278,97 @@ own hooks stay where they are.
273
278
 
274
279
  Claude Code runs hooks from a committed settings file without asking each
275
280
  developer, including in `claude -p`. These four read `.rness/`, write
276
- nothing in the workspace and run no install. Only with a pulse declared do
281
+ no file of the workspace and run no install; with a journal, the session
282
+ start records the clone's `HEAD` in its git directory. Only with a board created do
277
283
  they reach the network, through a detached `rness` process that uses your
278
- login; when that process fails, it writes why to `pulse.json` in rness's
284
+ login; when that process fails, it writes why to `board.json` in rness's
279
285
  configuration directory (`~/.config/rness/` by default), which the next
280
- session start reads, says once and deletes. See "Pulse". Review a change to
286
+ session start reads, says once and deletes (0.21 wrote `pulse.json`: read
287
+ once too). See "Boards". Review a change to
281
288
  them like code. Verified with Claude Code 2.1.284 on 2026-09-29: in a clone
282
289
  and at the root, the model received the context; after an edit that broke a
283
290
  status, it received the problem. The `sh` line is not verified on Windows.
284
291
 
285
- ### Pulse
286
-
287
- rness pulse create # once per organization: the board, then a first sync
288
- rness pulse create marketing # a collection's own project (0.17.0)
289
- rness pulse sync # every declared project, as often as wanted
290
-
291
- A projection of rness's state on the organization's GitHub Projects: a
292
- project named **Agent Pulse**, where the team sees — and reads — every
293
- document of `.rness/`, and sees when an agent is at work on one. rness
294
- writes the board; if it is not in rness, it does not belong there.
292
+ ### Boards
293
+
294
+ rness board push # every board rness.json declares: created if it is not yet, then brought up to date
295
+ rness note "…" # the agent's journal on the plan in progress
296
+
297
+ A board is a view of the rules the agent applies: the documents of
298
+ `.rness/` — decisions, specifications, plans, a team's own collections —
299
+ and where an agent is at work on them. The provider supplies the tool:
300
+ GitHub Projects today. Each board is declared in `rness.json`, under
301
+ `boards`: first **Agent Pulse** (`pulse`), where the team sees — and reads
302
+ — every document of `.rness/`, and sees when an agent is at work on one.
303
+ rness writes the boards; if it is not in rness, it does not belong there.
304
+
305
+ - **Boards declared in `rness.json`** (0.21.0; `boards` since 0.22.0,
306
+ `projects` before): `boards` maps a name to a board, written whole — its
307
+ `number`, `collections`, `colors`, `fields`, `labels`, `views`, and
308
+ optionally `title`, `description`, `readme` and `updates` (files of
309
+ `.rness/`) — or to a preset not created yet, by name:
310
+ `"boards": { "pulse": "agent-pulse", "marketing": "collection" }`
311
+ (`agent-pulse` for `pulse`, `collection` for a collection's board; see
312
+ "A board created"). What `rness.json` says is
313
+ the board; what it does not declare, the board does not have, but the
314
+ engine's own: one issue of `.rness` per document, found by its `Path`,
315
+ its `Status`, the label `rness`. Nothing knows Agent Pulse by name: it
316
+ is a board like any other, made from the preset `agent-pulse`, and the
317
+ example a team copies for another.
318
+ - `collections`: `"all"` (ADR, specs and plans with the contract's
319
+ statuses, then every other directory `rness status` finds, but those
320
+ another board holds), or directories, each with `statuses` —
321
+ `"contract"`, `"found"` or a list in order — a `label` and an optional
322
+ `field`, a select of its statuses alone.
323
+ - `fields`: each a `type` (`text`, `date`, `select`, `number`) and a
324
+ `from`: a front-matter key, or what rness knows — `$collection`,
325
+ `$status`, `$agent` (`working` while a session marks the card),
326
+ `$session`, `$sessions`, `$id` — or a list, the first present winning.
327
+ `Path`, `Status` and GitHub's own fields cannot be declared.
328
+ - `views`: each a `name` and a `layout` (`board`, `table`, `roadmap`),
329
+ optionally `collection` and `filter`; a board's `columns`, a table's
330
+ `fields`, a roadmap's `date`.
331
+ - `colors`: option → `gray`, `blue`, `green`, `yellow`, `orange`, `red`,
332
+ `pink` or `purple`; an option not named takes its kind's colour (a
333
+ status its tone, a collection pink). A colour changed is set at the
334
+ next sync, every card keeping its value.
335
+ - A board that `rness.json` declares wrongly is refused alone, by its
336
+ full key (`"boards.marketing.views[2].date" must name a date
337
+ field`); the others sync. `validate` reports it, and also a collection
338
+ that is no directory of `.rness/`, one on two boards, a `readme` or
339
+ `updates` not there, a README that still declares (below). `readme`
340
+ and `updates` are paths within `.rness/`: a path out of it, or a file
341
+ that is a link leading out, is refused before anything is read.
342
+ - A view declared and missing is made; one whose filter or fields differ
343
+ is updated in place, keeping its id, sort and widths (GitHub returns a
344
+ filter as given and the fields in the project's order, read on
345
+ 2026-10-06); one whose layout or columns differ is made again. A view
346
+ rness made and no longer declared is deleted; one the team made is
347
+ left, and said once per clone (`note view Roadmap is not declared:
348
+ delete it on GitHub if unwanted`), as is a field no longer declared and
349
+ views out of the declared order (GitHub reorders none). A roadmap's
350
+ date is chosen in its settings: GitHub's API does not set it.
351
+ - **Presets and their revisions.** `board push` writes a board from a
352
+ preset, `agent-pulse` or `collection`, recording it (`"preset":
353
+ "agent-pulse/1"`). When a later rness ships a newer revision, `rness
354
+ sync` merges it into the board value by value: a value the team never
355
+ changed takes the new one, one it changed is kept, and said when the
356
+ revision changed it too (`kept boards.pulse.colors.Draft: kept orange,
357
+ agent-pulse/2 gives blue`). Removing `preset` takes a board off the
358
+ merge. An upgrade commits the result, for the team to read before
359
+ pushing.
360
+ - **From 0.20 and before.** A board declared by its number
361
+ (`"pulse": 4`) is read as its preset, and `rness sync` — which `rness
362
+ upgrade` runs — writes it whole; nothing changes on GitHub. A
363
+ collection's `README.md` that declared `description`, `statuses`,
364
+ `fields` or `labels` has them moved into its board and out of its front
365
+ matter (a value declared otherwise in both is refused, naming both).
366
+ Until then, `board push` skips a board whose README still declares.
367
+ - **From 0.21 and before.** `projects`, and the older
368
+ `"pulse": { "project": <number> }`, still read as `boards`; more than
369
+ one of the three at once is refused. `rness sync` renames the key
370
+ (`renamed projects to boards in .rness/rness.json`), the boards as they
371
+ were, and every write of `rness.json` writes `boards`.
295
372
 
296
373
  - **Items**: one per document of `rness status`, an issue of
297
374
  `<org>/.rness` added to the project. GitHub shows it only to people who
@@ -327,10 +404,9 @@ status, …`) and its body the document (see **Body**). It carries the
327
404
  on `main`, and branches of it are rare). The project is linked to
328
405
  `.rness`, so Agent Pulse shows in its Projects tab. Comments are the
329
406
  team's: a spec's discussion lives on its issue, and rness never writes
330
- or deletes one. `pulse create` and `pulse sync` need Issues on
331
- `.rness`. Without them they stop before
332
- writing anything: `the pulse needs Issues on <org>/.rness: turn them on
333
- in its Settings`.
407
+ or deletes one. `board push` needs Issues on `.rness`. Without them it
408
+ stops before writing anything: `boards need Issues on <org>/.rness: turn
409
+ them on in its Settings`.
334
410
  - **Body**: the document, the same bytes for the same document. It starts
335
411
  with a first line: the document's path and a link to the file. Then comes
336
412
  the document without its front matter (the status is a field) and
@@ -363,35 +439,46 @@ Session → Working session`). A board per directory, named as
363
439
  `rness status` names its tab, filtered on its `Collection`, its columns
364
440
  that collection's own steps: each collection has its own status field
365
441
  (`ADR status`, `Specs status`, …; a directory without statuses is columned
366
- by `Status`, which holds them all for the `All` table). `pulse sync`
442
+ by `Status`, which holds them all for the `All` table). `board push`
367
443
  remakes a board built by an earlier version (its URL changes once); a `Working` table, filtered on `Agent: working`. A first view named `View 1` (GitHub's default) becomes `All` when no `All` exists, on create and on sync, showing Title, Collection, Status and Working session. Options are coloured (statuses by lifecycle: blue proposed, purple approved, yellow in progress, red blocked or rejected, yellow superseded, green done, gray abandoned; collections and `working` too); a sync recolours rness's options and leaves other options' colours alone. rness does
368
444
  not set the project's visibility: the project gets GitHub's default for a
369
445
  new organization project, which is private (`public: false`). Verified
370
446
  with Claude Code 2.1.284 on 2026-09-29.
371
- - **Login**: the pulse needs the `project` scope. `rness login` asks for it
372
- when the workspace declares a pulse, or when `pulse create` needs it (in a
373
- terminal it offers to log in; with `-y` or off a terminal it stops with
374
- `the pulse needs a GitHub login: run rness login` or
375
- `the pulse needs the project scope: run rness login`). A developer who
376
- never uses the pulse grants nothing more than `repo read:org`. When GitHub
377
- cannot be asked, the pulse says so (`cannot reach GitHub: …`, or GitHub's
378
- own answer) instead of asking for a login. A classic `GITHUB_TOKEN` works
379
- if it carries the scope; a fine-grained or GitHub App token reports no
380
- scope, so the pulse refuses it whatever its permissions. An organization
447
+ - **Login**: boards need the `project` scope. `rness login` asks for it
448
+ when the workspace declares a board, and `board push` when it creates one
449
+ (in a terminal it offers to log in; with `-y` or off a terminal it stops
450
+ with `boards need a GitHub login: run rness login` or
451
+ `boards need the project scope: run rness login`). A developer who never
452
+ uses boards grants nothing more than `repo read:org`. When GitHub cannot
453
+ be asked, rness says so (`cannot reach GitHub: …`, or GitHub's own
454
+ answer) instead of asking for a login. A classic `GITHUB_TOKEN` works if
455
+ it carries the scope; a fine-grained or GitHub App token reports no
456
+ scope, so boards refuse it whatever its permissions. An organization
381
457
  that restricts OAuth apps must approve "Rness", as for its private
382
458
  repositories.
383
- - **`pulse create`** needs an `org` in `rness.json` (a blank workspace is
384
- refused) and no pulse yet. It creates the project and at once writes
385
- `"projects": { "pulse": <number> }` (and the `provider`) into `rness.json`,
386
- then adds the fields, options and views, and runs a first sync; its
387
- `created` lines say what it added. If a step after the project fails, it
388
- exits 1 with the pulse declared, and `rness pulse sync` completes the
389
- layout. Commit `rness.json` in `.rness`. The number is that of the
390
- project in the organization. A CLI older than 0.12.0 refuses the key, so
391
- the pin moves first (`rness upgrade`). A workspace without a `provider`
392
- whose repositories look like GitLab is refused before anything is
393
- created: write `"provider": "github"` if the organization is on GitHub.
394
- - **`pulse sync`** works in two passes. First every document gets its
459
+ - **A board created** (0.22.0): a board of `boards` without a `number` —
460
+ a preset's name, or a declaration that leaves it out — is not on GitHub
461
+ yet, and `board push` creates it. Its name is `pulse` or a collection of
462
+ `.rness/`, else it is refused before GitHub is asked anything (`validate`
463
+ says so too). In a terminal push asks once
464
+ (`Create Agent Pulse (pulse) on GitHub, in acme?`); `-y` does not ask.
465
+ Off a terminal without `-y`, or declined, it creates none, pushes the
466
+ others and exits 1: `not created: pulse — rness board push in a terminal,
467
+ or with --yes, creates it`. Then, in order: the login and its `project`
468
+ scope, Issues on `.rness`, the project, and at once its `number` in
469
+ `rness.json` — a preset's name is written whole from that preset, a
470
+ declaration gets its `number` and nothing else — with the `provider`;
471
+ then the fields, options and views, and a first push. Its `created` lines
472
+ say what it added, and the last line what to commit
473
+ (`git -C .rness commit -am "chore: board pulse"`). If a step after the
474
+ project fails, it exits 1 with the number written, and the next
475
+ `board push` completes the layout. A project deleted on GitHub is not
476
+ made again: push stops on `GitHub has no project 4 in acme`; removing the
477
+ `number` asks for a new one. A workspace needs an `org` (a blank one is
478
+ refused); one without a `provider` whose repositories look like GitLab
479
+ is refused before anything is created: write `"provider": "github"` if
480
+ the organization is on GitHub.
481
+ - **`board push`** works in two passes. First every document gets its
395
482
  issue — created, converted from a 0.12.0 draft, or reopened — with its
396
483
  title, label and fields. Then the bodies whose digest differs are
397
484
  written, since a body links to other documents' issues by number. It
@@ -411,15 +498,18 @@ unchanged`.
411
498
  as the team's item. A workflow someone turns on (`Item closed`, `Item
412
499
  added to project`) may change a field of rness's items; the next sync
413
500
  writes it back.
414
- - **A collection's own project** (0.17.0): `rness pulse create <collection>`
415
- gives a directory of `.rness/` whose documents carry a status — a tab of
416
- `rness status` — a project of its own, named after it, declared as
417
- `"projects": { "pulse": 4, "marketing": 5 }`. Agent Pulse keeps every
418
- other collection and the `Working` table; the collection's items leave
419
- it (`deleteProjectV2Item`, their issues untouched) once its project
420
- holds them. `pulse sync` syncs the collections' projects, then Agent
421
- Pulse. The project is written from the collection's files, one way:
422
- - `<collection>/README.md` is its README; its front matter may give
501
+ - **A collection's own board** (0.17.0): `"marketing": "collection"` in
502
+ `boards`, then `rness board push`, gives a directory of `.rness/` whose
503
+ documents carry a status — a tab of `rness status` — a project of its
504
+ own, named after it, written whole from the preset `collection` into
505
+ `boards.marketing`. Agent Pulse keeps every other collection and the
506
+ `Working` table; the collection's items leave it (`deleteProjectV2Item`,
507
+ their issues untouched) once its board holds them. `board push` pushes
508
+ the collections' boards, then Agent Pulse. The project is written from the collection's files, one way:
509
+ - `<collection>/README.md` is its README (`readme` in its board since
510
+ 0.21.0, where `description`, `statuses`, `fields` and `labels` are
511
+ declared too: what follows is how a README declared them before,
512
+ which `rness sync` moves); its front matter may give
423
513
  `description:` (the short description), `statuses:` (the columns, in
424
514
  order, each there even when empty), `fields:` (each a name, a `type` —
425
515
  `text`, `date`, `select`, `number` — and `from`, the front-matter key
@@ -438,9 +528,25 @@ added to project`) may change a field of rness's items; the next sync
438
528
  none of this reaches Agent Pulse: a workspace that declares no collection
439
529
  keeps its board as 0.16 made it.
440
530
 
441
- - **Hooks**, with `claude` in `agents` and a pulse declared: session start
442
- marks the `In progress` plans of the session's scope `Agent: working`, with
443
- 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
531
+ - **Hooks**, with `claude` in `agents`: what a session does to a board is
532
+ declared in its `hooks` (spec 0032, 0.21.0), each event — `session-start`,
533
+ `edit`, `session-end` — listing actions from a fixed catalogue:
534
+ `mark-in-progress` (the documents of the session's scope in the given
535
+ `collections` and `statuses`, `plans` and `In progress` by default),
536
+ `mark` (the document edited, on a board that holds it) and `clear-marks`
537
+ (the session's marks, then a sync). Marks are written through the
538
+ board's select from `$agent` and its text from `$session`, whatever their
539
+ names. A hook never runs a command `rness.json` names: the file is the
540
+ organization's and the hooks run on every developer's machine. The
541
+ preset `agent-pulse` declares all three, which is 0.20's behaviour;
542
+ removing `hooks` from a board turns its marking off, and a board without
543
+ them is only what `board push` makes of the documents. For each event,
544
+ one detached `rness board run <event>` does every board's actions, in
545
+ the order of `boards`; a failure is recorded naming the board and the
546
+ action (`mark on Marketing failed: …`). A board not created yet runs
547
+ none. With Agent Pulse's preset:
548
+ session start marks the `In progress` plans of the session's scope
549
+ `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
444
550
  through a sync, and so does one whose status the edit changed, so its
445
551
  card moves within seconds rather than at the session's end; an edit that
446
552
  leaves the status alone, or of any other file of `.rness/`, costs no sync;
@@ -453,11 +559,45 @@ added to project`) may change a field of rness's items; the next sync
453
559
  its file 3 s after `/exit`. It uses your login: with none, without the
454
560
  scope, or when GitHub cannot be reached, nothing is sent, and the next
455
561
  session start's banner says why, once:
456
- `rness: pulse not updated — <reason>`, such as
457
- `the pulse needs the project scope: run rness login` or
562
+ `rness: board not updated — <reason>`, such as
563
+ `boards need the project scope: run rness login` or
458
564
  `cannot reach GitHub: …`. Two sessions marking one plan both write; the
459
565
  last wins.
460
- - **One issue per document**: before making an issue, `pulse sync` reads
566
+ - **Agent journal** (spec 0030, 0.21.0): how a plan was implemented, in
567
+ the agent's words. A board that holds `plans` declares
568
+ `{ "action": "journal", "to": "plan" | "repo", "limit": 5 }` at
569
+ `session-start`, and `journal-summary` at `session-end`. With a plan
570
+ `In progress` in the session's scope, the session start tells the model
571
+ to post a note — `rness note`, or `rness_note` — when it chooses an
572
+ approach, deviates from the plan, is blocked, and when done: decisions
573
+ and their reasons, not steps. A note is a comment headed
574
+ `**Approach** · claude · 1a2b3c4d · \`feat/limits\` @ \`9f8e7d6\``
575
+ (`--kind`titles it; the branch and commit are those of the session's
576
+ 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
577
+ implementation issue in the repository the session works in: labelled
578
+ `rness:plan`, found again by the last line of its body, made by the
579
+ first note and made a sub-issue of the plan's. The note prints where it
580
+ went (`acme/api#87`), for the pull request that completes the plan there
581
+ to carry `Closes acme/api#87`and an earlier one`Refs acme/api#87`.
582
+ GitHub closes the issue when a person merges that pull request into the
583
+ default branch; rness closes nothing. A repository with its issues off,
584
+ that refuses this login's write, or not under the organization on GitHub
585
+ sends the note to the plan's issue, its first line saying why, and the
586
+ next session start says so once. A session in `.rness/`or at the root
587
+ 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
588
+ pull request`); a note that looks like it holds a credential (`ghp_`,
589
+ `gho_`, `github_pat_`, `AKIA…`, a private key, `xox…-`, `sk-…`) is
590
+ refused, never redacted. The counts stay in the `.rness` clone's git
591
+ directory (`rness/journal`), never pushed. At the session's end,
592
+ `journal-summary`posts`**Session summary** · claude · 1a2b3c4d ·
593
+ 47 min`, the branch and its commits since the session started (its
594
+ `HEAD`, recorded in the clone's git directory, `rness/sessions/<id>`)
595
+ and the branch's pull request: to each issue the notes went to, else to
596
+ the one plan in progress when there is a commit; with neither, nothing.
597
+ It counts toward no limit. Not verified against GitHub yet (2026-10-06):
598
+ a sub-issue across repositories, and `Closes` on a pull request merged
599
+ into the default branch.
600
+ - **One issue per document**: before making an issue, `board push` reads
461
601
  the open issues of `.rness` labelled `rness` (one request per 100, and
462
602
  only when it has one to make). One whose body starts with the document's
463
603
  first line was made by an earlier sync, whose item the board's listing
@@ -474,10 +614,16 @@ added to project`) may change a field of rness's items; the next sync
474
614
  documents, 2026-09-30): the migration from 0.12.0 took 169 s with no
475
615
  wait; a sync with nothing to change, 4 s; the listing, one page of
476
616
  852 KB in about 1 s.
477
- - **Not yet**: GitLab and Atlassian. `create` lists them, disabled; a
478
- workspace whose `rness.json` names one is refused by `pulse`. `Waiting`
479
- and `Review` statuses, several agents on one board and hooks for agents
480
- other than Claude Code are not built.
617
+ - **Not yet**: GitLab and Atlassian, and their own boards. `create` lists
618
+ them, disabled; a workspace whose `rness.json` names one is refused by
619
+ `board push`. `Waiting` and `Review` statuses, several agents on one
620
+ board and hooks for agents other than Claude Code are not built.
621
+ - **`rness pulse`**, the name of all this until 0.21: hidden, each of its
622
+ commands still runs as its new one and says so once on stderr
623
+ (`rness pulse sync is now rness board push`); `rness pulse create
624
+ [<collection>]` adds the preset's name to `boards` when it is not there,
625
+ then pushes without asking. `rness pulse mark`, which 0.20's hooks
626
+ started, exits 0 and does nothing. The alias goes in a later minor.
481
627
 
482
628
  ### Provider
483
629
 
@@ -496,13 +642,16 @@ repository, the board).
496
642
  names.
497
643
  - A workspace without the key is read from its first repository URL: a
498
644
  GitLab host reads as `gitlab`, any other host — or no repository — as
499
- `github`. Nothing to change: `pulse create` writes it, and refuses a
500
- detected provider this version cannot talk to. The order of keys is
501
- `contract`, `provider`, `org`, `agents`, `projects`, `repos`, `scopes`.
502
- The former `"pulse": { "project": <number> }` still reads, as
503
- `"projects": { "pulse": <number> }`; both at once are refused.
645
+ `github`. Nothing to change: `board push` writes it when it creates a
646
+ board, and refuses a detected provider this version cannot talk to. The
647
+ order of keys is `contract`, `provider`, `org`, `agents`, `boards`,
648
+ `repos`, `scopes`. The former `projects` and
649
+ `"pulse": { "project": <number> }` still read, as `boards`; more than one
650
+ of them at once is refused. A board in `boards` is written whole, on
651
+ several lines, once created; a number still reads, as its preset, until
652
+ `rness sync` writes it whole.
504
653
  - A provider _written_ in `rness.json` that this version cannot talk to
505
- (`gitlab`) is refused by `validate`, `create`, `login` and `pulse`:
654
+ (`gitlab`) is refused by `validate`, `create`, `login` and `board`:
506
655
  `provider "gitlab" is not supported by @rness/cli 0.17.1 (supported:
507
656
  github)`. `add`, `sync` and the local commands never refuse. `null` is not a
508
657
  value: an absent key means none.
@@ -511,22 +660,26 @@ github)`. `add`, `sync` and the local commands never refuse. `null` is not a
511
660
 
512
661
  rness mcp # started by the agent over stdio, not by hand
513
662
 
514
- A local, read-only MCP server over the workspace's `.rness/`, for an agent to
515
- find what applies to the repository it works in and where a subject was
516
- decided:
663
+ A local MCP server over the workspace's `.rness/`, for an agent to find what
664
+ applies to the repository it works in and where a subject was decided. It
665
+ is read-only but for `rness_note`, listed only when a board keeps a journal
666
+ as the server starts (Claude Code starts it with the session):
517
667
 
518
- | Tool | Returns |
519
- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
520
- | `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 |
521
- | `rness_list({ collection, status? })` | every document of a collection, across scopes, optionally of one status |
522
- | `rness_read({ path })` | one file of `.rness/`, 256 KiB at most; nothing outside it |
523
- | `rness_search({ query, collection? })` | the documents that match, most matching first, with the matching lines |
668
+ | Tool | Returns |
669
+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
670
+ | `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 |
671
+ | `rness_list({ collection, status? })` | every document of a collection, across scopes, optionally of one status |
672
+ | `rness_read({ path })` | one file of `.rness/`, 256 KiB at most; nothing outside it |
673
+ | `rness_search({ query, collection? })` | the documents that match, most matching first, with the matching lines |
674
+ | `rness_note({ text, session, kind?, plan? })` | a note of the agent's journal posted to GitHub, as `rness note` posts it; the issue it went to (see "Boards") |
524
675
 
525
676
  - MCP over stdio, one message per line: the `2026-07-28` revision and the
526
677
  earlier ones that open with `initialize` (`2025-11-25` back to
527
678
  `2024-11-05`). No MCP SDK, no added dependency.
528
679
  - `.rness/` is read again on each call, so an edit shows at once. Nothing is
529
- written.
680
+ written, but by `rness_note`, which posts with your login. It takes the
681
+ session as an argument (`claude · 1a2b3c4d`, as the session start spells
682
+ it): the server's environment names none.
530
683
  - With `claude` in `agents`, `sync` registers it in each clone:
531
684
  `.mcp.json` gets `mcpServers.rness`, which runs the pinned copy
532
685
  (`node ../../.rness/node_modules/@rness/cli/dist/bin/rness.js mcp`). A
@@ -558,7 +711,7 @@ anonymously.
558
711
  it — CI needs no login.
559
712
  - rness asks for `repo` and `read:org`: GitHub has no read-only scope for
560
713
  private repositories. It only lists and clones. Where the workspace
561
- declares a pulse it asks for `project` too; see "Pulse".
714
+ declares a board it asks for `project` too; see "Boards".
562
715
  - A new workspace has to reach GitHub before teammates can join it. Logged
563
716
  in, the `create` wizard offers to do it: it creates the private repository
564
717
  `<org>/.rness` and pushes the context with `git`. Declined, refused by
@@ -695,6 +848,75 @@ per repository, the files to commit there.
695
848
  Exit codes: 0 success, 1 failure, 2 usage — or a refusal without a TTY.
696
849
  `RNESS_DEBUG=1` adds stack traces; `RNESS_NO_DELEGATE=1` skips the delegation.
697
850
 
851
+ ## 0.22.0 — `board`, not `pulse`: `rness board push`, `rness note`, `boards` in rness.json
852
+
853
+ - One word for the view, whatever the provider (spec 0033, see "Boards"):
854
+ `rness board push` replaces `rness pulse sync`, `rness note` replaces
855
+ `rness pulse note`, and the hooks start `rness board run`. `rness.json`
856
+ names its boards `boards`; `projects` and the older `pulse` still read,
857
+ and `rness sync` (which `rness upgrade` runs) renames the key. `pulse`
858
+ stays the name of the board rness ships, Agent Pulse.
859
+ - A board comes to exist by its declaration and a push: there is no
860
+ `create`. A board of `boards` without a `number`, or a preset by name
861
+ (`"pulse": "agent-pulse"`, `"marketing": "collection"`), is created by
862
+ `rness board push` once asked (`-y` off a terminal), its number then
863
+ written into `rness.json`.
864
+ - `rness pulse …` still runs, hidden, as its new command, and says so on
865
+ stderr; it goes in a later minor. `rness pulse mark`, kept through
866
+ 0.21.x for 0.20's hooks, now exits 0 and does nothing. Spec 0031's
867
+ `eject` is not built and will not be: every board is written whole.
868
+ - The session-start notice is `rness: board not updated — …`, its record
869
+ `board.json` (one 0.21 left in `pulse.json` is said once), and messages
870
+ name boards: `boards need a GitHub login: run rness login`.
871
+ - Upgrade with `npx @rness/cli@latest upgrade`, which runs the new copy
872
+ throughout: a 0.21 copy cannot read `boards` (`unknown key "boards"`).
873
+ - For the package's API: `Manifest.projects` is `Manifest.boards`
874
+ (`Boards`, a record of `BoardEntry`: a number, a preset's name, or a
875
+ `BoardDeclaration`, whose `number` is null until the board is created).
876
+
877
+ ## 0.21.0 — boards declared in rness.json; what a session does to a board, declared; the agent's journal
878
+
879
+ - The agent journal (spec 0030, see "Pulse"): a board declares `journal`
880
+ at `session-start` and `journal-summary` at `session-end`. The agent
881
+ posts notes on the plan it implements — `rness pulse note`, or
882
+ `rness_note` of `rness mcp` — to the plan's issue in `.rness`, or to an
883
+ implementation issue in the repository it works in, a sub-issue of the
884
+ plan's, which the pull request closes. The session's end posts a summary:
885
+ its commits, its pull request, its duration. The preset `agent-pulse`
886
+ declares neither: nothing changes until a board does.
887
+ - Boards are declared in `rness.json` (spec 0031, see "Pulse"): each one's
888
+ collections, statuses, colours, fields, labels and views, written whole.
889
+ Agent Pulse is the preset `agent-pulse`, no longer a special case of the
890
+ code. `pulse create` writes the board it makes; `rness sync` (which
891
+ `rness upgrade` runs) writes a board declared by its number whole, moves
892
+ a collection's README declarations into its board, and merges a newer
893
+ revision of a preset value by value. Views are updated in place where
894
+ GitHub allows; a view rness made and no longer declared is deleted, the
895
+ team's are said once. Nothing changes on GitHub for a board 0.20 made.
896
+ For the package's API: `Projects` is `Record<string, number |
897
+ BoardDeclaration>`; `BoardDeclaration` and `parseBoard` are exported.
898
+ - A board's `readme` and `updates` stay within `.rness/`: a path out of
899
+ it, or a link leading out, is refused before anything is read.
900
+ - What a session does to a board is declared in its `hooks` (spec 0032):
901
+ `mark-in-progress`, `mark` and `clear-marks`, from a fixed catalogue; a
902
+ hook never runs a command `rness.json` names. The preset `agent-pulse`
903
+ declares today's three, so nothing changes on GitHub. The hooks start
904
+ `rness pulse run <event>`; `rness pulse mark` stays, hidden, through
905
+ 0.21.x, for sessions started before an upgrade.
906
+ - `upgrade` run by a workspace's pinned copy installed with pnpm no longer
907
+ ends on `mod directory not found next to the @rness/cli package`. After
908
+ its commit it lists the files to commit in the clones, and that list read
909
+ the plugin's mod from the folder of the copy running it, which the install
910
+ had just removed. The list now takes the paths alone. The upgrade itself
911
+ was complete: 0.20.0 to 0.20.1 printed the error after its commit.
912
+ - An upgrade to 0.21.0 run by a 0.20 copy (`pnpm rness upgrade` from
913
+ `.rness/`) ends, after its commit, on `rness.json: "projects" must map
914
+ names to distinct project numbers`: the old copy, finishing, cannot read
915
+ the boards the new version has just written. The upgrade is complete, and
916
+ `rness validate` passes. `npx @rness/cli@latest upgrade` runs the new
917
+ copy throughout; `pnpm dlx @rness/cli` takes the previous version for a
918
+ day after a release, pnpm refusing a version younger than 24 hours.
919
+
698
920
  ## 0.20.1 — the workspace's line in the prompt footer, without a warning sign; the mod never installs the pin
699
921
 
700
922
  - The workspace's line in Claude Code moves from the pinned status line to
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-2AFAQO5B.js";
7
+ } from "../chunk-S4NARIQP.js";
8
+ import {
9
+ VERSION,
10
+ reportError
11
+ } from "../chunk-XCO5ZT2H.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":[]}