@open-tomato/rafa 0.44.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 (142) hide show
  1. package/README.md +280 -42
  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 +120 -24
  14. package/dist/bundled/skills/documentation/SKILL.md +2 -12
  15. package/dist/cli.js +72387 -59411
  16. package/dist/config/entry.d.ts +119 -0
  17. package/dist/config/index.d.ts +36 -0
  18. package/dist/config/index.js +51 -0
  19. package/dist/config/shape.d.ts +232 -0
  20. package/dist/config-registry.d.ts +127 -0
  21. package/dist/config-schema-board-project.d.ts +157 -0
  22. package/dist/config-schema-bugs.d.ts +71 -0
  23. package/dist/config-schema-errors.d.ts +54 -0
  24. package/dist/config-schema-features.d.ts +64 -0
  25. package/dist/config-schema-hub.d.ts +150 -0
  26. package/dist/config-schema-issues.d.ts +95 -0
  27. package/dist/config-schema-logger.d.ts +91 -0
  28. package/dist/config-schema-loop-continue.d.ts +122 -0
  29. package/dist/config-schema-loop-retries.d.ts +94 -0
  30. package/dist/config-schema-release.d.ts +307 -0
  31. package/dist/config-schema-tests.d.ts +170 -0
  32. package/dist/config-schema-triage.d.ts +142 -0
  33. package/dist/config-schema-wrap-up.d.ts +96 -0
  34. package/dist/core/command/command.d.ts +228 -0
  35. package/dist/core/command/confirm.d.ts +67 -0
  36. package/dist/core/command/index.d.ts +112 -0
  37. package/dist/core/command/lazy.d.ts +54 -0
  38. package/dist/core/command/registry-types.d.ts +96 -0
  39. package/dist/core/command/running.d.ts +96 -0
  40. package/dist/core/command/spends.d.ts +39 -0
  41. package/dist/core/command/types.d.ts +92 -0
  42. package/dist/core/command/version.d.ts +4 -0
  43. package/dist/core/config/branch-template.d.ts +20 -0
  44. package/dist/core/config/config-items.d.ts +86 -0
  45. package/dist/core/config/config-load.d.ts +47 -0
  46. package/dist/core/config/config-origins.d.ts +57 -0
  47. package/dist/core/config/config-readers.d.ts +167 -0
  48. package/dist/core/config/config-schema.d.ts +207 -0
  49. package/dist/core/config/config-sections.d.ts +493 -0
  50. package/dist/core/config/config.d.ts +320 -0
  51. package/dist/core/config/core-section.d.ts +39 -0
  52. package/dist/core/config/dangerous.d.ts +58 -0
  53. package/dist/core/config/default-routing.d.ts +16 -0
  54. package/dist/core/config/index.d.ts +141 -0
  55. package/dist/core/config/scope.d.ts +42 -0
  56. package/dist/core/config/section.d.ts +206 -0
  57. package/dist/core/config/sections.d.ts +45 -0
  58. package/dist/core/config/standard-schema.d.ts +27 -0
  59. package/dist/core/git/commit.d.ts +232 -0
  60. package/dist/core/git/gh-runner.d.ts +24 -0
  61. package/dist/core/git/git.d.ts +22 -0
  62. package/dist/core/git/index.d.ts +85 -0
  63. package/dist/core/loop/claude.d.ts +388 -0
  64. package/dist/core/loop/hop-record.d.ts +52 -0
  65. package/dist/core/loop/index.d.ts +179 -0
  66. package/dist/core/loop/session-env.d.ts +20 -0
  67. package/dist/core/loop/session-record-parse.d.ts +63 -0
  68. package/dist/core/loop/sessions.d.ts +408 -0
  69. package/dist/core/loop/suite-failure.d.ts +25 -0
  70. package/dist/core/output/active.d.ts +100 -0
  71. package/dist/core/output/stream.d.ts +20 -0
  72. package/dist/core/output/text.d.ts +55 -0
  73. package/dist/core/plan/block-reader.d.ts +46 -0
  74. package/dist/core/plan/blocks.d.ts +139 -0
  75. package/dist/core/plan/declaration.d.ts +366 -0
  76. package/dist/core/plan/plan-stamp.d.ts +75 -0
  77. package/dist/core/runtime/identity.d.ts +21 -0
  78. package/dist/core/runtime/index.d.ts +46 -0
  79. package/dist/effort/store/bring-forward.d.ts +131 -0
  80. package/dist/effort/store/check-rows.d.ts +31 -0
  81. package/dist/effort/store/commit-stats.d.ts +42 -0
  82. package/dist/effort/store/copy.d.ts +35 -0
  83. package/dist/effort/store/development-build.d.ts +105 -0
  84. package/dist/effort/store/effort-levels.d.ts +13 -0
  85. package/dist/effort/store/findings.d.ts +214 -0
  86. package/dist/effort/store/fix-schema.d.ts +184 -0
  87. package/dist/effort/store/index.js +10 -7
  88. package/dist/effort/store/location.d.ts +44 -0
  89. package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
  90. package/dist/effort/store/merge-conflicts.d.ts +91 -0
  91. package/dist/effort/store/merge-rules.d.ts +82 -0
  92. package/dist/effort/store/merge-store.d.ts +211 -0
  93. package/dist/effort/store/merge-union.d.ts +92 -0
  94. package/dist/effort/store/migrate.d.ts +146 -0
  95. package/dist/effort/store/migrations.d.ts +92 -0
  96. package/dist/effort/store/ndjson-files.d.ts +114 -0
  97. package/dist/effort/store/origins.d.ts +116 -0
  98. package/dist/effort/store/plan-stubs.d.ts +79 -0
  99. package/dist/effort/store/rebuild-aside.d.ts +172 -0
  100. package/dist/effort/store/schema-plan.d.ts +102 -0
  101. package/dist/effort/store/session-row-types.d.ts +64 -0
  102. package/dist/effort/store/settings.d.ts +13 -0
  103. package/dist/effort/store/sqlite.d.ts +340 -0
  104. package/dist/effort/store/store-generation.d.ts +17 -0
  105. package/dist/effort/store/store-identity.d.ts +220 -0
  106. package/dist/effort/store/store-meta.d.ts +305 -0
  107. package/dist/effort/store/store-replaced.d.ts +72 -0
  108. package/dist/effort/store/types.d.ts +223 -0
  109. package/dist/errors/codes.d.ts +56 -0
  110. package/dist/errors/rafa-codes.d.ts +36 -0
  111. package/dist/index-2a7nb3an.js +125 -0
  112. package/dist/{index-57mmzkcn.js → index-39kbcw68.js} +516 -217
  113. package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
  114. package/dist/index-cy98y87p.js +2597 -0
  115. package/dist/index-gqz4jn6j.js +13447 -0
  116. package/dist/index-gvek4r8m.js +710 -0
  117. package/dist/index-j4k2vdz3.js +1125 -0
  118. package/dist/index-jh1r4kcn.js +1067 -0
  119. package/dist/index-ykggr043.js +540 -0
  120. package/dist/index-z1w83f81.js +4 -0
  121. package/dist/index.js +16091 -22798
  122. package/dist/learning/index.js +5 -6
  123. package/dist/plan/index.js +12 -11
  124. package/dist/plan-prompt.md +11 -2
  125. package/dist/ports/index.d.ts +732 -0
  126. package/dist/ports/learning.d.ts +101 -0
  127. package/dist/ports/log-level.d.ts +13 -0
  128. package/dist/ports/spec-review.d.ts +66 -0
  129. package/dist/pr/checks.d.ts +133 -0
  130. package/dist/pr/types.d.ts +422 -0
  131. package/dist/ready-4f8edezt.js +2016 -0
  132. package/dist/release-templates/release-bot-fragment.yml +153 -0
  133. package/dist/release-templates/release-publish.yml +110 -0
  134. package/dist/release-templates/release-settle.yml +122 -0
  135. package/dist/release-templates/release-tag.yml +102 -0
  136. package/dist/report/parse.d.ts +415 -0
  137. package/dist/schema/project-id.d.ts +79 -0
  138. package/dist/schema/tiers.d.ts +107 -0
  139. package/package.json +42 -4
  140. package/dist/index-6q30dtgm.js +0 -6342
  141. package/dist/index-cknayrpc.js +0 -2414
  142. package/dist/index-hf2k497d.js +0 -16
@@ -0,0 +1,63 @@
1
+ import type { SessionDecision, SessionRecord, SessionStep } from './sessions.js';
2
+ /** The states a session record holds, in the spec's order. */
3
+ export declare const SESSION_STATES: readonly ['running', 'paused', 'stopped', 'done'];
4
+ /** One of {@link SESSION_STATES}. */
5
+ export type SessionState = (typeof SESSION_STATES)[number];
6
+ /**
7
+ * The phases of a run, in the order a run meets them: its tasks, the
8
+ * wrap-up, the pull request, the CI wait, and a repair of what CI failed.
9
+ */
10
+ export declare const SESSION_PHASES: readonly ['task', 'wrap-up', 'pull-request', 'ci', 'repair'];
11
+ /** One of {@link SESSION_PHASES}. */
12
+ export type SessionPhase = (typeof SESSION_PHASES)[number];
13
+ /** What a record's file name ends in. */
14
+ export declare const RECORD_EXTENSION = ".json";
15
+ /** The kinds of suite step a run records, in the order a run meets them. */
16
+ export declare const SESSION_STEP_KINDS: readonly ['baseline', 'task', 'stage', 'pre-wrap-up'];
17
+ /** One of {@link SESSION_STEP_KINDS}. */
18
+ export type SessionStepKind = (typeof SESSION_STEP_KINDS)[number];
19
+ /**
20
+ * Why a suite step ran at the scope it did: the task's own `tests=`
21
+ * declaration, a full-suite trigger, the `--changed` fallback taken when
22
+ * no `Owns:` folder narrows the run, a `tests=module` task whose module
23
+ * holds no test file, and a stage step's own scope.
24
+ */
25
+ export declare const SESSION_STEP_REASONS: readonly ['declared', 'trigger', 'fallback', 'no-module-tests', 'stage'];
26
+ /** One of {@link SESSION_STEP_REASONS}. */
27
+ export type SessionStepReason = (typeof SESSION_STEP_REASONS)[number];
28
+ /** The strategies a saved decision may name: the two that pass over a task. */
29
+ export declare const SESSION_DECISION_STRATEGIES: readonly ['jump', 'defer'];
30
+ /** A record file that cannot be read, or holds no record this module accepts. */
31
+ export declare class SessionRecordError extends Error {
32
+ /** The record's path. */
33
+ readonly file: string;
34
+ constructor(file: string, problem: string);
35
+ }
36
+ /** True when the text can name a record file. */
37
+ export declare function isSessionId(value: string): boolean;
38
+ /** True for a whole number from 1. */
39
+ export declare function isPositiveWhole(value: unknown): value is number;
40
+ /** Every problem with one step, the `at` naming its place in the record. */
41
+ export declare function stepProblems(step: unknown, at: string): string[];
42
+ /** Every problem with a parsed record read from `file`. */
43
+ export declare function recordProblems(fields: object, file: string): string[];
44
+ /**
45
+ * A frozen record of fields already checked, in the order it is written;
46
+ * `phase`, `hop`, `worktree`, `steps` then `decisions` last, each only
47
+ * when there, `phase` only when it is one of {@link SESSION_PHASES}.
48
+ */
49
+ export declare function freezeRecord(fields: object): SessionRecord;
50
+ /** The steps a record holds, oldest first; none for a record written before the field. */
51
+ export declare function sessionSteps(record: Pick<SessionRecord, 'steps'>): readonly SessionStep[];
52
+ /** The pass-over list a record saved; none for a record without the key. */
53
+ export declare function sessionDecisions(record: Pick<SessionRecord, 'decisions'>): readonly SessionDecision[];
54
+ /**
55
+ * The phase a record reads as: its own, or `task` for a record without
56
+ * one, as a record from a rafa older than the field is.
57
+ */
58
+ export declare function sessionPhase(record: Pick<SessionRecord, 'phase'>): SessionPhase;
59
+ /**
60
+ * Reads a record out of the text of `file`, or throws
61
+ * {@link SessionRecordError} naming every problem. See the module note.
62
+ */
63
+ export declare function parseSessionRecord(text: string, file: string): SessionRecord;
@@ -0,0 +1,408 @@
1
+ /**
2
+ * The session records of `loop start`: one JSON file per run under
3
+ * `<root>/.rafa/runs/`, named by the session's id.
4
+ *
5
+ * `.rafa/specs/cli-surface.md`, "Sessions": `loop stop`, `pause`, `resume`,
6
+ * `status` and `list` act on a run through its record, so every
7
+ * `loop start` writes `.rafa/runs/<session-id>.json`. This module reads,
8
+ * judges and writes those files, and prints nothing. `start/session.ts`
9
+ * is where `loop start` calls it.
10
+ *
11
+ * ## The record
12
+ *
13
+ * A {@link SessionRecord}, written as indented JSON:
14
+ *
15
+ * - `sessionId`: the run's id, the file's name before `.json`.
16
+ * - `planStub`: the plan's stub, or null for a plan whose file name
17
+ * carries none, as `PLAN.md` does (`core/plan/plan-stamp.ts`).
18
+ * - `plan`: the plan's path relative to the project root. The spec lists
19
+ * the stub alone; the path is what tells two stubless plans apart.
20
+ * - `branch`: the branch the run was started on.
21
+ * - `pid`: the process running the loop.
22
+ * - `startedAt`: when the run began, as an ISO timestamp.
23
+ * - `state`: one of {@link SESSION_STATES}.
24
+ * - `task`: the task running, its 1-based tracker line and its sentence,
25
+ * or null.
26
+ * - `phase`: where the run is, one of {@link SESSION_PHASES}: `task`
27
+ * while it works its plan's tasks, then `wrap-up`, `pull-request`,
28
+ * `ci` and `repair`. The field is additive, as `steps` is: a record
29
+ * written before it, and a new record, carry no `phase` key, and
30
+ * {@link sessionPhase} reads such a record as `task`. A phase this
31
+ * rafa does not know, as a later rafa may write, is no problem on
32
+ * read and reads as `task` too; it is left out of the record read,
33
+ * so the next write of the record carries no `phase` key. It is
34
+ * written as its own `"phase": "<value>"` key, as `state` is, which
35
+ * the zsh prompt plugin's patterns over the file's text rely on
36
+ * (`extras/zsh/rafa-prompt/`). Once written, every later write keeps
37
+ * it until a change names another ({@link SessionChange}'s `phase`).
38
+ * - `hop`: only on a run started with `loop start --roadmap` while a
39
+ * `rafa next --roadmap` hop is away, the hop record as
40
+ * `.rafa/hop.json` held it when the run began
41
+ * (`src/next/hop-record.ts`, `start/session.ts`). A record of any
42
+ * other run carries no `hop` key at all, never one set to null, so
43
+ * such a record is written byte for byte as it was before the field.
44
+ * Once written, every later write of the record keeps it as it is.
45
+ * - `worktree`: only on a run whose checkout is a linked worktree rather
46
+ * than the project root, as a `loop start --as-worktree` run's is, the
47
+ * absolute path of that worktree (`start/session.ts`). A run in the
48
+ * main checkout carries no `worktree` key at all, as it carries no
49
+ * `hop`, and every later write keeps the path as it is.
50
+ * - `steps`: the suite steps the runner has recorded for the run, oldest
51
+ * first, each a {@link SessionStep}. The field is additive: a record
52
+ * written before it, and a run that has recorded no step, carry no
53
+ * `steps` key at all, and {@link sessionSteps} reads such a record as
54
+ * holding none. A stored `steps: []` reads the same way and is written
55
+ * back without the key. Steps are only ever appended
56
+ * ({@link SessionChange}'s `appendStep`); every later write keeps them.
57
+ * - `decisions`: the pass-over list of a `loop start --continue` run
58
+ * (`start/pass-over.ts`), each a {@link SessionDecision}: the task
59
+ * jumped or deferred, its `lineNum` counted from 0 as the tracker
60
+ * reader counts it, its text with the blocker comment and
61
+ * declaration off, and its `ordinal`, which copy of that text it is
62
+ * among the tracker's task lines (left out of a record from before
63
+ * the field, and read as 1), the strategy, `jump` or `defer`, the
64
+ * decision's reason, and on a defer alone the task it waits on, as
65
+ * `after`. A later run of the plan reads it back off the newest
66
+ * ended record, stopped or done, and matches each task by its text
67
+ * and ordinal.
68
+ * The field is additive, as `steps` is: a record carries no
69
+ * `decisions` key until a change hands a list, an empty list is
70
+ * written without the key, and
71
+ * {@link sessionDecisions} reads such a record as holding none. A
72
+ * change's list replaces the stored one whole
73
+ * ({@link SessionChange}'s `decisions`); every other write keeps it.
74
+ *
75
+ * ## Steps
76
+ *
77
+ * A {@link SessionStep} is one suite run the runner made for the run:
78
+ *
79
+ * - `kind`: one of {@link SESSION_STEP_KINDS}, `baseline` at the plan's
80
+ * first dispatch, `task` after a task commits, `stage` after a stage's
81
+ * last task, `pre-wrap-up` before the wrap-up.
82
+ * - `scope`: `affected`, `module` or `full` (`TEST_SCOPES`,
83
+ * `core/plan/declaration.ts`), or the list of files and folders run, which
84
+ * may be empty.
85
+ * - `reason`: optional, why the step ran at that scope, one of
86
+ * {@link SESSION_STEP_REASONS}: `declared` for the task's own `tests=`
87
+ * declaration, `trigger` for a full-suite trigger, `fallback` for the
88
+ * `--changed` run taken when no `Owns:` folder narrows it,
89
+ * `no-module-tests` for a `tests=module` task whose module holds no
90
+ * test file, and `stage` for a stage step's own scope. It is written
91
+ * as its own key right after `scope`. A step written by rafa 0.35.0
92
+ * or earlier carries no `reason` key and reads as having none; a
93
+ * value outside the set is left out on read, never refused, as an
94
+ * unknown `phase` is.
95
+ * - `command`: the argv spawned, `bun` first, never empty.
96
+ * - `exitCode`: the command's exit code, a whole number.
97
+ * - `summary`: Bun's `Ran N tests across M files.` line, or null when it
98
+ * printed none.
99
+ * - `failures`: the failing tests, each a file and a full test name
100
+ * (`SuiteFailure`, `suite-failure.ts`).
101
+ * - `newFailures`: those of `failures` that are new against the
102
+ * baseline; every one of them is also in `failures`, compared by file
103
+ * and name. A new failure also carries `errorLines`, after its
104
+ * `name`: the first lines of the error Bun printed for it
105
+ * (`suite/failure-lines.ts`), which the JUnit report does not hold.
106
+ * The key is left out of a failure Bun printed nothing for and of
107
+ * every step written before it; a value that is no non-empty list of
108
+ * strings is left out on read, never refused, as an unknown `reason`
109
+ * is.
110
+ * - `stepOnly`: only on a step that reran its newly red test files
111
+ * alone and found some green (`start/suite-retake-alone.ts`), each a
112
+ * {@link SessionStepOnly}: the file, the names of its tests that were
113
+ * red in the step, the error lines Bun printed for the first of
114
+ * them, its `position` in the order its `bun test` process ran the
115
+ * files, counted from 1, or null when that order did not hold it, and
116
+ * `before`, the few files run just before it, in run order. Such a
117
+ * file's tests stay in `failures` and are left out of `newFailures`:
118
+ * they were red only in the step's own file order. The key is
119
+ * written right after `newFailures`. Every other step carries no
120
+ * `stepOnly` key, and an entry outside that shape is left out on
121
+ * read, never refused, as an unknown `reason` is.
122
+ * - `outsideCase`: only on a step whose run exited nonzero and listed
123
+ * no failing case (`start/suite-outside-case.ts`), `true`, with
124
+ * `outputTail` right after it: the last non-blank lines of that
125
+ * run's stderr (`SuiteResult.outputTail`, `suite/run.ts`), which is
126
+ * the evidence of a run the JUnit report says nothing about; the
127
+ * list is empty when the run printed none. The pair is written
128
+ * after `stepOnly`. Every other step carries neither key, so it is
129
+ * written byte for byte as it was before them. On read an
130
+ * `outsideCase` that is not `true` is left out with the tail, never
131
+ * refused, as an unknown `reason` is, and a tail that is no list of
132
+ * strings reads as empty.
133
+ * - `interrupted`: only on a step the runner read as a stop rather than
134
+ * a suite's answer, `true`: its `bun test` ended on SIGINT, or the
135
+ * runner received SIGINT while it ran (`start/suite-step.ts`). Its
136
+ * failures are what Bun had reported by then, and none of them is new.
137
+ * Every other step carries no `interrupted` key at all, never one set
138
+ * to false, so it is written byte for byte as it was before the field.
139
+ *
140
+ * ## One session is one `loop start`
141
+ *
142
+ * A session's id is new for every run, and a rerun of a plan never takes
143
+ * over an earlier session's record. The preflight's run id is the session
144
+ * id, and the store refuses a second preflight write under a run id it
145
+ * already holds (`effort/store/preflight.ts`). Measured on 2026-09-15: the
146
+ * second `writePreflightChecks` under one id threw
147
+ * `UNIQUE constraint failed: preflight.run_id, preflight.position`, while
148
+ * a write under another id went in. A rerun reusing its plan's session
149
+ * would halt on that refusal whenever the plan names a prerequisite. So a
150
+ * plan run three times on its branch leaves three records.
151
+ *
152
+ * ## The state a record reads as
153
+ *
154
+ * A record stored as `running` or `paused` whose pid is gone reads as
155
+ * `stopped` ({@link readState}): the run ended without writing its end, as
156
+ * a killed process does. `stopped` and `done` read as stored. A pid is
157
+ * probed with signal 0, which sends nothing: a process that is gone
158
+ * answers `ESRCH`, and one that exists under another user `EPERM`, which
159
+ * reads as alive. A pid the system has handed to an unrelated process
160
+ * since reads as alive too, so such a record keeps refusing its plan until
161
+ * it is deleted; the refusal names its file.
162
+ *
163
+ * ## Uniqueness over plan, session and branch
164
+ *
165
+ * Two records name the same plan when both carry a stub and the stubs are
166
+ * equal, or neither carries one and the paths are equal
167
+ * ({@link samePlan}). A stub names the plan wherever its file sits, as
168
+ * effort attribution reads it. {@link sessionConflicts} refuses a run over
169
+ * each record of its plan that:
170
+ *
171
+ * - **names another branch**, in whatever state: a plan that has a
172
+ * branch does not create another. A revised plan is a new stub, and a
173
+ * new stub has no record.
174
+ * - **names the run's branch and reads `running` or `paused`**: that
175
+ * session is running the plan, and a second run on the same tracker
176
+ * would dispatch its tasks twice.
177
+ *
178
+ * A record of the plan on the run's branch that reads `stopped` or `done`
179
+ * refuses nothing. The rerun is a session of its own under the plan and
180
+ * branch that match, which is the one way a plan runs twice.
181
+ *
182
+ * ## Writes
183
+ *
184
+ * Every write lands whole: the text goes to a temporary file beside the
185
+ * record, whose name does not end in `.json`, and is then linked into
186
+ * place for a new record or renamed over an existing one. The link fails
187
+ * when the name is taken, so {@link beginSession} never overwrites a
188
+ * record. A record is checked as it would be read before it is written,
189
+ * so nothing this module writes is one it refuses to read.
190
+ *
191
+ * {@link beginSession} reads the records and writes the new one with no
192
+ * lock between the two, so two runs of one plan started within that
193
+ * window both go ahead. The loop is single-thread until phase 6, and the
194
+ * window is one directory read.
195
+ *
196
+ * {@link updateSession} reads the stored record again and changes only
197
+ * the fields it is handed, so a state another process wrote, as
198
+ * `loop pause` writes `paused`, survives the loop writing its running
199
+ * task. A key this module does not know is dropped by that write.
200
+ *
201
+ * A change handed `onlyFrom` is refused with {@link SessionStateError},
202
+ * writing nothing, when the state stored is none of those. `loop pause`
203
+ * moves only a `running` record and `loop resume` only a `paused` one, so
204
+ * a run that writes its end between such a command's read and its write
205
+ * keeps that end. The window left is the one between this module's own
206
+ * read and its rename, which holds no lock either.
207
+ *
208
+ * ## Reading a record
209
+ *
210
+ * The record's field readers and {@link parseSessionRecord} live in
211
+ * `session-record-parse.ts`, beside this file, whose note lists what is
212
+ * refused on read. This module re-exports what a caller reads from
213
+ * there, so `./sessions.js` stays the one import a record's reader
214
+ * needs: `isPositiveWhole` and `RECORD_EXTENSION` among them, which
215
+ * `src/stretch/folder.ts` and `src/cleanup/runs.ts` read.
216
+ */
217
+ import type { HopRecord } from './hop-record.js';
218
+ import type { SessionPhase, SessionState, SessionStepKind, SessionStepReason } from './session-record-parse.js';
219
+ import type { SuiteFailure } from './suite-failure.js';
220
+ import type { TestScope } from '../plan/declaration.js';
221
+ export type { SessionPhase, SessionState, SessionStepKind, SessionStepReason, } from './session-record-parse.js';
222
+ export { isPositiveWhole, isSessionId, parseSessionRecord, RECORD_EXTENSION, SESSION_DECISION_STRATEGIES, SESSION_PHASES, SESSION_STATES, SESSION_STEP_KINDS, SESSION_STEP_REASONS, SessionRecordError, sessionDecisions, sessionPhase, sessionSteps, } from './session-record-parse.js';
223
+ /** The directory the records are kept in, under a project's `.rafa/`. */
224
+ export declare const RUNS_DIR = "runs";
225
+ /** The task a session is running. */
226
+ export interface SessionTask {
227
+ /** The task's line in the tracker, counted from 1. */
228
+ readonly line: number;
229
+ /** The task's sentence, its routing declaration left out. */
230
+ readonly text: string;
231
+ }
232
+ /** One test file red in a step and green when run alone. See the module note, "Steps". */
233
+ export interface SessionStepOnly {
234
+ /** The test file, relative to the checkout. */
235
+ readonly file: string;
236
+ /** The full names of its tests that were red in the step. */
237
+ readonly tests: readonly string[];
238
+ /** The error lines Bun printed for the first of them; none when it printed none. */
239
+ readonly errorLines: readonly string[];
240
+ /** Its place, from 1, in the order its `bun test` process ran the files; null when that order did not hold it. */
241
+ readonly position: number | null;
242
+ /** The files run just before it, in run order. */
243
+ readonly before: readonly string[];
244
+ }
245
+ /** One suite run the runner recorded for a session. See the module note. */
246
+ export interface SessionStep {
247
+ readonly kind: SessionStepKind;
248
+ /** A named scope, or the files and folders run. */
249
+ readonly scope: TestScope | readonly string[];
250
+ /** Why the step ran at {@link SessionStep.scope}; left out of a step from rafa 0.35.0 or earlier. See the module note. */
251
+ readonly reason?: SessionStepReason;
252
+ /** The argv spawned, `bun` first. */
253
+ readonly command: readonly string[];
254
+ readonly exitCode: number;
255
+ /** Bun's summary line, or null when it printed none. */
256
+ readonly summary: string | null;
257
+ readonly failures: readonly SuiteFailure[];
258
+ /** Those of {@link SessionStep.failures} new against the baseline. */
259
+ readonly newFailures: readonly SuiteFailure[];
260
+ /** The files red in the step and green alone; left out of a step with none. See the module note. */
261
+ readonly stepOnly?: readonly SessionStepOnly[];
262
+ /** `true` on a step whose run exited nonzero and listed no failing case; left out of every other. See the module note. */
263
+ readonly outsideCase?: true;
264
+ /** The last lines of that run's stderr; only beside {@link SessionStep.outsideCase}. */
265
+ readonly outputTail?: readonly string[];
266
+ /** `true` on a step read as a stop on SIGINT; left out of every other. See the module note. */
267
+ readonly interrupted?: true;
268
+ }
269
+ /** A task a saved decision names: its zero-based tracker line and its text. */
270
+ export interface SessionTaskRef {
271
+ /** Counted from 0, as `TaskInfo.lineNum`; a hint, the text and the ordinal being the key. */
272
+ readonly lineNum: number;
273
+ /** The task's text, its blocker comment and declaration off. */
274
+ readonly task: string;
275
+ /**
276
+ * Which copy of `task` it is among the tracker's task lines, ticked
277
+ * ones included, counted from 1 in tracker order. Left out of a record
278
+ * written before the field, which reads as 1.
279
+ */
280
+ readonly ordinal?: number;
281
+ }
282
+ /** One task a `--continue` run passes over, as its record saves it. See the module note. */
283
+ export interface SessionDecision {
284
+ readonly task: SessionTaskRef;
285
+ readonly strategy: 'jump' | 'defer';
286
+ readonly reason: string;
287
+ /** On a defer alone: the task it waits on. */
288
+ readonly after?: SessionTaskRef;
289
+ }
290
+ /** One `loop start` run, as its record holds it. See the module note. */
291
+ export interface SessionRecord {
292
+ readonly sessionId: string;
293
+ readonly planStub: string | null;
294
+ readonly plan: string;
295
+ readonly branch: string;
296
+ readonly pid: number;
297
+ readonly startedAt: string;
298
+ readonly state: SessionState;
299
+ readonly task: SessionTask | null;
300
+ /** Where the run is; left out of a record from before the field. Read it with {@link sessionPhase}. */
301
+ readonly phase?: SessionPhase;
302
+ /** The away hop the run was started under; left out of every other run's record. See the module note. */
303
+ readonly hop?: HopRecord;
304
+ /** The linked worktree the run's checkout is; left out of a run in the main checkout. See the module note. */
305
+ readonly worktree?: string;
306
+ /** The suite steps recorded, oldest first; left out while there are none. Read it with {@link sessionSteps}. */
307
+ readonly steps?: readonly SessionStep[];
308
+ /** The pass-over list saved; left out while there is none. Read it with {@link sessionDecisions}. */
309
+ readonly decisions?: readonly SessionDecision[];
310
+ }
311
+ /** What a new record is made from; it opens `running`, with no task, no phase, no step and no decision. */
312
+ export type SessionDraft = Omit<SessionRecord, 'state' | 'task' | 'phase' | 'steps' | 'decisions'>;
313
+ /** What {@link updateSession} changes. A field left out keeps its stored value. */
314
+ export interface SessionChange {
315
+ readonly state?: SessionState;
316
+ readonly task?: SessionTask | null;
317
+ /** The phase the run is now in. */
318
+ readonly phase?: SessionPhase;
319
+ /** A step appended after the stored ones. */
320
+ readonly appendStep?: SessionStep;
321
+ /** The pass-over list, replacing the stored one whole; an empty one drops the key. */
322
+ readonly decisions?: readonly SessionDecision[];
323
+ /**
324
+ * The stored states the change acts on. Any other refuses it with
325
+ * {@link SessionStateError}, writing nothing. Every state when left out.
326
+ */
327
+ readonly onlyFrom?: readonly SessionState[];
328
+ }
329
+ /** Answers whether a process with this pid exists. */
330
+ export type PidProbe = (pid: number) => boolean;
331
+ /** The seams a read goes through. */
332
+ export interface SessionReadSeams {
333
+ /** Whether a pid is alive. {@link isPidAlive} when left out. */
334
+ readonly isAlive?: PidProbe;
335
+ }
336
+ /** Why a record of the plan refuses a run: its branch, or its live session. */
337
+ export type SessionConflictReason = 'branch' | 'live';
338
+ /** A record refusing a run, and why. */
339
+ export interface SessionConflict {
340
+ readonly reason: SessionConflictReason;
341
+ readonly record: SessionRecord;
342
+ }
343
+ /** A run {@link beginSession} refused, over the records that refuse it. */
344
+ export declare class SessionConflictError extends Error {
345
+ readonly conflicts: readonly SessionConflict[];
346
+ constructor(conflicts: readonly SessionConflict[]);
347
+ }
348
+ /** A change {@link updateSession} refused: the record stores a state the change does not act on. */
349
+ export declare class SessionStateError extends Error {
350
+ /** The record's path. */
351
+ readonly file: string;
352
+ /** The state the record stores. */
353
+ readonly state: SessionState;
354
+ constructor(file: string, state: SessionState, onlyFrom: readonly SessionState[]);
355
+ }
356
+ /** `<root>/.rafa/runs`. */
357
+ export declare function runsDir(root: string): string;
358
+ /** `<root>/.rafa/runs/<sessionId>.json`. Throws on an id {@link isSessionId} refuses. */
359
+ export declare function sessionFilePath(root: string, sessionId: string): string;
360
+ /** The system's code on a thrown error, such as `ENOENT`, or null when it carries none. */
361
+ export declare function errorCode(error: unknown): string | null;
362
+ /**
363
+ * Whether a process with this pid exists, by signal 0; false for a pid
364
+ * that is no positive whole number, which is never signalled. See the
365
+ * module note.
366
+ */
367
+ export declare function isPidAlive(pid: number): boolean;
368
+ /** The state a record reads as: `running` or `paused` with its pid gone reads `stopped`. */
369
+ export declare function readState(record: SessionRecord, isAlive?: PidProbe): SessionState;
370
+ /**
371
+ * Every record under `<root>/.rafa/runs/`, each with the state it reads as
372
+ * ({@link readState}), oldest first. Throws {@link SessionRecordError} for
373
+ * the first file that holds no record.
374
+ */
375
+ export declare function readSessions(root: string, seams?: SessionReadSeams): readonly SessionRecord[];
376
+ /**
377
+ * The record of `sessionId` under `<root>/.rafa/runs/`, with the state it
378
+ * reads as ({@link readState}). Throws {@link SessionRecordError} when its
379
+ * file is not there or holds no record, and the error
380
+ * {@link sessionFilePath} throws for an id it refuses.
381
+ */
382
+ export declare function readSession(root: string, sessionId: string, seams?: SessionReadSeams): SessionRecord;
383
+ /** Whether two records name the same plan. See the module note. */
384
+ export declare function samePlan(a: Pick<SessionRecord, 'planStub' | 'plan'>, b: Pick<SessionRecord, 'planStub' | 'plan'>): boolean;
385
+ /**
386
+ * The records refusing a run of `candidate`'s plan on `candidate`'s
387
+ * branch, in the order handed in. `records` carry the state they read as,
388
+ * as {@link readSessions} answers them. See the module note.
389
+ */
390
+ export declare function sessionConflicts(records: readonly SessionRecord[], candidate: Pick<SessionRecord, 'planStub' | 'plan' | 'branch'>): readonly SessionConflict[];
391
+ /**
392
+ * Opens a session: refuses it with {@link SessionConflictError} when a
393
+ * record of its plan does ({@link sessionConflicts}), and otherwise writes
394
+ * its record, `running` with no task, and answers it. Throws
395
+ * {@link SessionRecordError} when a record cannot be read, and the
396
+ * system's error when the new one cannot be written, its id's file
397
+ * already existing included.
398
+ */
399
+ export declare function beginSession(root: string, draft: SessionDraft, seams?: SessionReadSeams): SessionRecord;
400
+ /**
401
+ * Changes the stored record of a session, reading it again first, and
402
+ * answers the record written; a change's `appendStep` goes after the
403
+ * stored steps. Throws {@link SessionRecordError} when the
404
+ * record cannot be read or the change would make it one that cannot be,
405
+ * and {@link SessionStateError} when it stores a state the change's
406
+ * `onlyFrom` leaves out.
407
+ */
408
+ export declare function updateSession(root: string, sessionId: string, change: SessionChange): SessionRecord;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The record of one failing test, as `src/suite/run.ts` reads it from a
3
+ * JUnit report and a session record keeps it among a suite step's
4
+ * failures (`sessions.ts`).
5
+ *
6
+ * It sits in core so `sessions.ts` reads it without importing above
7
+ * core; `src/suite/run.ts` re-exports it under the same name.
8
+ */
9
+ /** One failing test: its file, relative to the run's directory, and its full name. */
10
+ export interface SuiteFailure {
11
+ readonly file: string;
12
+ /** Its describe names, outermost first, and its own, joined by ` > `. */
13
+ readonly name: string;
14
+ /**
15
+ * The first line of its JUnit failure's message; absent when the
16
+ * report gave none. Compared by `src/triage/inherited.ts` when present to
17
+ * identify inherited failures.
18
+ */
19
+ readonly message?: string;
20
+ /**
21
+ * The first lines of the error Bun printed for it on stderr, trimmed;
22
+ * absent when it printed none. See the module note of `src/suite/run.ts`.
23
+ */
24
+ readonly errorLines?: readonly string[];
25
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The Output this process writes through, and the mode it renders in.
3
+ *
4
+ * A function that prints asks {@link activeOutput} for the output rather
5
+ * than taking one as an argument, as `src/start/stamp.ts` holds a run's
6
+ * plan stub: an `Output` threaded through every function that prints
7
+ * would add an argument to call sites that have nothing to say about
8
+ * output. The output is held in two places, and the readers answer the
9
+ * first that has one.
10
+ *
11
+ * ## The scope
12
+ *
13
+ * {@link runWithActiveOutput} runs a function with an output and a mode
14
+ * in an async scope of its own, an `AsyncLocalStorage`
15
+ * (`node:async_hooks`). Inside that function, and in everything it
16
+ * awaits or starts, the readers answer that output and that mode; once
17
+ * the function ends, however it ends, nothing of them is left. The
18
+ * dispatcher runs every command this way (`src/cli/dispatch.ts`), with
19
+ * the command's context output in the invocation's mode.
20
+ *
21
+ * A scope belongs to one run, so runs need not nest to stay apart. Two
22
+ * runs that overlap in one process, started together and awaited with
23
+ * `Promise.all`, each write through their own output whichever ends
24
+ * first. A run started inside another reads the inner output inside and
25
+ * the outer one after. The module-level value below keeps overlapping
26
+ * runs apart only while they nest: set and put back in a `finally` it is
27
+ * a stack, and two that end out of order leave the output of one of them
28
+ * for the rest of the process (#927).
29
+ *
30
+ * ## The module-level value
31
+ *
32
+ * Outside every scope the readers answer one module-level value, which
33
+ * no module but a test and its fixtures sets. Until one sets another, it
34
+ * is the `text` adapter at verbosity 0 writing to `process.stdout`.
35
+ * {@link setActiveOutput} with `null` puts that default back. The
36
+ * default is made once, when this module is first imported, so
37
+ * {@link activeOutput} answers the same object on every read while
38
+ * nothing is set. It holds `process.stdout` itself and calls its `write`
39
+ * on each line, so a spy set on `process.stdout.write` after the import
40
+ * sees every line.
41
+ *
42
+ * ## A set made inside a scope
43
+ *
44
+ * The readers prefer the scope, and {@link setActiveOutput} writes the
45
+ * module-level value alone, inside a scope as outside one. So a set made
46
+ * inside a scope changes nothing that scope reads, and is what the
47
+ * readers answer once the scope has ended. A scope's output cannot be
48
+ * replaced from inside it.
49
+ *
50
+ * ## What reads outside the scope
51
+ *
52
+ * A scope follows the awaits and the callbacks started inside it. A
53
+ * callback the runtime calls from elsewhere does not carry it. Measured
54
+ * under bun 1.3.14, three listeners registered inside a scope read no
55
+ * store: a `process.on` signal listener when the signal arrived, an
56
+ * abort listener that handler fired, and an abort listener an
57
+ * `AbortSignal.timeout` fired. A timer, a child process's `data`
58
+ * listener, a `readline` `line` listener and the code after an await
59
+ * such a handler settled all read the scope's. A reader outside the
60
+ * scope writes through the module-level value; one that needs the
61
+ * scope's output reads it where the listener is registered, or is
62
+ * wrapped with `AsyncResource.bind` there.
63
+ *
64
+ * ## The mode
65
+ *
66
+ * An Output carries no mode of its own, and two writers need one, so the
67
+ * mode is held beside the output, in the scope and at module level
68
+ * alike. `core/loop/claude.ts` echoes a session's stdout as the bytes it
69
+ * wrote in `text` mode and as one `log` event per line in `json` mode.
70
+ * `start/dispatch.ts` emits a `step` event per task in `json` mode
71
+ * alone, where the `text` adapter would render it as a `step: ` line
72
+ * beside the line announcing the task. The dispatcher hands each
73
+ * invocation's mode to its scope with its output. The default, and an
74
+ * output set at module level with no mode, render in `text`.
75
+ *
76
+ * Bun runs every test file in one process, and the module-level value is
77
+ * module state: a case that sets an output sets `null` after it, or each
78
+ * file bun runs later writes through that case's output, in that case's
79
+ * mode.
80
+ */
81
+ import type { Output } from '../../ports/index.js';
82
+ import type { OutputMode } from '../config/config-sections.js';
83
+ /**
84
+ * Runs `run` with `output` as the active output, rendering in `mode`, in
85
+ * an async scope of its own, and answers what `run` answers. Both are
86
+ * read inside `run` and whatever it awaits, and nowhere after it; see
87
+ * the module note.
88
+ */
89
+ export declare function runWithActiveOutput<T>(output: Output, mode: OutputMode, run: () => T): T;
90
+ /**
91
+ * Sets the module-level output and the mode it renders in, `text`
92
+ * unless one is named. `null` puts the default back, in `text` whatever
93
+ * mode is named. Inside a scope it changes nothing that scope reads; see
94
+ * the module note.
95
+ */
96
+ export declare function setActiveOutput(output: Output | null, mode?: OutputMode): void;
97
+ /** The output to write through: the one of the scope this is read in, else the module-level one. */
98
+ export declare function activeOutput(): Output;
99
+ /** The mode the active output renders in, read as {@link activeOutput} is; see the module note. */
100
+ export declare function activeOutputMode(): OutputMode;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Where an Output adapter writes.
3
+ *
4
+ * Copied from open-tomato's `packages/shared/cli-core/src/output.ts`,
5
+ * where it is named `CliOutputStream`, at commit
6
+ * `18f94c843fff1bce65ed2245f8712ed9b64a2d51` (2026-06-25). Every output
7
+ * adapter writes through it, `text.ts` beside this module and `json.ts`
8
+ * and `events.ts` under `src/adapters/output/`, so it lives in none of
9
+ * them.
10
+ *
11
+ * The source spells `write` as a method. It is a function-typed property
12
+ * here, as every port function is in `packages/rafa/src/ports/index.ts`, whose note
13
+ * gives the reason. `process.stdout` is assignable to it, which
14
+ * `src/adapter-registry.ts` relies on when a context names no stream.
15
+ */
16
+ /** A stream an Output adapter writes whole lines to. */
17
+ export interface OutputStream {
18
+ /** Writes one chunk. What it answers is never read. */
19
+ write: (chunk: string) => unknown;
20
+ }