@open-tomato/rafa 0.28.0 → 0.32.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
@@ -153,9 +153,9 @@ the next one, so you rarely have to remember it.
153
153
  stored, one commit. A task that reports `blocked` is marked and the
154
154
  run stops with the reason; fix what it names and start again, and the
155
155
  blocked task goes first. After the last task a wrap-up session syncs
156
- with the base, bumps the version and the changelog when the project
157
- has them, pushes, opens the pull request and waits for CI, spending
158
- repair sessions on a red one.
156
+ with the base, commits a release fragment when the project has them
157
+ (the plan's level and notes), pushes, opens the pull request and waits
158
+ for CI, spending repair sessions on a red one.
159
159
 
160
160
  From another terminal: `rafa loop status`, `rafa loop pause` (after the
161
161
  running task), `rafa loop stop` (now).
@@ -163,14 +163,64 @@ the next one, so you rarely have to remember it.
163
163
  5. **Land it and look at what it cost.**
164
164
 
165
165
  ```bash
166
- rafa pr current # number, title, checks, URL
167
- rafa pr triage # why is it red, and is the fix simple (🪙 only with --resolve)
168
- rafa pr merge [--skip-checks] # asks y/N, merges, switches to the base, pulls, deletes both branches
169
- rafa release tag # tag the merged version
166
+ rafa pr current # number, title, checks, URL
167
+ rafa pr triage # why is it red, and is the fix simple (🪙 only with --resolve)
168
+ rafa pr merge [--skip-checks] # asks y/N, merges, switches to the base, pulls, deletes branches
169
+ rafa release settle # folds fragments to a version and pushes
170
+ rafa release tag # tag the released version (optional, depends on config)
170
171
  rafa effort collect && rafa effort report
171
172
  ```
172
173
 
173
- Then step 2 again, or `rafa plan create --next`.
174
+ Then step 2 again, or `rafa plan create --next`. When `rafa next` is used,
175
+ it runs settle and optionally tag as part of the workflow.
176
+
177
+ ### Release fragments and settling
178
+
179
+ A branch no longer owns a version number. Instead, the wrap-up commits a
180
+ release fragment (a small markdown file naming the plan, level and notes),
181
+ and `rafa release settle` assigns the version on the base branch after
182
+ merges. This lets several branches merge in any order without a version
183
+ collision: whichever machine settles first releases all waiting fragments
184
+ under one version.
185
+
186
+ The pull request shows a forecast of what version the branch would get if
187
+ merged now. The fragment is stored under `release.fragments` (default
188
+ `.changes/`), one file per plan. `rafa doctor` warns when fragments are
189
+ waiting and suggests running settle. `rafa pr list` marks forecasts as
190
+ `(base moved)` if the base branch changed since the forecast was written.
191
+
192
+ Every project starts with fragments settling to the base branch and tagging
193
+ by hand. You can configure settle to open a pending release pull request
194
+ instead (for protected base branches), and have settle tag the commit
195
+ automatically (for CI ownership of releases). Four keys, all optional:
196
+
197
+ ```yaml
198
+ # Empty config: fragments in .changes/, semver-by-level, settle pushes, tags by hand
199
+ # (This is the default for every new project.)
200
+
201
+ release:
202
+ tag: settle # settle tags the commit it pushed
203
+
204
+ release:
205
+ settle: pr # protected base branch: one pending release PR
206
+ pr:
207
+ versionCollision: refuse
208
+
209
+ release:
210
+ settle: pr
211
+ tag: settle
212
+ pr:
213
+ versionCollision: refuse
214
+ ```
215
+
216
+ The keys are: `release.fragments` (path, default `.changes/`), `release.strategy`
217
+ (fold strategy, default `semver-by-level`), `release.settle` (`push` or `pr`,
218
+ default `push`), `release.tag` (`manual` or `settle`, default `manual`),
219
+ `release.publishCommand` (the publish line `rafa release tag` prints and never
220
+ runs, default `npm publish`). `pr.versionCollision` controls what happens when a merge would collide
221
+ (default `report`). Read `context/release.md` for the full design,
222
+ `docs/ci-release-settle.md` for automating settle in CI, and
223
+ `rafa release settle --help` for the settle command.
174
224
 
175
225
  ### Tracking effort and learning from skills
176
226
 
@@ -180,7 +230,7 @@ were offered, invoked and worked. Key commands for the effort record:
180
230
  ```bash
181
231
  rafa effort collect # gather and store session logs
182
232
  rafa effort report # per-plan summary: sessions, tasks, costs
183
- rafa effort merge <file> # join another device's store into this one
233
+ rafa effort import <file> # merge another device's store into this one
184
234
  ```
185
235
 
186
236
  `rafa effort report` shows the per-plan tables (sessions by status and
@@ -211,10 +261,75 @@ The report opens with a fixed line explaining that it shows co-occurrence,
211
261
  not causation: a skill can be invoked and its failure recur for reasons the
212
262
  skill does not cover.
213
263
 
264
+ To see whether task sessions are getting more expensive over time, and
265
+ whether that follows the plan or the calendar:
266
+
267
+ ```bash
268
+ rafa effort report --trend # last 3 days against the 14 before, then the loops
269
+ rafa effort report --trend --loops=10 --by=effort
270
+ ```
271
+
272
+ The trend report has two parts. **Trend** compares the recent task
273
+ sessions with a baseline: median and p90 of minutes, output tokens,
274
+ cache-read tokens and turns per task, one row per day, and the sessions
275
+ over the outlier line (baseline median plus three robust standard
276
+ deviations). **Loops** lists one row per plan's loop, newest first: its
277
+ wall-clock and summed task time, number of tasks, minutes per task (min,
278
+ max, average, median), and average tokens and turns per task. Under the
279
+ rows, a drift reading tests each per-task figure across the loops in the
280
+ order they ran (the Mann-Kendall trend test). `no steady trend` means the
281
+ cost moves with the plan; `rising` means it grows whatever the plan.
282
+
283
+ To see everything at once, the loops running now included:
284
+
285
+ ```bash
286
+ rafa effort collect && rafa effort dashboard
287
+ rafa effort dashboard --output=json # one key per widget, for a dashboard
288
+ ```
289
+
290
+ The dashboard prints the running loops with their tasks done over total
291
+ and three estimates of the time left: **by task** (the plan's average task
292
+ session), **by progress** (the time since the plan first started, per task
293
+ done), and **this session** (what `rafa loop status` prints). By progress
294
+ counts the time between tasks too, so it is the one to compare against
295
+ the clock. Then come the trend, the loops, each plan's skills M1 and M2,
296
+ and the totals of every stored session.
297
+
298
+ ### Choosing how the effort store travels
299
+
300
+ An effort store can travel between a project's devices in different ways, each
301
+ suited to different setups. Set `effort.sync` in `.rafa/config.yaml`:
302
+
303
+ | Setup | Strategy | Why |
304
+ |---|---|---|
305
+ | One laptop | `local` | Nothing to sync |
306
+ | Two personal devices, occasional | `file` | No server, no account; copy a file |
307
+ | Personal projects, frequent | `git` | History and access from git, works offline |
308
+ | A company or several teams | `service` | One hub, live status, shared rules |
309
+ | Advanced, exploratory | `p2p` | Devices talk directly (spike) |
310
+
311
+ Configuration examples:
312
+
313
+ ```yaml
314
+ # Default: no sync, each device keeps its own store
315
+ # (nothing set, or effort: {sync: local})
316
+
317
+ # File exchange with no server
318
+ effort:
319
+ sync: file
320
+
321
+ # Hub-based sync (requires module and service URL)
322
+ effort:
323
+ sync: service
324
+ syncUrl: https://hub.example.com
325
+ ```
326
+
327
+ Only `local` and `file` ship with rafa; `git`, `service` and `p2p` need a module.
328
+
214
329
  ### Starting a second device
215
330
 
216
- To run rafa on a second device for the same project, bring the effort
217
- store over consistently:
331
+ To run rafa on a second device for the same project, bring the effort store over
332
+ consistently using `rafa effort copy` and `rafa effort import`:
218
333
 
219
334
  1. On the new device, check for an existing `.rafa/effort/` store and set
220
335
  it aside (rename or back up).
@@ -225,10 +340,11 @@ store over consistently:
225
340
  Or use `sqlite3 .rafa/effort/effort.sqlite ".backup <path>"`, but never
226
341
  a plain file copy during a write.
227
342
  3. Transfer the copied store and `.rafa/config.yaml` to the new device's
228
- `.rafa/` directory.
343
+ `.rafa/` directory. **The exported file holds private text — task
344
+ descriptions, findings, and notes — meant only for your own devices.**
229
345
  4. After both devices have recorded effort, bring them back together with:
230
346
  ```bash
231
- rafa effort merge <copied-store-file>
347
+ rafa effort import <copied-store-file>
232
348
  ```
233
349
 
234
350
  ### Which agents and skills are involved
@@ -303,11 +419,13 @@ with its section line, to change it.
303
419
  ### `effort`
304
420
 
305
421
  One setting says how long a command waits for another rafa process that
306
- is writing the effort store before it gives up with `SQLITE_BUSY`.
422
+ is writing the effort store before it gives up with `SQLITE_BUSY`, and
423
+ another names how the store travels between a project's devices.
307
424
 
308
425
  | Key | Default | What it sets |
309
426
  |---|---|---|
310
427
  | `effort.busyTimeoutMs` | `5000` | the wait in milliseconds, a whole number from 1 to 60000 |
428
+ | `effort.sync` | `local` | how the store travels: `local`, `file`, `git`, `service` or `p2p` |
311
429
 
312
430
  There is no unlimited wait, since a lock wait with no end can hang a
313
431
  loop; `0`, a fraction and a quoted `"5000"` are refused.
@@ -352,6 +470,87 @@ status:
352
470
  notice: false
353
471
  ```
354
472
 
473
+ ### `claims`
474
+
475
+ Two settings control how claims work when two devices work on one project.
476
+ Devices claim issues to prevent collisions: no two can plan or start the same
477
+ issue, and stale or lost claims are recovered without losing work.
478
+
479
+ | Key | Default | What it sets |
480
+ |---|---|---|
481
+ | `claims.staleAfter` | `3d` | how long a `rafa:claimed` claim stands before takeover is allowed: a duration such as `3d`, `1w`, or `disabled` |
482
+ | `claims.ahead` | `off` | whether to reserve one issue ahead on the roadmap: `off` or `allow` |
483
+
484
+ `claims.staleAfter` takes a duration or the word `disabled`; `0`, a fraction,
485
+ and a quoted value are refused. When `disabled`, nothing is ever stale and
486
+ claims must be released by hand. `claims.ahead` enables claim-ahead as an
487
+ opt-in per run with `--claim-ahead` on `rafa next --roadmap` and `plan create
488
+ --next`.
489
+
490
+ ```yaml
491
+ # Default: claims are made, staleAfter is 3d, no claim ahead
492
+ # (nothing set, or claims: {})
493
+
494
+ # Set staleness only
495
+ claims:
496
+ staleAfter: 3d
497
+
498
+ # Disable staleness and enable claim ahead
499
+ claims:
500
+ staleAfter: disabled
501
+ ahead: allow
502
+ ```
503
+
504
+ ### `loop` and the guard
505
+
506
+ A loop guards itself: every turn, it watches the checkout's branch and HEAD
507
+ and halts if either changes externally (a branch switch in another terminal,
508
+ a pull that moved the base). The work stays committed and nothing is lost;
509
+ without the guard the edits would move with the checkout. The guard runs on
510
+ every loop and never interferes with the loop's own commits.
511
+
512
+ **Worktree use cases.** `loop start --as-worktree` runs the loop in a new
513
+ git worktree alongside your main checkout, so you stay on `main` in your
514
+ terminal and can keep working — reviewing, pulling or merging other PRs —
515
+ while the loop runs beside you. This is a git worktree, the second desk on
516
+ the same repository, with its own branch and its own working directory. Both
517
+ share one `.rafa/` and one effort store. The worktree is created under
518
+ `.rafa/worktrees/` by default, or in the directory `loop.worktreeDir` names
519
+ when configured. A loop without `--as-worktree` runs in the current checkout,
520
+ which is unchanged. The guard stops a loop if you switch away from it; with
521
+ a worktree, you are free to switch the main checkout to anything, and the
522
+ loop stays on its branch. Two loops run in two worktrees, each with its own
523
+ branch, both under the same `.rafa/`, allowing parallel work on two epics at
524
+ once. Merging a PR while a loop runs no longer offers `rafa self-update` right
525
+ away; `pr merge` names the running loop and asks you to update after it stops,
526
+ since the binary is what the loop spawns its sessions from, and swapping it
527
+ mid-run changes the engine while driving.
528
+
529
+ **Edge case: the 2026-09-29 incident.** A loop switched the main checkout to
530
+ its branch; 48 seconds later, `git checkout main` and `git pull` in another
531
+ terminal moved it back, and the edits followed. Both branches were at the
532
+ same commit, so `git switch <loop-branch>` recovered it. With the guard the
533
+ loop halts instead, keeping the work and the branch where it is while you
534
+ sort out what happened.
535
+
536
+ | Key | Default | What it sets |
537
+ |---|---|---|
538
+ | `loop.worktreeDir` | `.rafa/worktrees` | the directory where `--as-worktree` creates worktrees, absolute or relative to the project root |
539
+ | `dangerous.selfUpdateDuringLoop` | `false` | whether `rafa self-update` runs while a loop of this project is live |
540
+
541
+ Both take a string and a boolean as written; quoted values are refused.
542
+
543
+ ```yaml
544
+ # empty: loops run in the current checkout, guarded
545
+ # --as-worktree puts them in .rafa/worktrees (the default)
546
+
547
+ loop:
548
+ worktreeDir: ../rafa-loops # worktrees beside the repository
549
+
550
+ dangerous:
551
+ selfUpdateDuringLoop: true # update the binary even while a loop runs
552
+ ```
553
+
355
554
  ## Specs, issues and the roadmap
356
555
 
357
556
  You can plan from a local file and never touch a board. When you want
@@ -410,6 +609,18 @@ from GitHub Issues:
410
609
  `waiting on #C (owner review)` until the team reviews and approves. The
411
610
  hop record (`.rafa/hop.json`) tracks where home is and what rafa works.
412
611
  When `rafa next` finishes, it deletes the record and returns home.
612
+ - **Board relationships: a choice of modes.** A board tracks relationships
613
+ — which issues block which — in one of two ways. `labels` (the default)
614
+ uses `spec:blocked` labels and `Blocked by:` lines, carrying no cost
615
+ when a board is read. `native` uses GitHub's built-in parent and
616
+ blocked-by links, showing them in GitHub's UI and costing about 8 API
617
+ points per read of 5,000 per hour. Solo projects and those new to rafa
618
+ start with `labels`. Chose `native` when the relationships should show
619
+ in GitHub, or when nothing should clear a blocker by hand. Config it as
620
+ `board: relationships: native`, then run `rafa init --board` to move
621
+ from one mode to the other and remove the old marks. Read
622
+ [docs/specs-and-roadmap.md](docs/specs-and-roadmap.md) under "Board
623
+ relationships" for the full choice and upgrade path.
413
624
  - **Other trackers.** GitHub Issues is what works today. Linear support
414
625
  is being ported from the project rafa grew out of, as an optional
415
626
  add-on in a later version. For anything else, open or upvote a request
@@ -459,10 +670,13 @@ of `~/.bun/bin`; both warn when it is not, and `rafa doctor` checks it.
459
670
  Both exit 1 before building anything while a plan tracker in `plan.dir`
460
671
  (`.rafa/plans` unless `.rafa/config.yaml` names another) still holds an
461
672
  open or blocked task, and name every such tracker. `rafa self-update`
462
- runs only inside a project, so `rafa init` the checkout first. They exit
673
+ runs only inside a project, so `rafa init` the checkout first, and it
674
+ also exits 1 while a loop of the project is live, naming each loop's
675
+ branch and pid, unless `dangerous.selfUpdateDuringLoop` is true in the
676
+ config; `--force` does not override that wait. They exit
463
677
  2 when they could not run: a `package.json` that is not rafa's or cannot
464
- be read, a config or tracker they could not read, or a build, copy or
465
- link that failed.
678
+ be read, a config or tracker they could not read, a loop record
679
+ `rafa self-update` could not read, or a build, copy or link that failed.
466
680
 
467
681
  A version is installed once. Both also exit 1, before building, when
468
682
  `~/.rafa/runtime/<version>/` is already there, naming that directory and
@@ -519,7 +733,9 @@ and a line here is ticked by the change that finishes the feature.
519
733
  - ✅ Plan straight from the issue board, or from whatever is next on the roadmap
520
734
  - ✅ Review, merge and clean up pull requests from the command line
521
735
  - ✅ A failing pull request is diagnosed, and fixed when the fix is simple
522
- - ✅ A version bump and a changelog entry with every pull request
736
+ - ✅ Release fragments on branches, settled to a version on the base
737
+ branch after merges, with forecasts in the PR and protection for
738
+ multiple concurrent branches
523
739
  - ✅ Start a plan from the main branch and rafa makes the branch for you
524
740
  - ✅ One command takes you to the next step: merge, clean up, plan,
525
741
  branch, start
@@ -155,7 +155,7 @@ Recognized fields:
155
155
  - `stub` — The plan identifier (string, following the `rafa-<n>-<slug>` format). Used to organize findings and dedupe rows across runs.
156
156
  - `issue` — Optional GitHub issue number (integer, e.g., `123`). Links the work back to the `open-tomato/rafa` board.
157
157
  - `spec` — Optional path to the specification document that guided the plan.
158
- - `release` — Optional release level for the version bump this plan's pull request ships: `patch`, `minor`, `major` or `none`. Anything else is reported as `unusable-field` and read as if unset. Left out, the level is the highest among the change notes the plan's tasks stored, and `none` when they stored none.
158
+ - `release` — Optional release level for the version bump this plan's pull request ships: `patch`, `minor`, `major` or `none`. Anything else is reported as `unusable-field` and read as if unset. When absent, the level is the highest severity among the plan's collected change notes. The wrap-up writes this level to a release fragment file (under `release.fragments`, default `.changes/`) that `rafa release settle` will process to compute the version bump after the branch merges.
159
159
 
160
160
  Unknown keys are retained and ignored by the loop; they do not cause parsing to fail.
161
161
 
@@ -270,7 +270,7 @@ Report fields:
270
270
  | `skills_used` | list of strings | Names of skills referenced or applied |
271
271
  | `blockers` | list of objects | What blocked the task. Any entry marks the task `[BLOCKED]` whatever `status` says, so write `[]` when nothing did |
272
272
  | `out_of_scope_bugs` | list of objects | Bugs found that are outside this task's scope |
273
- | `changes` | list of objects | The changelog for this task's own diff, one entry per user-visible change (see below). An absent list is empty with no issue; a task whose diff a user would notice nothing of writes one entry at level `none` |
273
+ | `changes` | list of objects | Release notes for this task's own diff (contributing to the release fragment), one entry per user-visible change (see below). An absent list is empty with no issue; a task whose diff a user would notice nothing of writes one entry at level `none`. The wrap-up collects these from all tasks and writes them to the fragment that `rafa release settle` processes after the branch merges. |
274
274
 
275
275
  Finding entry fields:
276
276
 
@@ -414,13 +414,13 @@ The second example above has no `skills=` and is still a valid task declaration.
414
414
  invariant sweeps), live-seam runs, migrations, and close-out — these
415
415
  preserve resumability where a halt is most likely.
416
416
  * The RUNNER owns the push, the pull request, the merge with the base,
417
- the wait for CI, and the version bump and changelog entry. After the last
417
+ the wait for CI, and the release fragment creation. After the last
418
418
  task it runs a wrap-up session that promotes findings, compacts
419
419
  `progress.txt`, merges `origin/main`, commits, pushes and opens (or
420
420
  updates) the PR — and then polls that PR's checks, spending repair sessions
421
421
  on a red or conflicting result. So a plan must NOT carry a task that opens
422
422
  a PR, resolves a merge conflict, waits on CI, compacts `progress.txt`,
423
- or writes a version bump or changelog entry. Two openers race: measured,
423
+ or writes a release fragment. Two openers race: measured,
424
424
  one run cut a second branch and opened a second PR for a single plan. A
425
425
  close-out task SHOULD still take the mergeability reading (`git merge-tree
426
426
  --write-tree origin/main HEAD`) and assemble the body material — the gate