@open-tomato/rafa 0.45.0 → 0.46.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.
Files changed (140) hide show
  1. package/README.md +255 -23
  2. package/dist/PROMPT.md +18 -1
  3. package/dist/adapters/logger/settings.d.ts +132 -0
  4. package/dist/bugs/closest.d.ts +19 -0
  5. package/dist/bugs/fenced.d.ts +8 -0
  6. package/dist/bugs/places.d.ts +20 -0
  7. package/dist/bugs/record.d.ts +145 -0
  8. package/dist/bugs/title.d.ts +83 -0
  9. package/dist/bundled/agents/doc-updater.md +17 -3
  10. package/dist/bundled/agents/loop-implementer.md +14 -2
  11. package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
  12. package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
  13. package/dist/bundled/skills/dev-planner/SKILL.md +101 -18
  14. package/dist/cli.js +74726 -62960
  15. package/dist/config/entry.d.ts +119 -0
  16. package/dist/config/index.d.ts +36 -0
  17. package/dist/config/index.js +51 -0
  18. package/dist/config/shape.d.ts +232 -0
  19. package/dist/config-registry.d.ts +127 -0
  20. package/dist/config-schema-board-project.d.ts +157 -0
  21. package/dist/config-schema-bugs.d.ts +71 -0
  22. package/dist/config-schema-errors.d.ts +54 -0
  23. package/dist/config-schema-features.d.ts +64 -0
  24. package/dist/config-schema-hub.d.ts +150 -0
  25. package/dist/config-schema-issues.d.ts +95 -0
  26. package/dist/config-schema-logger.d.ts +91 -0
  27. package/dist/config-schema-loop-continue.d.ts +122 -0
  28. package/dist/config-schema-loop-retries.d.ts +94 -0
  29. package/dist/config-schema-release.d.ts +307 -0
  30. package/dist/config-schema-tests.d.ts +170 -0
  31. package/dist/config-schema-triage.d.ts +142 -0
  32. package/dist/config-schema-wrap-up.d.ts +96 -0
  33. package/dist/core/command/command.d.ts +228 -0
  34. package/dist/core/command/confirm.d.ts +67 -0
  35. package/dist/core/command/index.d.ts +112 -0
  36. package/dist/core/command/lazy.d.ts +54 -0
  37. package/dist/core/command/registry-types.d.ts +96 -0
  38. package/dist/core/command/running.d.ts +96 -0
  39. package/dist/core/command/spends.d.ts +39 -0
  40. package/dist/core/command/types.d.ts +92 -0
  41. package/dist/core/command/version.d.ts +4 -0
  42. package/dist/core/config/branch-template.d.ts +20 -0
  43. package/dist/core/config/config-items.d.ts +86 -0
  44. package/dist/core/config/config-load.d.ts +47 -0
  45. package/dist/core/config/config-origins.d.ts +57 -0
  46. package/dist/core/config/config-readers.d.ts +167 -0
  47. package/dist/core/config/config-schema.d.ts +207 -0
  48. package/dist/core/config/config-sections.d.ts +493 -0
  49. package/dist/core/config/config.d.ts +320 -0
  50. package/dist/core/config/core-section.d.ts +39 -0
  51. package/dist/core/config/dangerous.d.ts +58 -0
  52. package/dist/core/config/default-routing.d.ts +16 -0
  53. package/dist/core/config/index.d.ts +141 -0
  54. package/dist/core/config/scope.d.ts +42 -0
  55. package/dist/core/config/section.d.ts +206 -0
  56. package/dist/core/config/sections.d.ts +45 -0
  57. package/dist/core/config/standard-schema.d.ts +27 -0
  58. package/dist/core/git/commit.d.ts +232 -0
  59. package/dist/core/git/gh-runner.d.ts +24 -0
  60. package/dist/core/git/git.d.ts +22 -0
  61. package/dist/core/git/index.d.ts +85 -0
  62. package/dist/core/loop/claude.d.ts +388 -0
  63. package/dist/core/loop/hop-record.d.ts +52 -0
  64. package/dist/core/loop/index.d.ts +179 -0
  65. package/dist/core/loop/session-env.d.ts +20 -0
  66. package/dist/core/loop/session-record-parse.d.ts +63 -0
  67. package/dist/core/loop/sessions.d.ts +408 -0
  68. package/dist/core/loop/suite-failure.d.ts +25 -0
  69. package/dist/core/output/active.d.ts +100 -0
  70. package/dist/core/output/stream.d.ts +20 -0
  71. package/dist/core/output/text.d.ts +55 -0
  72. package/dist/core/plan/block-reader.d.ts +46 -0
  73. package/dist/core/plan/blocks.d.ts +139 -0
  74. package/dist/core/plan/declaration.d.ts +366 -0
  75. package/dist/core/plan/plan-stamp.d.ts +75 -0
  76. package/dist/core/runtime/identity.d.ts +21 -0
  77. package/dist/core/runtime/index.d.ts +46 -0
  78. package/dist/effort/store/bring-forward.d.ts +131 -0
  79. package/dist/effort/store/check-rows.d.ts +31 -0
  80. package/dist/effort/store/commit-stats.d.ts +42 -0
  81. package/dist/effort/store/copy.d.ts +35 -0
  82. package/dist/effort/store/development-build.d.ts +105 -0
  83. package/dist/effort/store/effort-levels.d.ts +13 -0
  84. package/dist/effort/store/findings.d.ts +214 -0
  85. package/dist/effort/store/fix-schema.d.ts +184 -0
  86. package/dist/effort/store/index.js +10 -7
  87. package/dist/effort/store/location.d.ts +44 -0
  88. package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
  89. package/dist/effort/store/merge-conflicts.d.ts +91 -0
  90. package/dist/effort/store/merge-rules.d.ts +82 -0
  91. package/dist/effort/store/merge-store.d.ts +211 -0
  92. package/dist/effort/store/merge-union.d.ts +92 -0
  93. package/dist/effort/store/migrate.d.ts +146 -0
  94. package/dist/effort/store/migrations.d.ts +92 -0
  95. package/dist/effort/store/ndjson-files.d.ts +114 -0
  96. package/dist/effort/store/origins.d.ts +116 -0
  97. package/dist/effort/store/plan-stubs.d.ts +79 -0
  98. package/dist/effort/store/rebuild-aside.d.ts +172 -0
  99. package/dist/effort/store/schema-plan.d.ts +102 -0
  100. package/dist/effort/store/session-row-types.d.ts +64 -0
  101. package/dist/effort/store/settings.d.ts +13 -0
  102. package/dist/effort/store/sqlite.d.ts +340 -0
  103. package/dist/effort/store/store-generation.d.ts +17 -0
  104. package/dist/effort/store/store-identity.d.ts +220 -0
  105. package/dist/effort/store/store-meta.d.ts +305 -0
  106. package/dist/effort/store/store-replaced.d.ts +72 -0
  107. package/dist/effort/store/types.d.ts +223 -0
  108. package/dist/errors/codes.d.ts +56 -0
  109. package/dist/errors/rafa-codes.d.ts +36 -0
  110. package/dist/index-2a7nb3an.js +125 -0
  111. package/dist/{index-r28fzsvc.js → index-39kbcw68.js} +350 -165
  112. package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
  113. package/dist/index-cy98y87p.js +2597 -0
  114. package/dist/index-gqz4jn6j.js +13447 -0
  115. package/dist/index-gvek4r8m.js +710 -0
  116. package/dist/index-j4k2vdz3.js +1125 -0
  117. package/dist/index-jh1r4kcn.js +1067 -0
  118. package/dist/{index-mgh2s9bj.js → index-ykggr043.js} +58 -455
  119. package/dist/index-z1w83f81.js +4 -0
  120. package/dist/index.js +14715 -21516
  121. package/dist/learning/index.js +5 -6
  122. package/dist/plan/index.js +12 -11
  123. package/dist/plan-prompt.md +3 -1
  124. package/dist/ports/index.d.ts +732 -0
  125. package/dist/ports/learning.d.ts +101 -0
  126. package/dist/ports/log-level.d.ts +13 -0
  127. package/dist/ports/spec-review.d.ts +66 -0
  128. package/dist/pr/checks.d.ts +133 -0
  129. package/dist/pr/types.d.ts +422 -0
  130. package/dist/ready-4f8edezt.js +2016 -0
  131. package/dist/release-templates/release-bot-fragment.yml +153 -0
  132. package/dist/release-templates/release-publish.yml +110 -0
  133. package/dist/release-templates/release-settle.yml +122 -0
  134. package/dist/release-templates/release-tag.yml +102 -0
  135. package/dist/report/parse.d.ts +415 -0
  136. package/dist/schema/project-id.d.ts +79 -0
  137. package/dist/schema/tiers.d.ts +107 -0
  138. package/package.json +41 -3
  139. package/dist/index-hf2k497d.js +0 -16
  140. package/dist/index-nsr78ct2.js +0 -8219
package/README.md CHANGED
@@ -220,10 +220,19 @@ merged now. The fragment is stored under `release.fragments` (default
220
220
  waiting and suggests running settle. `rafa pr list` marks forecasts as
221
221
  `(base moved)` if the base branch changed since the forecast was written.
222
222
 
223
+ A workspace package under `packages/` that is not `private: true` can release
224
+ on its own. A fragment names it with a fourth front-matter line,
225
+ `package: <name>` (the package's manifest `name`); a fragment without the line
226
+ is the root's. Settle then moves that package's version, changelog and tag
227
+ beside the root's, and `rafa release list` prints one row per unit that
228
+ releases: the root, then each publishable package, with its version, waiting
229
+ fragments and newest tag. `context/release.md` has the rules.
230
+
223
231
  Every project starts with fragments settling to the base branch and tagging
224
232
  by hand. You can configure settle to open a pending release pull request
225
233
  instead (for protected base branches), and have settle tag the commit
226
- automatically (for CI ownership of releases). Four keys, all optional:
234
+ automatically (for CI ownership of releases). Every key is optional; the
235
+ examples below set `release.settle`, `release.tag` and `pr.versionCollision`:
227
236
 
228
237
  ```yaml
229
238
  # Empty config: fragments in .changes/, semver-by-level, settle pushes, tags by hand
@@ -244,14 +253,74 @@ pr:
244
253
  versionCollision: refuse
245
254
  ```
246
255
 
247
- The keys are: `release.fragments` (path, default `.changes/`), `release.strategy`
248
- (fold strategy, default `semver-by-level`), `release.settle` (`push` or `pr`,
249
- default `push`), `release.tag` (`manual` or `settle`, default `manual`),
250
- `release.publishCommand` (the publish line `rafa release tag` prints and never
251
- runs, default `npm publish`). `pr.versionCollision` controls what happens when a merge would collide
252
- (default `report`). Read `context/release.md` for the full design,
253
- `docs/ci-release-settle.md` for automating settle in CI, and
254
- `rafa release settle --help` for the settle command.
256
+ The keys are: `release.fragments` (path, default `.changes/`),
257
+ `release.strategy` (fold strategy, default `semver-by-level`),
258
+ `release.settle` (`push` or `pr`, default `push`), `release.tag` (`manual` or
259
+ `settle`, default `manual`), `release.publish` (`manual` or `ci`, who publishes
260
+ a tagged version, default `manual`), `release.publishCommand` (the publish line
261
+ `rafa release tag` prints and `rafa release publish` runs in each tagged unit's
262
+ folder, default `npm publish`), `release.moveLock` (`true` has settle write
263
+ `rafa.lock` in its release commit, default `false`), and `pr.versionCollision`
264
+ (what happens when a merge would collide, default `report`). Read
265
+ `context/release.md` for the full design, `docs/ci-release-settle.md` for
266
+ automating settle in CI, and `rafa release settle --help` for the settle
267
+ command.
268
+
269
+ ### The release ladder
270
+
271
+ The release keys step a project up a ladder, one level at a time, from nothing
272
+ on its own (0) to a release on every merge (4):
273
+
274
+ | Level | `release.settle` | `release.tag` | `release.publish` | Runs on its own |
275
+ |---|---|---|---|---|
276
+ | 0 Manual | any | `manual` | `manual` | nothing |
277
+ | 1 Settle tags | `push` | `settle` | `manual` | version, changelog and tag in one settle |
278
+ | 2 CI publishes | `push` | `manual` | `ci` | a pushed tag runs the gates and publishes with provenance |
279
+ | 3 Release PR | `pr` | `settle` | `ci` | every merge updates one release pull request; merging it tags and publishes |
280
+ | 4 Continuous | `push` | `settle` | `ci` | every push to the default branch settles, then publishes the release it tags |
281
+
282
+ `rafa doctor` prints the reading of the keys as its `Release ladder:` row, with
283
+ what the level still needs. The row never refuses a setting. The release order
284
+ across packages is parked until two publishable packages depend on each other.
285
+
286
+ To move to level 2, where a pushed tag publishes, take four steps:
287
+
288
+ 1. Set the key in `.rafa/config.yaml`:
289
+
290
+ ```yaml
291
+ release:
292
+ publish: ci
293
+ ```
294
+
295
+ 2. Run `rafa release workflows --write`. It writes
296
+ `.github/workflows/release-publish.yml` from the shipped template. A file
297
+ already at that path is left as it is.
298
+ 3. On npmjs.com, add a trusted publisher for the package that names this
299
+ repository and `release-publish.yml`. Trusted publishing needs npm 11.5.1 or
300
+ newer.
301
+ 4. In the repository's GitHub settings, open Settings > Secrets and variables >
302
+ Actions > Variables, and add the repository variable `RAFA_RELEASE_PUBLISH`
303
+ with the value `ci`. From a shell, the same is:
304
+
305
+ ```bash
306
+ gh variable set RAFA_RELEASE_PUBLISH --body ci
307
+ ```
308
+
309
+ Until the variable is `ci`, the publish job is skipped, and a skipped job is not
310
+ a failed run. Then `rafa release tag --push` tags the release and pushes the
311
+ tag, and the publish workflow runs the gates and publishes it with provenance.
312
+
313
+ The release commands beyond settle and tag:
314
+
315
+ ```bash
316
+ rafa release publish [--tag=<tag>] [--dry-run] # publishCommand in each unit tagged at HEAD
317
+ rafa release workflows [--write] # the ladder's workflow files
318
+ rafa release fragment --plan=<id> --level=<level> --title=<text> # a branch with no wrap-up
319
+ ```
320
+
321
+ `rafa release publish` skips a version the registry already holds, so a rerun
322
+ after a publish that stopped halfway publishes only the units the registry
323
+ lacks.
255
324
 
256
325
  ### Tracking effort and learning from skills
257
326
 
@@ -265,8 +334,11 @@ rafa effort import <file> # merge another device's store into this one
265
334
  ```
266
335
 
267
336
  `rafa effort report` shows the per-plan tables (sessions by status and
268
- outcome), the task report tallies, and any runs whose preflight halted. To
269
- see which skills earned their place and which are ignored:
337
+ outcome), the task report tallies, any runs whose preflight halted,
338
+ how many of the filed bugs name only documentation, and per plan its
339
+ out-of-scope entries by the kind the loop routed them under, with the
340
+ share filed. To see which skills earned their place and which are
341
+ ignored:
270
342
 
271
343
  ```bash
272
344
  rafa effort report --skills
@@ -405,7 +477,12 @@ writes it; you can change it.
405
477
  `project` pin while a loaded user tier does; pin the tier the refusal
406
478
  names, or delete or rename the copy it names. `loop start` refuses a
407
479
  plan that names an agent or a skill it cannot resolve before any
408
- session is paid for.
480
+ session is paid for. `rafa plan validate` names a plan whose task edits
481
+ an agent or a skill rafa bundles, with the pin that item needs, and
482
+ writes the pin as a `[human]` prerequisite of the plan, so a clone that
483
+ merges the plan sets the pin itself; that note is never a refusal. The
484
+ pin is not set at merge, since a pin is per clone and gitignored, and
485
+ one set unasked would hide the drift `doctor` shows.
409
486
  - User-tier items are invisible unless `loop.settingSources` includes
410
487
  `user`. `rafa agent list` shows what a run sees, `rafa agent vendor
411
488
  <name>` copies one in or updates it, and `rafa skill check .claude/skills
@@ -517,16 +594,121 @@ rafa· no pr no open pull request for feat/rafa-485
517
594
 
518
595
  ## Configuration
519
596
 
520
- Settings live in `.rafa/config.yaml` in the project, and in
521
- `~/.rafa/config.yaml` for every project on the machine; a setting in the
522
- project's file outranks the same one in yours. `rafa init` writes both
523
- with every setting commented out at its default, so uncomment a line,
524
- with its section line, to change it.
597
+ rafa reads its settings from one file per scope. The project scope is
598
+ the repository's root; the user scope is `~/.rafa/`, read under every
599
+ project on the machine. In each scope the lookup takes the first of
600
+ three names that is a file, and the others are not read:
601
+
602
+ 1. `rafa.config.ts`, a TypeScript module
603
+ 2. `rafa.config.mjs`, a JavaScript module
604
+ 3. `.rafa/config.yaml` in the project, `~/.rafa/config.yaml` for the user
605
+
606
+ The layers stack: rafa's defaults, under the user scope's file, under the
607
+ project's. A setting the project's file names outranks the same one in
608
+ the user's, key by key; a setting neither file names takes its default.
609
+ `rafa init` writes both YAML files with every setting commented out at
610
+ its default, so uncomment a line, with its section line, to change it.
525
611
 
526
612
  Tab indentation in `.rafa/config.yaml` is refused with rafa's own error
527
613
  on every Bun version, before the parser sees the file. The error names
528
614
  the line that carries a tab; use spaces only.
529
615
 
616
+ ### Which form to use
617
+
618
+ Pick the lightest form your situation needs:
619
+
620
+ - **No config at all.** rafa runs on its defaults, as it always has.
621
+ - **An older project on YAML.** Nothing changes; `.rafa/config.yaml` is
622
+ read as before.
623
+ - **A solo Python repository with rafa installed globally.** Write
624
+ `rafa.config.mjs` holding a plain list, or `rafa.config.ts` with only
625
+ an `import type`. The type import is erased before the file runs, so
626
+ nothing is needed at run time. A module that imports
627
+ `@open-tomato/rafa` at run time finds no such package here and is
628
+ refused, naming the file.
629
+ - **A JS or TS project that installs rafa as a dev dependency.** Write
630
+ `satisfies Config` or `defineConfig`, both from
631
+ `@open-tomato/rafa/config`, and your editor checks the entries.
632
+ - **A project moving from YAML to TypeScript.** For a while both files
633
+ sit side by side. The TypeScript file is read, and a warning names the
634
+ YAML file as not read; `rafa doctor` lists it until its keys have
635
+ moved, and then it can be removed.
636
+
637
+ The same setting in each form:
638
+
639
+ ```yaml
640
+ # .rafa/config.yaml
641
+ naming:
642
+ branch: feat/{stub}
643
+ ```
644
+
645
+ ```js
646
+ // rafa.config.mjs
647
+ export default [{ naming: { branch: 'feat/{stub}' } }];
648
+ ```
649
+
650
+ ```ts
651
+ // rafa.config.ts
652
+ import type { Config } from '@open-tomato/rafa/config';
653
+
654
+ export default [{ naming: { branch: 'feat/{stub}' } }] satisfies Config;
655
+ ```
656
+
657
+ ```ts
658
+ // rafa.config.ts
659
+ import { defineConfig } from '@open-tomato/rafa/config';
660
+
661
+ export default defineConfig([{ dangerous: { selfUpdateDuringLoop: true } }]);
662
+ ```
663
+
664
+ The import from `@open-tomato/rafa/config` is the one that needs the
665
+ package installed where the file runs. For this iteration it can be
666
+ solved with something simple attacking the type project specifically,
667
+ but the bigger issue remains.
668
+
669
+ Some routes were set aside, each for one reason. Merging both files of
670
+ one scope is not done: a scope reads one file, so one answer per key.
671
+ A Bun plugin redirecting the import does not resolve, as measured.
672
+ Setting `NODE_PATH` and starting rafa again is not done: it would start
673
+ a second process to load one file. Requiring a local install, as ESLint
674
+ and Vite do, is not done: it would refuse the global Python case above.
675
+
676
+ ### Shadowed YAML and `rafa config show`
677
+
678
+ When a scope holds a module file and a YAML file, the YAML file is not
679
+ read, and every load says so:
680
+
681
+ ```text
682
+ rafa config: /repo/.rafa/config.yaml is not read: /repo/rafa.config.ts holds the project scope's config, so move its keys into rafa.config.ts and remove it
683
+ ```
684
+
685
+ `rafa config show` prints every setting with its merged value, nested
686
+ as a config file spells it. `rafa config show --sources` prints one line
687
+ per setting instead: its value, the layer that answered (`project`,
688
+ `user` or `default`) and, for a file, the file and the zero-based entry
689
+ that last set it; a YAML file is entry 0. `--output=json` carries the
690
+ same data for a script. The two views are refused together.
691
+
692
+ ### Changing a key with `config set`
693
+
694
+ `rafa config set <key>=<value>` edits the project's `.rafa/config.yaml`
695
+ in place and keeps its comments. In a project whose config is
696
+ `rafa.config.ts` or `rafa.config.mjs`, it refuses with exit 1, naming
697
+ that file and writing nothing, because a module is code the command does
698
+ not rewrite. Edit the module by hand until #893 gives the command a file
699
+ to write.
700
+
701
+ ### `dangerous.*` settings
702
+
703
+ A `dangerous.*` key is a person's consent, so rafa says what is in force:
704
+ once per run, before its work starts, every command that loads the
705
+ config prints one line for each `dangerous.*` key set off its default,
706
+ naming the file and entry that set it, such as:
707
+
708
+ ```text
709
+ dangerous.selfUpdateDuringLoop: true (/repo/rafa.config.ts, entry 0)
710
+ ```
711
+
530
712
  ### `effort`
531
713
 
532
714
  One setting says how long a command waits for another rafa process that
@@ -692,6 +874,52 @@ dangerous:
692
874
  selfUpdateDuringLoop: true # update the binary even while a loop runs
693
875
  ```
694
876
 
877
+ ### `merge`
878
+
879
+ One setting says what a load does when one key is set twice in a file.
880
+
881
+ | Key | Default | What it sets |
882
+ |---|---|---|
883
+ | `merge.duplicates` | `warn` | `warn` prints each duplicate, `error` refuses the load with every one, `allow` drops them |
884
+
885
+ Two entries of one module file setting one key, or a YAML key given
886
+ twice, are duplicates. The project's file decides the level, over the
887
+ user's. `config set` refuses a key written twice in the same file, as
888
+ the same mistake.
889
+
890
+ ```yaml
891
+ merge:
892
+ duplicates: error
893
+ ```
894
+
895
+ ### `naming`
896
+
897
+ One setting names the branch `rafa loop start` creates for a plan.
898
+
899
+ | Key | Default | What it sets |
900
+ |---|---|---|
901
+ | `naming.branch` | `feat/{stub}` | the branch name; `{stub}` appears exactly once and stands for the plan's stub |
902
+
903
+ A template with no `{stub}`, or with it twice, is refused. The value is
904
+ kept as written.
905
+
906
+ ```yaml
907
+ naming:
908
+ branch: feat/{stub}
909
+ ```
910
+
911
+ ### Config code and `pr triage --resolve`
912
+
913
+ A `rafa.config.ts` or `rafa.config.mjs` is a module rafa imports, so it
914
+ runs with your shell, credentials and remotes. `pr triage --resolve`
915
+ checks a pull request's branch out and starts a loop in it, and that loop
916
+ loads whatever config the branch carries. So under `--resolve`, a head
917
+ carrying a config module that `origin/<base>` does not hold as the same
918
+ file (compared byte for byte) is refused with exit 1, before the worktree
919
+ is added. Passing `--allow-local-config` runs it anyway and prints one
920
+ warning line naming what it let through. A YAML config is data and is not
921
+ checked this way.
922
+
695
923
  ## Specs, issues and the roadmap
696
924
 
697
925
  You can plan from a local file and never touch a board. When you want
@@ -784,6 +1012,9 @@ The full guide, with the spec template explained, a prompt for drafting
784
1012
  a spec, epics, boards and every gate in order, is
785
1013
  [docs/specs-and-roadmap.md](docs/specs-and-roadmap.md).
786
1014
 
1015
+ The version policy, the bump levels, which package publishes and the
1016
+ `REFACTOR_NEEDED.md` rule are in [docs/versioning.md](docs/versioning.md).
1017
+
787
1018
  ## From a checkout
788
1019
 
789
1020
  To install dependencies:
@@ -800,7 +1031,7 @@ bun src/rafa.ts loop start --plan=<file>
800
1031
 
801
1032
  Every command but `init`, the help and `describe` runs inside a rafa
802
1033
  project: the nearest directory at or above the working directory holding
803
- `.rafa/config.yaml`. Outside one it prints the `rafa init` hint and exits
1034
+ `rafa.config.ts`, `rafa.config.mjs` or `.rafa/config.yaml`. Outside one it prints the `rafa init` hint and exits
804
1035
  1; `bun src/rafa.ts init --yes` sets up the git toplevel as one.
805
1036
 
806
1037
  `bun run build` writes `dist/`, which the `rafa` bin and the package's
@@ -868,11 +1099,12 @@ takes `--yes`. The rest of `rafa update` (`self`, `project`, `board`,
868
1099
  The build targets bun, and `engines` names `bun` alone: there is no
869
1100
  `engines.node`, because no node version runs the binary. The package
870
1101
  root, `./cli` and `./store` import `bun:sqlite`, which node's ESM loader
871
- refuses before any module code runs, so only `./plan`, `./ports` and
872
- `./learning` load under node at all. `module` points at
873
- `./dist/index.js`, the same build the root of `exports` names. The package ships no type declarations:
874
- `exports` names no `types`, and a TypeScript consumer gets TS7016 under
875
- `strict`.
1102
+ refuses before any module code runs, so only `./plan`, `./ports`,
1103
+ `./learning` and `./config` load under node at all. `module` points at
1104
+ `./dist/index.js`, the same build the root of `exports` names. Only
1105
+ `./config` ships type declarations, at `./dist/config/index.d.ts`, which
1106
+ its `exports` entry names as `types`. No other entry names `types`, and
1107
+ a TypeScript consumer importing one gets TS7016 under `strict`.
876
1108
 
877
1109
  ## Roadmap
878
1110
 
package/dist/PROMPT.md CHANGED
@@ -4,7 +4,16 @@ Do not stage or commit anything: the loop owns staging and committing, and once
4
4
 
5
5
  Run every command in the foreground, the full test suite included: never start a background command and never wait on a notification. This session is a single `claude -p` turn, so ending it to wait for something leaves no report and no commit; end the session only after your `rafa:report` block.
6
6
 
7
- End your final message with exactly one `rafa:report` block shaped like the example below, and write nothing after it: the loop stores what it reads there, and a message without the block stores none of your findings. Write all seven fields, `status`, `feedback`, `findings`, `skills_used`, `blockers`, `out_of_scope_bugs` and `changes`, and write a list with nothing in it as `[]` rather than leaving its field out. `status` is `done` or `blocked`. `feedback` says what was done and how it went. `findings` is a list with one entry per distinct observation, never several merged into one; a finding's `kind` is one of `gotcha`, `pattern`, `location` or `skill-suggestion`, its `signal` is `loud` when it surfaced as a failure or `silent` when something passed while wrong, and its `artifact`, when there is one, is the exact string a recurrence would match on, such as an error message. A finding may also carry a `domain`, the area of work its lesson belongs to, one of `code-style`, `testing`, `git`, `debugging`, `workflow`, `security` or `general-best-practices`; leave it out when none fits. `skills_used` names each skill you invoked. `blockers` lists what stopped the task, each entry a `what` and, when there is one, an `artifact`, `out_of_scope_bugs` the bugs you saw outside it, each bug with `security` set to `true` or `false` — a test type error that the task's base already held is part of the measured backlog and is not reported as a bug, and the type step's line names how many errors the base held — and `changes` is the changelog for your diff, each entry holding the required fields `level` (one of patch, minor, major, none) and `summary` (a quoted line the user would understand), plus optional `area` (the changelog section). Write those closed-set words and `security` bare, as the example does, and double-quote every other value on one line: an unquoted value that opens with a backtick, `@`, `%` or `[` makes the whole block unreadable, and a `#` after a space silently cuts a value short.
7
+ End your final message with exactly one `rafa:report` block shaped like the example below, and write nothing after it: the loop stores what it reads there, and a message without the block stores none of your findings. Write all eight fields, `status`, `feedback`, `findings`, `skills_used`, `blockers`, `out_of_scope_bugs`, `changes` and `stale_docs`, and write a list with nothing in it as `[]` rather than leaving its field out. `status` is `done` or `blocked`. `feedback` says what was done and how it went. `findings` is a list with one entry per distinct observation, never several merged into one; a finding's `kind` is one of `gotcha`, `pattern`, `location` or `skill-suggestion`, its `signal` is `loud` when it surfaced as a failure or `silent` when something passed while wrong, and its `artifact`, when there is one, is the exact string a recurrence would match on, such as an error message. A finding may also carry a `domain`, the area of work its lesson belongs to, one of `code-style`, `testing`, `git`, `debugging`, `workflow`, `security` or `general-best-practices`; leave it out when none fits. `skills_used` names each skill you invoked. `blockers` lists what stopped the task, each entry a `what` and, when there is one, an `artifact`, `out_of_scope_bugs` what you saw wrong outside it, each entry a `what`, an `artifact` when there is one, `security` set to `true` or `false`, a `kind` from the six listed after this paragraph, and the optional `module`, `action`, `reproduce` and `cause` keys, which may be left out, with `reproduce` and `cause` always double-quoted — a test type error that the task's base already held is part of the measured backlog and is not reported as a bug, and the type step's line names how many errors the base held — and `changes` is the changelog for your diff, each entry holding the required fields `level` (one of patch, minor, major, none) and `summary` (a quoted line the user would understand), plus optional `area` (the changelog section). `stale_docs` lists the documentation lines you saw that no longer match the code, each entry a `where` (the file, with the line when known, as one string such as "context/triage.md:12") and a `what` (what no longer matches). A doc line your own change falsified is not an entry: repair it in the same commit as the change. Any other stale doc line you come across goes into `stale_docs` and never into `out_of_scope_bugs`. Write those closed-set words, `security` and an entry's `kind` bare, as the example does, and double-quote every other value on one line: an unquoted value that opens with a backtick, `@`, `%` or `[` makes the whole block unreadable, and a `#` after a space silently cuts a value short.
8
+
9
+ An `out_of_scope_bugs` entry's `kind` is one of six, a different list from a finding's `kind`:
10
+
11
+ - `bug` applies when the code does the wrong thing and nothing this plan did caused it.
12
+ - `own-fallout` applies when an earlier task of this plan broke something, or left it stale, outside your task.
13
+ - `own-gap` applies when this plan built something and left it unfinished, short of what your task needs or the plan describes.
14
+ - `convention` applies when the code works but breaks a house rule, such as a file over the line ceiling or a name off the pattern.
15
+ - `docs` applies when the leftover is a documentation fault larger than one stale line, since a single stale doc line goes into `stale_docs` instead.
16
+ - `machine` applies when the fault lies in this machine or its setup, such as a missing tool or an unreachable service, rather than in the code.
8
17
 
9
18
  ```rafa:report
10
19
  status: done
@@ -25,7 +34,15 @@ out_of_scope_bugs:
25
34
  - what: "..."
26
35
  artifact: "..."
27
36
  security: false
37
+ kind: bug
38
+ module: "src/report"
39
+ action: "parse the entry"
40
+ reproduce: "run the report parser on an entry with the four keys"
41
+ cause: "the parser did not read the optional keys"
28
42
  changes:
29
43
  - level: patch
30
44
  summary: "the loop waits for the running task's commit"
45
+ stale_docs:
46
+ - where: "context/triage.md:12"
47
+ what: "says triage has three channels"
31
48
  ```
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The logger's settings: what `logger.*` in the config says, held where
3
+ * every logger of the process can read it (#950).
4
+ *
5
+ * The dispatcher builds a command's logger before the project is found,
6
+ * and a command loads its config later. So the settings cannot be handed
7
+ * to the logger when it is made: `loadConfig`
8
+ * (`packages/rafa/src/core/config/config-load.ts`) calls core's load hook once a
9
+ * config is read, as it sets the effort store's settings, and the hook
10
+ * `src/config-registry.ts` sets hands the logger section's reading to
11
+ * this module. A logger reads them at each entry. Until then the default
12
+ * applies, with the invocation's verbosity beside it.
13
+ *
14
+ * This module imports types and `node:async_hooks` alone, so the
15
+ * registry can import it without pulling an adapter in.
16
+ *
17
+ * ## Where they are held
18
+ *
19
+ * In two places, and {@link activeLoggerSettings} and
20
+ * {@link setActiveLoggerSettings} both act on the first that applies:
21
+ *
22
+ * - **A scope.** {@link runWithLoggerSettings} runs a function with
23
+ * settings of its own in an async scope, an `AsyncLocalStorage`
24
+ * (`node:async_hooks`). Inside that function, and in everything it
25
+ * awaits or starts, the reader answers the scope's settings and the
26
+ * setter replaces them. Once the function ends, however it ends,
27
+ * nothing of them is left. The dispatcher runs every command this
28
+ * way, starting from the defaults (`src/cli/dispatch.ts`), so the
29
+ * config a command loads sets that invocation's settings alone.
30
+ * - **The module-level value.** Outside every scope the reader answers
31
+ * one module-level value and the setter replaces it, the defaults
32
+ * until something sets another. A config loaded outside every
33
+ * command sets it, as the module loader (`src/modules/load.ts`) and
34
+ * the command hook (`src/status/hook.ts`) load one around an
35
+ * invocation; no command reads it.
36
+ *
37
+ * A scope belongs to one run, so runs need not nest to stay apart. Two
38
+ * that overlap in one process, started together and awaited with
39
+ * `Promise.all`, each read and set their own whichever ends first. A
40
+ * scope opened inside another starts from the settings it is given,
41
+ * never from the outer one's, and the outer one reads its own again
42
+ * after it. A module-level value set and put back in a `finally` keeps
43
+ * overlapping runs apart only while they nest (#927).
44
+ *
45
+ * ## A set made inside a scope
46
+ *
47
+ * It replaces that scope's settings, and never the module-level value.
48
+ * This is where the settings differ from the active output
49
+ * (`packages/rafa/src/core/output/active.ts`), whose scope cannot be replaced from
50
+ * inside it: the config naming the settings is loaded inside the run
51
+ * that reads them, so the setter has to reach the run it is called in.
52
+ * The scope therefore holds a cell, and a set swaps the frozen settings
53
+ * the cell holds.
54
+ *
55
+ * ## What reads outside the scope
56
+ *
57
+ * A scope follows the awaits and the callbacks started inside it, by
58
+ * where the code runs and not by where it was written: a function made
59
+ * in one invocation and called from another reads the caller's. A
60
+ * callback the runtime calls from elsewhere does not carry it, and reads
61
+ * and sets the module-level value. Measured under bun 1.3.14, three
62
+ * listeners registered inside a scope read the module-level settings: a
63
+ * `process.on` signal listener when the signal arrived, an abort
64
+ * listener that handler fired, and an abort listener an
65
+ * `AbortSignal.timeout` fired. A timer, a microtask and the code after
66
+ * an await read the scope's, and so did a signal listener wrapped with
67
+ * `AsyncResource.bind` where it was registered.
68
+ *
69
+ * ## Which entries are on
70
+ *
71
+ * `error`, `warn` and `debug` are ordered, quietest first
72
+ * ({@link LOG_LEVELS}). An entry is on when its level is at or quieter
73
+ * than the threshold: the level its module has in `modules`, else
74
+ * `level`. `debug` is also on from verbosity {@link DEBUG_VERBOSITY}.
75
+ *
76
+ * `api` sits outside that order. It is on when `api` is true or from
77
+ * verbosity {@link API_VERBOSITY}, and nothing else turns it on: a
78
+ * level of `debug` asks for rafa's own reasoning, never for the traffic
79
+ * to a service.
80
+ *
81
+ * Bun runs every test file in one process and the module-level value is
82
+ * module state, so a case that sets settings outside a scope sets `null`
83
+ * after it.
84
+ */
85
+ import type { LogLevel } from '@open-tomato/rafa/ports/log-level';
86
+ /** How a logger colours its lines. */
87
+ export type LoggerTheme = 'default' | 'plain';
88
+ /** The ordered levels, quietest first. `api` is not one of them. */
89
+ export declare const LOG_LEVELS: readonly LogLevel[];
90
+ /** Every theme, in the order a refusal names them. */
91
+ export declare const LOGGER_THEMES: readonly LoggerTheme[];
92
+ /** The adapter kind core ships. */
93
+ export declare const DEFAULT_LOGGER_KIND = "console";
94
+ /** The verbosity at and above which `debug` entries are on. */
95
+ export declare const DEBUG_VERBOSITY = 2;
96
+ /** The verbosity at and above which `api` entries are on. */
97
+ export declare const API_VERBOSITY = 3;
98
+ /** What a logger reads at each entry. */
99
+ export interface LoggerSettings {
100
+ /** The adapter kind. `logger.kind`. */
101
+ readonly kind: string;
102
+ /** The loudest ordered level written. `logger.level`. */
103
+ readonly level: LogLevel;
104
+ /** How lines are coloured. `logger.theme`. */
105
+ readonly theme: LoggerTheme;
106
+ /** A level per module, over `level`. `logger.modules.<module>.level`. */
107
+ readonly modules: ReadonlyMap<string, LogLevel>;
108
+ /** Whether a `debug` line names the file that wrote it. `logger.callSite`. */
109
+ readonly callSite: boolean;
110
+ /** Whether `api` entries are written. `logger.api`. */
111
+ readonly api: boolean;
112
+ }
113
+ /** The settings until a config is loaded. */
114
+ export declare const DEFAULT_LOGGER_SETTINGS: LoggerSettings;
115
+ /**
116
+ * Runs `run` with settings of its own in an async scope, starting from
117
+ * `settings`, the defaults for `null`, and answers what `run` answers.
118
+ * Inside `run` and whatever it awaits they are what is read and what a
119
+ * set replaces, and nothing of them is left after it; see the module
120
+ * note.
121
+ */
122
+ export declare function runWithLoggerSettings<T>(settings: LoggerSettings | null, run: () => T): T;
123
+ /**
124
+ * Sets the settings every logger reads from here on: those of the scope
125
+ * this is called in, else the module-level ones. `null` puts the
126
+ * defaults back. See the module note.
127
+ */
128
+ export declare function setActiveLoggerSettings(settings: LoggerSettings | null): void;
129
+ /** The settings a logger reads now: those of the scope this is read in, else the module-level ones. */
130
+ export declare function activeLoggerSettings(): LoggerSettings;
131
+ /** Whether an entry at `level` from `module` is written; see the module note. */
132
+ export declare function levelEnabled(level: LogLevel, module: string | undefined, settings: LoggerSettings, verbosity: number): boolean;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The names closest to a value a person typed (#951), for a refusal that
3
+ * says "closest: billing" beside the name it turns down.
4
+ *
5
+ * Names here are single words: a module, an action, a place. So the
6
+ * measure is edits between spellings ({@link editDistance}), never the
7
+ * shared words `src/errors/match.ts` counts for a cause written as a
8
+ * sentence. A name further than half the value's length, and further than
9
+ * {@link MIN_REACH} edits, is no suggestion: it is another word.
10
+ */
11
+ /** How many names a refusal shows. */
12
+ export declare const CLOSEST_LIMIT = 3;
13
+ /** The single-character inserts, deletes and swaps that turn `a` into `b`. */
14
+ export declare function editDistance(a: string, b: string): number;
15
+ /**
16
+ * The `names` nearest `value` by edits, nearest first and by name where
17
+ * two tie, `limit` at most. See the module note for how far is too far.
18
+ */
19
+ export declare function closestNames(value: string, names: readonly string[], limit?: number): readonly string[];
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The fence a bug's free text is shown in, shared by the loop's own
3
+ * filing (`../triage/issue-text.ts`, which re-exports it) and the bug
4
+ * record (`./record.ts`). A leaf: it imports nothing, so either side
5
+ * reaches it without importing the other.
6
+ */
7
+ /** A value in a fence one backtick longer than any run of backticks it holds. */
8
+ export declare function fenced(value: string): string;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The places a bug is seen in (#951): the ones every project has, and
3
+ * the one rafa suggests.
4
+ *
5
+ * A place names WHERE a bug showed: `local`, `ci`, `prod`, or one a
6
+ * project adds under `issues.environments`. What was running belongs to
7
+ * the record's `action` and `reproduce`.
8
+ *
9
+ * rafa suggests `ci` when the `CI` variable is set to anything but
10
+ * nothing, `0` or `false`, and `local` otherwise. A filer's `--env` wins
11
+ * over the suggestion.
12
+ *
13
+ * This module imports nothing, so the config schema
14
+ * (`src/config-schema-issues.ts`) reads the built-in places without
15
+ * reaching the modules that read the config.
16
+ */
17
+ /** The places every project has, in the order a refusal lists them. */
18
+ export declare const BUILT_IN_ENVIRONMENTS: readonly ["local", "ci", "prod"];
19
+ /** The place rafa suggests for a run with the environment `env`; see the module note. */
20
+ export declare function suggestedEnvironment(env: Readonly<Record<string, string | undefined>>): 'ci' | 'local';