@rness/cli 0.20.1 → 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 +195 -29
- package/dist/bin/rness.js +5 -3
- package/dist/bin/rness.js.map +1 -1
- package/dist/chunk-4BM4FUWO.js +263 -0
- package/dist/chunk-4BM4FUWO.js.map +1 -0
- package/dist/chunk-D3D2WCGW.js +6897 -0
- package/dist/chunk-D3D2WCGW.js.map +1 -0
- package/dist/chunk-T5PINUCP.js +5582 -0
- package/dist/chunk-T5PINUCP.js.map +1 -0
- package/dist/index.js +32 -26
- package/dist/index.js.map +1 -1
- package/dist/note-RBG7OXVI.js +22 -0
- package/dist/note-RBG7OXVI.js.map +1 -0
- package/dist/summary-UCMFESSA.js +91 -0
- package/dist/summary-UCMFESSA.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-2AFAQO5B.js +0 -10305
- package/dist/chunk-2AFAQO5B.js.map +0 -1
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
|
|
|
@@ -244,7 +245,9 @@ workspace root's carry four Claude Code hooks, all run by the pinned copy:
|
|
|
244
245
|
repository or nothing installed there, a `rness.json` rness refuses, a pin
|
|
245
246
|
the installed copy does not match, the problems `rness validate` reports.
|
|
246
247
|
A Claude Code too old for the plugin's mod gets one more line, once (see
|
|
247
|
-
"The mod").
|
|
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.
|
|
248
251
|
- **Before an edit** (`PreToolUse` on `Edit|Write`): refused, with the
|
|
249
252
|
reason for the model, when it would change what `sync` generates — a
|
|
250
253
|
line of the block of an `AGENTS.md` or `CLAUDE.md` (the reason names the
|
|
@@ -258,10 +261,13 @@ workspace root's carry four Claude Code hooks, all run by the pinned copy:
|
|
|
258
261
|
- **After an edit** (`PostToolUse` on `Edit|Write`): when the file is under
|
|
259
262
|
`.rness/`, its front-matter problems — or those of `rness.json` — go back
|
|
260
263
|
to the model, which fixes them in the same turn. Any other edit costs one
|
|
261
|
-
path check. Stale blocks are left to `sync`.
|
|
262
|
-
edit is then marked on
|
|
263
|
-
|
|
264
|
-
|
|
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.
|
|
265
271
|
|
|
266
272
|
Each hook is one fixed `sh` line: it runs
|
|
267
273
|
`<.rness>/node_modules/@rness/cli/dist/bin/rness.js hook <event>` when that
|
|
@@ -273,7 +279,8 @@ own hooks stay where they are.
|
|
|
273
279
|
|
|
274
280
|
Claude Code runs hooks from a committed settings file without asking each
|
|
275
281
|
developer, including in `claude -p`. These four read `.rness/`, write
|
|
276
|
-
|
|
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
|
|
277
284
|
they reach the network, through a detached `rness` process that uses your
|
|
278
285
|
login; when that process fails, it writes why to `pulse.json` in rness's
|
|
279
286
|
configuration directory (`~/.config/rness/` by default), which the next
|
|
@@ -288,10 +295,70 @@ status, it received the problem. The `sh` line is not verified on Windows.
|
|
|
288
295
|
rness pulse create marketing # a collection's own project (0.17.0)
|
|
289
296
|
rness pulse sync # every declared project, as often as wanted
|
|
290
297
|
|
|
291
|
-
A projection of rness's state on the organization's GitHub Projects
|
|
292
|
-
|
|
293
|
-
document of `.rness/`, and sees when an agent is at work
|
|
294
|
-
writes the
|
|
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.
|
|
295
362
|
|
|
296
363
|
- **Items**: one per document of `rness status`, an issue of
|
|
297
364
|
`<org>/.rness` added to the project. GitHub shows it only to people who
|
|
@@ -381,8 +448,9 @@ Session → Working session`). A board per directory, named as
|
|
|
381
448
|
that restricts OAuth apps must approve "Rness", as for its private
|
|
382
449
|
repositories.
|
|
383
450
|
- **`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
|
-
|
|
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`,
|
|
386
454
|
then adds the fields, options and views, and runs a first sync; its
|
|
387
455
|
`created` lines say what it added. If a step after the project fails, it
|
|
388
456
|
exits 1 with the pulse declared, and `rness pulse sync` completes the
|
|
@@ -413,13 +481,17 @@ added to project`) may change a field of rness's items; the next sync
|
|
|
413
481
|
writes it back.
|
|
414
482
|
- **A collection's own project** (0.17.0): `rness pulse create <collection>`
|
|
415
483
|
gives a directory of `.rness/` whose documents carry a status — a tab of
|
|
416
|
-
`rness status` — a project of its own, named after it,
|
|
417
|
-
|
|
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
|
|
418
487
|
other collection and the `Working` table; the collection's items leave
|
|
419
488
|
it (`deleteProjectV2Item`, their issues untouched) once its project
|
|
420
489
|
holds them. `pulse sync` syncs the collections' projects, then Agent
|
|
421
490
|
Pulse. The project is written from the collection's files, one way:
|
|
422
|
-
- `<collection>/README.md` is its README
|
|
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
|
|
423
495
|
`description:` (the short description), `statuses:` (the columns, in
|
|
424
496
|
order, each there even when empty), `fields:` (each a name, a `type` —
|
|
425
497
|
`text`, `date`, `select`, `number` — and `from`, the front-matter key
|
|
@@ -438,9 +510,25 @@ added to project`) may change a field of rness's items; the next sync
|
|
|
438
510
|
none of this reaches Agent Pulse: a workspace that declares no collection
|
|
439
511
|
keeps its board as 0.16 made it.
|
|
440
512
|
|
|
441
|
-
- **Hooks**, with `claude` in `agents
|
|
442
|
-
|
|
443
|
-
|
|
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
|
|
444
532
|
through a sync, and so does one whose status the edit changed, so its
|
|
445
533
|
card moves within seconds rather than at the session's end; an edit that
|
|
446
534
|
leaves the status alone, or of any other file of `.rness/`, costs no sync;
|
|
@@ -457,6 +545,40 @@ added to project`) may change a field of rness's items; the next sync
|
|
|
457
545
|
`the pulse needs the project scope: run rness login` or
|
|
458
546
|
`cannot reach GitHub: …`. Two sessions marking one plan both write; the
|
|
459
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.
|
|
460
582
|
- **One issue per document**: before making an issue, `pulse sync` reads
|
|
461
583
|
the open issues of `.rness` labelled `rness` (one request per 100, and
|
|
462
584
|
only when it has one to make). One whose body starts with the document's
|
|
@@ -500,7 +622,9 @@ repository, the board).
|
|
|
500
622
|
detected provider this version cannot talk to. The order of keys is
|
|
501
623
|
`contract`, `provider`, `org`, `agents`, `projects`, `repos`, `scopes`.
|
|
502
624
|
The former `"pulse": { "project": <number> }` still reads, as
|
|
503
|
-
`"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.
|
|
504
628
|
- A provider _written_ in `rness.json` that this version cannot talk to
|
|
505
629
|
(`gitlab`) is refused by `validate`, `create`, `login` and `pulse`:
|
|
506
630
|
`provider "gitlab" is not supported by @rness/cli 0.17.1 (supported:
|
|
@@ -511,22 +635,26 @@ github)`. `add`, `sync` and the local commands never refuse. `null` is not a
|
|
|
511
635
|
|
|
512
636
|
rness mcp # started by the agent over stdio, not by hand
|
|
513
637
|
|
|
514
|
-
A local
|
|
515
|
-
|
|
516
|
-
|
|
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):
|
|
517
642
|
|
|
518
|
-
| Tool
|
|
519
|
-
|
|
|
520
|
-
| `rness_context({ scope? })`
|
|
521
|
-
| `rness_list({ collection, status? })`
|
|
522
|
-
| `rness_read({ path })`
|
|
523
|
-
| `rness_search({ query, collection? })`
|
|
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") |
|
|
524
650
|
|
|
525
651
|
- MCP over stdio, one message per line: the `2026-07-28` revision and the
|
|
526
652
|
earlier ones that open with `initialize` (`2025-11-25` back to
|
|
527
653
|
`2024-11-05`). No MCP SDK, no added dependency.
|
|
528
654
|
- `.rness/` is read again on each call, so an edit shows at once. Nothing is
|
|
529
|
-
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.
|
|
530
658
|
- With `claude` in `agents`, `sync` registers it in each clone:
|
|
531
659
|
`.mcp.json` gets `mcpServers.rness`, which runs the pinned copy
|
|
532
660
|
(`node ../../.rness/node_modules/@rness/cli/dist/bin/rness.js mcp`). A
|
|
@@ -695,6 +823,44 @@ per repository, the files to commit there.
|
|
|
695
823
|
Exit codes: 0 success, 1 failure, 2 usage — or a refusal without a TTY.
|
|
696
824
|
`RNESS_DEBUG=1` adds stack traces; `RNESS_NO_DELEGATE=1` skips the delegation.
|
|
697
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
|
+
|
|
698
864
|
## 0.20.1 — the workspace's line in the prompt footer, without a warning sign; the mod never installs the pin
|
|
699
865
|
|
|
700
866
|
- 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-
|
|
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";
|
package/dist/bin/rness.js.map
CHANGED
|
@@ -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":"
|
|
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":[]}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JournalRefused,
|
|
3
|
+
blobUrl,
|
|
4
|
+
branchOf,
|
|
5
|
+
cloneHolding,
|
|
6
|
+
context,
|
|
7
|
+
declaredBoards,
|
|
8
|
+
fromGithub,
|
|
9
|
+
headOf,
|
|
10
|
+
issueUrl,
|
|
11
|
+
notePosted,
|
|
12
|
+
notesPosted,
|
|
13
|
+
parseFrontMatter,
|
|
14
|
+
plansInProgress,
|
|
15
|
+
recordNotice,
|
|
16
|
+
resolveScope,
|
|
17
|
+
statusTabs
|
|
18
|
+
} from "./chunk-D3D2WCGW.js";
|
|
19
|
+
|
|
20
|
+
// src/pulse/note.ts
|
|
21
|
+
import { relative, resolve, sep } from "path";
|
|
22
|
+
var KINDS = ["approach", "deviation", "blocker", "done"];
|
|
23
|
+
var TITLE = {
|
|
24
|
+
approach: "Approach",
|
|
25
|
+
deviation: "Deviation",
|
|
26
|
+
blocker: "Blocker",
|
|
27
|
+
done: "Done"
|
|
28
|
+
};
|
|
29
|
+
var MAX = 4e3;
|
|
30
|
+
var SECRETS = [
|
|
31
|
+
/ghp_/,
|
|
32
|
+
/gho_/,
|
|
33
|
+
/github_pat_/,
|
|
34
|
+
/AKIA[0-9A-Z]{16}/,
|
|
35
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY/,
|
|
36
|
+
/xox[abpr]-/,
|
|
37
|
+
/sk-[A-Za-z0-9]{20,}/
|
|
38
|
+
];
|
|
39
|
+
var NoteRefused = class extends Error {
|
|
40
|
+
};
|
|
41
|
+
function sessionFrom(given, env) {
|
|
42
|
+
const raw = given ?? env["CLAUDE_CODE_SESSION_ID"] ?? "";
|
|
43
|
+
if (raw.trim() === "") return null;
|
|
44
|
+
return raw.includes(" \xB7 ") ? raw : `claude \xB7 ${raw.slice(0, 8)}`;
|
|
45
|
+
}
|
|
46
|
+
function checked(input) {
|
|
47
|
+
const length = input.text.trim().length;
|
|
48
|
+
if (length < 1 || length > MAX)
|
|
49
|
+
throw new NoteRefused(
|
|
50
|
+
`a note holds 1 to ${MAX.toLocaleString("en")} characters (this one ${length})`
|
|
51
|
+
);
|
|
52
|
+
if (SECRETS.some((s) => s.test(input.text)))
|
|
53
|
+
throw new NoteRefused("the note looks like it holds a credential");
|
|
54
|
+
const kind = KINDS.find((k) => k === (input.kind ?? "approach"));
|
|
55
|
+
if (kind === void 0)
|
|
56
|
+
throw new NoteRefused(
|
|
57
|
+
`--kind is one of ${KINDS.slice(0, -1).join(", ")} or ${KINDS.at(-1)}`
|
|
58
|
+
);
|
|
59
|
+
const session = sessionFrom(input.session, input.env ?? process.env);
|
|
60
|
+
if (session === null)
|
|
61
|
+
throw new NoteRefused(
|
|
62
|
+
"no session: write it from a Claude Code session, or pass --session"
|
|
63
|
+
);
|
|
64
|
+
return { kind, session };
|
|
65
|
+
}
|
|
66
|
+
function journalOf(declared) {
|
|
67
|
+
for (const d of declared)
|
|
68
|
+
for (const a of d.declaration.hooks["session-start"] ?? [])
|
|
69
|
+
if (a.action === "journal") return { board: d, journal: a };
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function githubRepo(url) {
|
|
73
|
+
const m = /^(?:git@github\.com:|ssh:\/\/git@github\.com\/|https:\/\/github\.com\/)([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(
|
|
74
|
+
url.trim()
|
|
75
|
+
);
|
|
76
|
+
return m === null || m[1] === void 0 || m[2] === void 0 ? null : { owner: m[1], name: m[2] };
|
|
77
|
+
}
|
|
78
|
+
async function planOf(c, cwd, given) {
|
|
79
|
+
const tabs = await statusTabs(c.rnessDir);
|
|
80
|
+
const plans = tabs.find((t) => t.name === "plans")?.rows ?? [];
|
|
81
|
+
if (given !== void 0) {
|
|
82
|
+
const path = given.replace(/^\.rness\//, "");
|
|
83
|
+
if (!plans.some((r) => r.path === path))
|
|
84
|
+
throw new NoteRefused(`${given} is no plan of .rness`);
|
|
85
|
+
return path;
|
|
86
|
+
}
|
|
87
|
+
const scope = resolveScope(
|
|
88
|
+
c.manifest,
|
|
89
|
+
relative(c.root, resolve(cwd)).split(sep).join("/")
|
|
90
|
+
);
|
|
91
|
+
const candidates = await plansInProgress(
|
|
92
|
+
{ root: c.root, rnessDir: c.rnessDir },
|
|
93
|
+
c.manifest,
|
|
94
|
+
scope
|
|
95
|
+
);
|
|
96
|
+
if (candidates.length === 0)
|
|
97
|
+
throw new NoteRefused(
|
|
98
|
+
`no plan in progress in scope ${scope ?? "global"}: set one In progress first`
|
|
99
|
+
);
|
|
100
|
+
if (candidates.length > 1)
|
|
101
|
+
throw new NoteRefused(
|
|
102
|
+
`several plans in progress: pass --plan, one of ${candidates.join(", ")}`
|
|
103
|
+
);
|
|
104
|
+
return candidates[0] ?? "";
|
|
105
|
+
}
|
|
106
|
+
async function planIssue(c, board, plan) {
|
|
107
|
+
const items = await fromGithub(
|
|
108
|
+
c.provider.items(board.board, { bodies: false })
|
|
109
|
+
);
|
|
110
|
+
const number = items.find((i) => i.path === plan)?.issue?.number;
|
|
111
|
+
const found = number === void 0 ? null : await fromGithub(c.provider.issueOf(c.org, ".rness", number));
|
|
112
|
+
if (found === null)
|
|
113
|
+
throw new NoteRefused(`${plan} has no issue yet: rness pulse sync`);
|
|
114
|
+
return {
|
|
115
|
+
id: found.id,
|
|
116
|
+
number: found.number,
|
|
117
|
+
repository: `${c.org}/.rness`
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
var refusedWrite = (e) => e instanceof Error && /not accessible|forbidden|permission|must have push access/i.test(e.message);
|
|
121
|
+
async function implementationBody(c, plan, planNumber) {
|
|
122
|
+
const lines = [
|
|
123
|
+
`Plan [\`${plan}\`](${blobUrl(c.org, plan)}) \xB7 [plan issue](${issueUrl(c.org, planNumber)})`
|
|
124
|
+
];
|
|
125
|
+
try {
|
|
126
|
+
const { readFile } = await import("fs/promises");
|
|
127
|
+
const front = parseFrontMatter(
|
|
128
|
+
await readFile(resolve(c.rnessDir, ...plan.split("/")), "utf8")
|
|
129
|
+
) ?? {};
|
|
130
|
+
const spec = /\(\.\.\/(specs\/[^)\s]+\.md)\)/.exec(
|
|
131
|
+
String(front["spec"] ?? "")
|
|
132
|
+
);
|
|
133
|
+
if (spec?.[1] !== void 0)
|
|
134
|
+
lines.push(`Specification [\`${spec[1]}\`](${blobUrl(c.org, spec[1])})`);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
return `${lines.join("\n")}
|
|
138
|
+
|
|
139
|
+
The agent's notes on this implementation follow as comments. The pull request that completes it carries \`Closes\` with this issue's reference.`;
|
|
140
|
+
}
|
|
141
|
+
async function postNote(input) {
|
|
142
|
+
const { kind, session } = checked(input);
|
|
143
|
+
const c = await context({
|
|
144
|
+
cwd: input.cwd,
|
|
145
|
+
...input.githubApi === void 0 ? {} : { githubApi: input.githubApi }
|
|
146
|
+
});
|
|
147
|
+
const declared = await declaredBoards(c);
|
|
148
|
+
const kept = journalOf(declared);
|
|
149
|
+
if (kept === null)
|
|
150
|
+
throw new NoteRefused(
|
|
151
|
+
'no board keeps a journal: add { "action": "journal", "to": "plan" } to its session-start hooks'
|
|
152
|
+
);
|
|
153
|
+
const plan = await planOf(c, input.cwd, input.plan);
|
|
154
|
+
if (await notesPosted(c.rnessDir, session, plan) >= kept.journal.limit)
|
|
155
|
+
throw new NoteRefused(
|
|
156
|
+
"journal full for this session: put the rest in the pull request"
|
|
157
|
+
);
|
|
158
|
+
const parent = await planIssue(c, kept.board, plan);
|
|
159
|
+
const clone = cloneHolding(c.root, c.manifest.repos, input.cwd);
|
|
160
|
+
let target = parent;
|
|
161
|
+
let why = null;
|
|
162
|
+
if (kept.journal.to === "repo" && clone !== null) {
|
|
163
|
+
const repo = githubRepo(clone.url);
|
|
164
|
+
if (repo === null || repo.owner.toLowerCase() !== c.org.toLowerCase())
|
|
165
|
+
why = `${clone.name} is not on the workspace's provider`;
|
|
166
|
+
else
|
|
167
|
+
try {
|
|
168
|
+
const there = await fromGithub(
|
|
169
|
+
c.provider.journalIssue(repo.owner, repo.name, plan)
|
|
170
|
+
);
|
|
171
|
+
if (there !== null) target = there;
|
|
172
|
+
else {
|
|
173
|
+
const row = (await statusTabs(c.rnessDir)).find((t) => t.name === "plans")?.rows.find((r) => r.path === plan);
|
|
174
|
+
target = await fromGithub(
|
|
175
|
+
c.provider.createJournalIssue(repo.owner, repo.name, plan, {
|
|
176
|
+
title: `Implement ${row === void 0 ? plan : `${row.id} \u2014 ${row.title}`}`,
|
|
177
|
+
body: await implementationBody(c, plan, parent.number)
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
try {
|
|
181
|
+
await fromGithub(c.provider.addSubIssue(parent.id, target.id));
|
|
182
|
+
} catch {
|
|
183
|
+
await fromGithub(
|
|
184
|
+
c.provider.comment(
|
|
185
|
+
parent.id,
|
|
186
|
+
`Implementation: ${target.repository}#${target.number}`
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (e) {
|
|
192
|
+
if (e instanceof JournalRefused)
|
|
193
|
+
why = e.why === "issues" ? `${repo.owner}/${repo.name} takes no issues` : `${repo.owner}/${repo.name} is not on GitHub, or this login cannot see it`;
|
|
194
|
+
else if (refusedWrite(e))
|
|
195
|
+
why = `${repo.owner}/${repo.name} refuses this login's write`;
|
|
196
|
+
else throw e;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (why !== null)
|
|
200
|
+
await recordNotice(`journal \u2014 ${why}, notes go to the plan's issue`);
|
|
201
|
+
const where = clone?.dir ?? input.cwd;
|
|
202
|
+
const [branch, head] = await Promise.all([branchOf(where), headOf(where)]);
|
|
203
|
+
const id = session.split(" \xB7 ").pop() ?? session;
|
|
204
|
+
const header = [
|
|
205
|
+
`**${TITLE[kind]}**`,
|
|
206
|
+
session,
|
|
207
|
+
...branch === null || head === null ? [] : [`\`${branch}\` @ \`${head.slice(0, 7)}\``]
|
|
208
|
+
].join(" \xB7 ");
|
|
209
|
+
const body = [
|
|
210
|
+
...why === null ? [] : [`_${why}: this note goes to the plan's issue._`, ""],
|
|
211
|
+
header,
|
|
212
|
+
"",
|
|
213
|
+
input.text.trim(),
|
|
214
|
+
"",
|
|
215
|
+
`<!-- rness note ${id} -->`
|
|
216
|
+
].join("\n");
|
|
217
|
+
await fromGithub(c.provider.comment(target.id, body));
|
|
218
|
+
const reference = `${target.repository}#${target.number}`;
|
|
219
|
+
await notePosted(c.rnessDir, session, plan, { id: target.id, reference });
|
|
220
|
+
return {
|
|
221
|
+
reference,
|
|
222
|
+
to: target === parent ? "plan" : "repo"
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
async function pulseNoteCommand(opts) {
|
|
226
|
+
try {
|
|
227
|
+
const text = opts.text === void 0 || opts.text === "-" ? await (opts.stdin ?? readStdin)() : opts.text;
|
|
228
|
+
const posted = await postNote({
|
|
229
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
230
|
+
text,
|
|
231
|
+
...opts.kind === void 0 ? {} : { kind: opts.kind },
|
|
232
|
+
...opts.plan === void 0 ? {} : { plan: opts.plan },
|
|
233
|
+
...opts.session === void 0 ? {} : { session: opts.session },
|
|
234
|
+
...opts.githubApi === void 0 ? {} : { githubApi: opts.githubApi }
|
|
235
|
+
});
|
|
236
|
+
process.stdout.write(`${posted.reference}
|
|
237
|
+
`);
|
|
238
|
+
return 0;
|
|
239
|
+
} catch (e) {
|
|
240
|
+
process.stderr.write(
|
|
241
|
+
`rness: ${e instanceof Error ? e.message : String(e)}
|
|
242
|
+
`
|
|
243
|
+
);
|
|
244
|
+
return 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function readStdin() {
|
|
248
|
+
const chunks = [];
|
|
249
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
250
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export {
|
|
254
|
+
KINDS,
|
|
255
|
+
NoteRefused,
|
|
256
|
+
sessionFrom,
|
|
257
|
+
journalOf,
|
|
258
|
+
githubRepo,
|
|
259
|
+
planIssue,
|
|
260
|
+
postNote,
|
|
261
|
+
pulseNoteCommand
|
|
262
|
+
};
|
|
263
|
+
//# sourceMappingURL=chunk-4BM4FUWO.js.map
|