@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,732 @@
1
+ /**
2
+ * The package's ports: the seven interfaces core defines for an adapter
3
+ * to implement, declared as types with no implementation behind any of
4
+ * them.
5
+ *
6
+ * The spec's `exports` map names this module's build as the `./ports`
7
+ * subpath, declared in phase 0 and filled in phase 1. Phase 1 puts one
8
+ * adapter per port under `src/adapters/` and lets an add-on register
9
+ * more by name; core imports an add-on only from a `path` source the
10
+ * config's `allowList:` names (`src/modules/load.ts`). What those adapters
11
+ * implement is declared here first, so a service (the orchestrator's
12
+ * worker pool, a Drizzle-backed store) can type an implementation
13
+ * against the subpath without importing the CLI.
14
+ *
15
+ * ## Types, and nothing else
16
+ *
17
+ * The entry exports no runtime value, so importing it runs nothing and
18
+ * adds nothing to a service's bundle. Its imports are `import type`
19
+ * lines, which the bundler erases, so naming {@link GeneratedPlan}'s
20
+ * review type pulls `./spec-review.ts` into no bundle. That
21
+ * keeps three things a port could otherwise carry out of this module:
22
+ *
23
+ * - The store's key projection, `EFFORT_KEY_PROJECTIONS`, which is a
24
+ * value. It stays on `./store`, beside the backends that read it.
25
+ * - The Tracker source's `as const` tuples. Each union they derived
26
+ * is spelled out as a literal union instead, keeping the members and
27
+ * dropping the tuple, so nothing here enumerates them at runtime.
28
+ * - The number core compares a port version against. The modules spec
29
+ * gives every port a version and has core refuse a module whose
30
+ * version it no longer serves. Each version is declared here as a
31
+ * number literal type, gathered in {@link PortVersions} under the
32
+ * port types the adapter registry keys by. The number core compares
33
+ * is `PORT_VERSIONS` in `packages/rafa/src/core/adapters/registry.ts`, held equal to
34
+ * these literals there.
35
+ *
36
+ * ## Where each port comes from
37
+ *
38
+ * - {@link Tracker}: copied from open-tomato's
39
+ * `packages/shared/issue-tracker/src/port.ts` at commit
40
+ * `45aaab563e5b4f4e6258e19ebf752b7cfeb67bf0` (2026-08-05). The phase
41
+ * 1 spec asks for a copy rather than a dependency, with its source
42
+ * commit recorded, so the monorepo can later be pointed at this
43
+ * export and the port does not live twice. Member types and their
44
+ * TSDoc are carried over as written, so they speak of open-tomato's
45
+ * OPT numbers, CLI and ledger, with three exceptions: {@link TrackerKind}
46
+ * is opened, {@link IssueType} gains `epic` and `spec`, and the note on
47
+ * `IssueRef.externalId` names the `local` adapter's issue number
48
+ * where the source named a file path. Four members are rafa's own
49
+ * and absent from the source, all optional: the `openIssues`
50
+ * reading, with the {@link OpenIssue} it answers, the `filedIssues`
51
+ * reading, with the {@link FiledIssue} it answers, and the `editable`
52
+ * and `edit` pair `rafa issue edit` writes through, with the
53
+ * {@link EditableIssue} and {@link IssueEdit} they take and answer. Left
54
+ * out: `BOARD_COLUMNS`, `CLOSED_STATES` and `GITHUB_ISSUE_TYPES`,
55
+ * which are values and the GitHub adapter's projections, and
56
+ * `LedgerEntry`, the local ledger's line. rafa ports no OPT ledger,
57
+ * so the degradation chain (`src/adapters/tracker/resolve.ts`)
58
+ * arrived without it, and the `local` adapter records the reason the
59
+ * chain hands it in each issue file instead.
60
+ * - {@link Store}: the effort store port phase 0 already built, in
61
+ * `packages/rafa/src/effort/store/types.ts`, re-exported under the port's name
62
+ * rather than declared again. `Store` is `EffortStore`, and the rows
63
+ * and results it is typed against come with it.
64
+ * - {@link Learning}: `distributed-learning-library.md`, "rafa's
65
+ * Learning port", with the records it passes taken from that spec's
66
+ * package shape. {@link InstinctRecord}, {@link SyncPayload},
67
+ * {@link BlessedBundle}, {@link MergeRule}, {@link MergeDecision} and
68
+ * {@link MergeResult} are declared in `./learning.ts` and re-exported
69
+ * here under the same names. The learning library declares its own
70
+ * six in `src/learning/types.ts`, since neither side may import the
71
+ * other, and `src/tests/learning-contract.test.ts` holds each pair
72
+ * mutually assignable.
73
+ * - {@link Output}: open-tomato's `packages/shared/cli-core`, the
74
+ * events from `src/events.ts` at
75
+ * `2b00895eaec9edcdce02839831429da97441c0f5` and the interface from
76
+ * `src/output.ts` at `18f94c843fff1bce65ed2245f8712ed9b64a2d51`
77
+ * (both 2026-06-25), where it is named `CliOutput`. The source's
78
+ * `text` and `json` factories are the two adapters phase 1 ports.
79
+ * - {@link Planner}: no spec gives it a signature. The phase 1 table
80
+ * names its core adapter as today's `plan.ts`, so the request and the
81
+ * answer are read off what `rafa plan` takes and leaves behind.
82
+ * - {@link Sync}: issue #323, "choose how a project's effort store
83
+ * travels". The spec gives its two directions, push (send the rows
84
+ * this store wrote) and pull (merge the rows another device wrote),
85
+ * and the five strategies `effort.sync` selects between. Every
86
+ * adapter's pull runs `mergeStore` (`packages/rafa/src/effort/store/merge-store.ts`),
87
+ * so {@link SyncPullResult} carries that merge's own result type,
88
+ * imported as `StoreMergeResult` because the Learning port's
89
+ * `MergeResult` already holds the name here. The request types are
90
+ * read off what the `file` strategy needs: the directory
91
+ * `rafa effort copy` writes into, and the `effort.sqlite` and
92
+ * `--dry-run` that `rafa effort import` hands the merge.
93
+ *
94
+ * ## Property signatures
95
+ *
96
+ * Every function a port declares is a function-typed property, never a
97
+ * method, as the store port's already are. TypeScript compares a
98
+ * method's parameters bivariantly and a property's strictly, so an
99
+ * adapter accepting less than the port hands it, such as a `transition`
100
+ * taking only `done`, compiles against a method and is refused against a
101
+ * property. The Tracker source and the Learning spec both spell methods.
102
+ * This is the one change the Learning port makes to a member's type; the
103
+ * Tracker copy makes one more, opening {@link TrackerKind}.
104
+ *
105
+ * ## Left open for phase 1
106
+ *
107
+ * - {@link MergeResult}: the spec names `discarded` and describes the
108
+ * rest without naming it. `decisions`, {@link MergeDecision} and the
109
+ * {@link MergeRule} names are the learning library's, one rule per
110
+ * row of the spec's merge table plus the record no existing trigger
111
+ * shares.
112
+ * - {@link PlanRequest} and {@link GeneratedPlan} carry what `rafa
113
+ * plan` has today. The webhook adapter phase 6 adds lands a plan on a
114
+ * branch, which may widen the answer.
115
+ */
116
+ import type { BlessedBundle, MergeResult, SyncPayload } from './learning.js';
117
+ import type { LogLevel } from './log-level.js';
118
+ import type { SpecReviewReading } from './spec-review.js';
119
+ import type { MergeResult as StoreMergeResult } from '../effort/store/merge-store.js';
120
+ export type { LogLevel } from './log-level.js';
121
+ export type { BlessedBundle, InstinctRecord, MergeDecision, MergeResult, MergeRule, SyncPayload, } from './learning.js';
122
+ export type { AppendResult, CommitEffortRow, EffortKeyProjections, EffortRow, EffortRowByKind, EffortRowKind, EffortStore as Store, SessionEffortRow, SessionMode, } from '../effort/store/types.js';
123
+ /** The version of the {@link Tracker} port this entry declares. */
124
+ export type TrackerPortVersion = 1;
125
+ /** The version of the {@link Store} port this entry declares. */
126
+ export type StorePortVersion = 1;
127
+ /** The version of the {@link Learning} port this entry declares. */
128
+ export type LearningPortVersion = 1;
129
+ /** The version of the {@link Output} port this entry declares. */
130
+ export type OutputPortVersion = 1;
131
+ /** The version of the {@link Planner} port this entry declares. */
132
+ export type PlannerPortVersion = 1;
133
+ /** The version of the {@link Sync} port this entry declares. */
134
+ export type SyncPortVersion = 1;
135
+ /**
136
+ * Each port's version, under the port type the adapter registry keys its
137
+ * adapters by. An adapter states the version of its port it implements,
138
+ * and the registry refuses one core does not serve.
139
+ */
140
+ export interface PortVersions {
141
+ tracker: TrackerPortVersion;
142
+ store: StorePortVersion;
143
+ learning: LearningPortVersion;
144
+ output: OutputPortVersion;
145
+ planner: PlannerPortVersion;
146
+ sync: SyncPortVersion;
147
+ logger: LoggerPortVersion;
148
+ }
149
+ /**
150
+ * The seven port types: `tracker`, `store`, `learning`, `output`,
151
+ * `planner`, `sync` and `logger`.
152
+ */
153
+ export type PortType = keyof PortVersions;
154
+ /**
155
+ * The platform a tracker adapter projects issues onto, named as the
156
+ * adapter registry keys it.
157
+ *
158
+ * The source closes this over `github`, `linear` and `local`. The copy
159
+ * keeps those three and admits any other name, because an add-on
160
+ * registers a tracker under a kind core has never heard of (`obsidian`
161
+ * in the phase 1 table), and the config accepts any kind name for
162
+ * `tracker.default` and `tracker.fallback`. Whether a kind has an
163
+ * adapter is the registry's answer, not this type's.
164
+ *
165
+ * `string & {}` is a string the checker does not merge with the three
166
+ * named members. Under `| string` the union reduces to `string`, and the
167
+ * three names are gone from the type.
168
+ */
169
+ export type TrackerKind = 'github' | 'linear' | 'local' | (string & {});
170
+ /**
171
+ * What an issue is for. `epic` and `spec` are rafa's own members, absent
172
+ * from the source: an issue labelled `type:epic` groups the issues
173
+ * carrying its `epic:<slug>` label, and one labelled `type:spec` holds a
174
+ * spec on the board.
175
+ */
176
+ export type IssueType = 'code' | 'bug' | 'spike' | 'adr' | 'chore' | 'package-api' | 'epic' | 'spec';
177
+ /** How urgent an issue is, once triage has set it. */
178
+ export type IssuePriority = 'urgent' | 'high' | 'medium' | 'low';
179
+ /**
180
+ * One state per board column, plus `cancelled`, spelled in lifecycle
181
+ * order.
182
+ */
183
+ export type IssueState = 'backlog' | 'todo' | 'in-progress' | 'in-review' | 'done' | 'released' | 'cancelled';
184
+ /** A resolved pointer to an issue on some tracker. */
185
+ export interface IssueRef {
186
+ /** Internal OPT number — stable across trackers. */
187
+ opt: number;
188
+ kind: TrackerKind;
189
+ /**
190
+ * GitHub issue number as a string, Linear uuid, or the number the `local`
191
+ * adapter gave the issue under `.rafa/issues/`, as a string.
192
+ */
193
+ externalId: string;
194
+ url: string | null;
195
+ /**
196
+ * Module the issue belongs to, when the caller already knows it (e.g. from
197
+ * a ledger entry). `transition()` on the GitHub adapter needs this to
198
+ * resolve which project board an issue lives on; when it's absent, the
199
+ * adapter falls back to reading the `module:` label via `get()` — an extra
200
+ * `gh issue view` round trip that a caller who already has this for free
201
+ * (the CLI, reading the ledger) can skip by setting it here.
202
+ */
203
+ module?: string;
204
+ /**
205
+ * `owner/name` for github refs. Absent means the configured default repo
206
+ * (`github.repo`) — set only when the issue's module routes to a repo of
207
+ * its own (`modules.*.repo`), so the common case stays untagged.
208
+ */
209
+ repo?: string;
210
+ /**
211
+ * Set when the issue was filed but a follow-up step failed — today, Projects
212
+ * v2 board placement. The issue exists and MUST still be recorded in the
213
+ * ledger; the caller is responsible for surfacing this to the user.
214
+ */
215
+ warning?: string;
216
+ }
217
+ /** Everything needed to create an issue, before any platform is chosen. */
218
+ export interface IssueDraft {
219
+ opt: number;
220
+ title: string;
221
+ body: string;
222
+ type: IssueType;
223
+ module: string;
224
+ /** null means triage has not set one yet — the adapter applies needs-triage. */
225
+ priority: IssuePriority | null;
226
+ /** Project/board name, or null when the tracker has no project concept. */
227
+ project: string | null;
228
+ /** OPT numbers this issue is blocked by. */
229
+ blockedBy: readonly number[];
230
+ /**
231
+ * rafa's own field, absent from the source: true when the issue waits
232
+ * on other issues of the board, which the `github` adapter files as
233
+ * the board's `spec:blocked` label (`SPEC_BLOCKED_LABEL` in
234
+ * `src/board/blocked.ts`) and the `local` adapter records in the issue
235
+ * file. Absent or false files no such mark. Not {@link blockedBy},
236
+ * which names OPT ledger numbers.
237
+ */
238
+ specBlocked?: boolean;
239
+ /**
240
+ * rafa's own field, absent from the source: the cause code a bug was
241
+ * filed with (`context/error-codes.md`). The `github` adapter files it
242
+ * as the label `cause:<code>` and the `local` adapter records it in the
243
+ * issue file. Absent files no such mark.
244
+ */
245
+ cause?: string;
246
+ }
247
+ /** An issue as a tracker holds it: the draft, where it lives, its state. */
248
+ export interface Issue extends IssueDraft {
249
+ ref: IssueRef;
250
+ state: IssueState;
251
+ }
252
+ /** What `find` narrows by; an absent field narrows by nothing. */
253
+ export interface IssueQuery {
254
+ module?: string;
255
+ text?: string;
256
+ type?: IssueType;
257
+ state?: IssueState;
258
+ limit?: number;
259
+ }
260
+ /**
261
+ * One open issue as `openIssues` answers it: where it lives, the title
262
+ * and body it holds, and when it was created, so a caller comparing
263
+ * issue text, or asking which issues were filed after a time, reads
264
+ * every open issue in one call rather than a `get` for each.
265
+ */
266
+ export interface OpenIssue {
267
+ ref: IssueRef;
268
+ title: string;
269
+ body: string;
270
+ /**
271
+ * When the issue was created, as an ISO 8601 date and time with its
272
+ * offset, or null when the tracker does not say: it holds no creation
273
+ * time for the issue, or one that does not read as a time. {@link Issue}
274
+ * carries no such time.
275
+ */
276
+ createdAt: string | null;
277
+ }
278
+ /**
279
+ * One issue as `filedIssues` answers it: where it lives, the title and
280
+ * body it holds, and when it was filed. rafa's own type, absent from the
281
+ * source.
282
+ *
283
+ * Unlike {@link OpenIssue}, its creation time is never null: a listing
284
+ * by filing time cannot place an undated issue against its cut-off, so
285
+ * an adapter never answers one.
286
+ */
287
+ export interface FiledIssue {
288
+ readonly ref: IssueRef;
289
+ readonly title: string;
290
+ /** The body as the tracker holds it, unnormalised. */
291
+ readonly body: string;
292
+ /**
293
+ * When the issue was created, as an ISO 8601 date and time with its
294
+ * offset.
295
+ */
296
+ readonly createdAt: string;
297
+ }
298
+ /**
299
+ * One issue as `editable` answers it: what `rafa issue edit` weighs
300
+ * before it writes, read in one call. rafa's own type, absent from the
301
+ * source.
302
+ *
303
+ * {@link Issue} carries neither labels nor an author, and the edit's
304
+ * gates read both: the author for whose issue it is, the labels for
305
+ * whether a plan or a loop is building from it.
306
+ */
307
+ export interface EditableIssue {
308
+ readonly ref: IssueRef;
309
+ readonly title: string;
310
+ /** The body as the tracker holds it, unnormalised. */
311
+ readonly body: string;
312
+ /**
313
+ * False when the tracker holds the issue closed: `done`, `released`
314
+ * or `cancelled`, as `openIssues` reads them.
315
+ */
316
+ readonly open: boolean;
317
+ /**
318
+ * The issue's labels, by name, in the order the tracker answers them.
319
+ * Empty on a tracker that holds no labels.
320
+ */
321
+ readonly labels: readonly string[];
322
+ /**
323
+ * The login of the account that opened the issue, or the empty string
324
+ * on a tracker that holds no accounts, such as `local`.
325
+ */
326
+ readonly author: string;
327
+ /**
328
+ * The priority the tracker holds for the issue, or null when triage
329
+ * has set none, as `get` answers {@link IssueDraft.priority}. Absent on
330
+ * a tracker whose `edit` writes no priority: a caller reads it here
331
+ * before it hands {@link IssueEdit} a priority, and hands none to a
332
+ * tracker that answers none.
333
+ */
334
+ readonly priority?: IssuePriority | null;
335
+ }
336
+ /**
337
+ * What one `edit` writes: a new body, a new title, a new priority, or
338
+ * any of the three together. Whatever it leaves out stays as the
339
+ * tracker holds it. A body is written whole: an append is the caller's,
340
+ * which hands the old body with its update below it. A priority is
341
+ * written in place of the one the issue held, and takes the issue out
342
+ * of triage: after it the tracker holds that priority and no other, and
343
+ * no mark that triage has yet to set one. An edit never returns an
344
+ * issue to triage, so the member holds no null. The union holds an edit
345
+ * to at least one of the three.
346
+ */
347
+ export type IssueEdit = {
348
+ readonly body: string;
349
+ readonly title?: string;
350
+ readonly priority?: IssuePriority;
351
+ } | {
352
+ readonly body?: string;
353
+ readonly title: string;
354
+ readonly priority?: IssuePriority;
355
+ } | {
356
+ readonly body?: string;
357
+ readonly title?: string;
358
+ readonly priority: IssuePriority;
359
+ };
360
+ /** What a tracker's platform supports beyond issues themselves. */
361
+ export interface TrackerCapabilities {
362
+ /** Supports project/board grouping (GitHub Projects v2, Linear projects). */
363
+ projects: boolean;
364
+ /** Supports custom single-select fields (priority as a board field). */
365
+ customFields: boolean;
366
+ /** Supports native issue types (GitHub org issue types). */
367
+ issueTypes: boolean;
368
+ }
369
+ /** A preflight's answer: reachable, or the reason it is not. */
370
+ export type PreflightResult = {
371
+ ok: true;
372
+ } | {
373
+ ok: false;
374
+ reason: string;
375
+ };
376
+ /**
377
+ * Outcome of a `transition()` call. Mirrors `IssueRef.warning`'s convention
378
+ * rather than inventing a new shape: the transition's primary write (the
379
+ * tracker's own open/closed or workflow state) succeeded — otherwise
380
+ * `transition()` throws — but a secondary write (the GitHub adapter's board
381
+ * column) may not have landed. `warning` set means exactly that: the caller
382
+ * MUST NOT treat this as a full success, and — for callers that persist a
383
+ * state claim of their own (the ledger) — must not persist that claim when a
384
+ * board-shaped warning says the board never received it.
385
+ */
386
+ export interface TransitionResult {
387
+ warning?: string;
388
+ }
389
+ /**
390
+ * An issue tracker: where `loop start` files what a task report's
391
+ * out-of-scope bugs name, through its triage (`start/triage.ts`, over
392
+ * `triage/triage.ts`). A report's blockers are written onto its task's
393
+ * tracker line instead.
394
+ */
395
+ export interface Tracker {
396
+ readonly kind: TrackerKind;
397
+ capabilities: () => TrackerCapabilities;
398
+ /** Cheap reachability/auth probe. Must not throw — returns a reason instead. */
399
+ preflight: () => Promise<PreflightResult>;
400
+ find: (query: IssueQuery) => Promise<IssueRef[]>;
401
+ get: (ref: IssueRef) => Promise<Issue>;
402
+ create: (draft: IssueDraft) => Promise<IssueRef>;
403
+ comment: (ref: IssueRef, body: string) => Promise<void>;
404
+ transition: (ref: IssueRef, state: IssueState) => Promise<TransitionResult>;
405
+ /**
406
+ * The open issues of `type`, each with its title, its body and the
407
+ * time it was created, in the order the adapter lists them. Open is
408
+ * every state the tracker does not hold closed: `done`, `released`
409
+ * and `cancelled` are closed, the rest open. rafa's own member, absent
410
+ * from the source, and optional: a tracker without it reads no open
411
+ * issues, and a caller that needs them goes without.
412
+ */
413
+ openIssues?: (type: IssueType) => Promise<OpenIssue[]>;
414
+ /**
415
+ * The issues of `type` filed after `since`, in any state: one closed
416
+ * since it was filed is listed beside the open ones, so a caller asking
417
+ * what was filed after a time does not lose an issue closed before it
418
+ * asks. Each carries its title, its body and the time it was created,
419
+ * in the order the adapter lists them. An issue whose creation time the
420
+ * tracker does not hold, or holds unreadable, is never answered: a
421
+ * listing by filing time cannot place it against `since`. rafa's own
422
+ * member, absent from the source, and optional: a tracker without it
423
+ * reads no filed issues, and a caller that needs them goes without.
424
+ */
425
+ filedIssues?: (type: IssueType, since: Date) => Promise<FiledIssue[]>;
426
+ /**
427
+ * The issue's title, body, open state, labels and author, in one
428
+ * read, with its priority on a tracker whose `edit` writes one.
429
+ * Rejects for a ref the tracker does not hold. rafa's own member,
430
+ * absent from the source, and optional with {@link edit}: a tracker
431
+ * without the two cannot have a body edited, and `rafa issue edit`
432
+ * refuses with a line naming the tracker's kind.
433
+ */
434
+ editable?: (ref: IssueRef) => Promise<EditableIssue>;
435
+ /**
436
+ * Writes the body, the title and the priority that `change` names,
437
+ * leaving the rest of the issue as it was, so that `editable` and
438
+ * `get` read the written text and priority back unchanged. Rejects
439
+ * when the tracker refuses the write or does not hold the ref.
440
+ * Optional with {@link editable}.
441
+ */
442
+ edit?: (ref: IssueRef, change: IssueEdit) => Promise<void>;
443
+ }
444
+ /**
445
+ * The learning tunnel: findings go in as instincts, blessed instincts
446
+ * come out for dispatch to inject. The modules spec closes it to third
447
+ * parties; there is core's `local` adapter and a first-party `remote`
448
+ * one, never a second source of blessed instincts.
449
+ */
450
+ export interface Learning {
451
+ /** Merges one source's instincts into what the adapter holds. */
452
+ push: (payload: SyncPayload) => Promise<MergeResult>;
453
+ /** The instincts currently blessed. */
454
+ pullBlessed: () => Promise<BlessedBundle>;
455
+ /** Flags one instinct by id, keeping it out of every later bundle. */
456
+ flag: (id: string, reason: string) => Promise<void>;
457
+ }
458
+ /** A command began. `ts` is an ISO 8601 timestamp on every event. */
459
+ export interface CliEventStart {
460
+ type: 'start';
461
+ command: string;
462
+ ts: string;
463
+ }
464
+ /** A named step of the command began. */
465
+ export interface CliEventStep {
466
+ type: 'step';
467
+ name: string;
468
+ ts: string;
469
+ }
470
+ /** One message at one level. */
471
+ export interface CliEventLog {
472
+ type: 'log';
473
+ level: 'debug' | 'info' | 'warn' | 'error';
474
+ message: string;
475
+ /** What a logger knew beyond the message: module, action, code, hint and the like. */
476
+ fields?: Readonly<Record<string, unknown>>;
477
+ ts: string;
478
+ }
479
+ /**
480
+ * The command's outcome: its data when it succeeded, its error when not.
481
+ *
482
+ * `error.code` is the dispatcher's own code (`command_exit`,
483
+ * `no_project`, …) and `error.message` the failure's words. The four
484
+ * optional members say what the failure was beside them (#952): `cause`
485
+ * is a `<family>:<leaf>` cause code on every failure the dispatcher
486
+ * writes; `hint` is a coded error's next step, when it has one; `module`
487
+ * and `action` name where a coded error was thrown, or the command that
488
+ * ran for an uncoded failure of one.
489
+ */
490
+ export interface CliEventResult {
491
+ type: 'result';
492
+ ok: boolean;
493
+ data?: unknown;
494
+ error?: {
495
+ code: string;
496
+ message: string;
497
+ cause?: string;
498
+ hint?: string;
499
+ module?: string;
500
+ action?: string;
501
+ };
502
+ ts: string;
503
+ }
504
+ /**
505
+ * One named event of a long-running command, such as a loop's task
506
+ * ending. `summary` is the one line the events output prints; `data`
507
+ * holds the same facts as fields for a reader of json.
508
+ */
509
+ export interface CliEventNamed {
510
+ type: 'event';
511
+ name: string;
512
+ summary: string;
513
+ data: Readonly<Record<string, unknown>>;
514
+ ts: string;
515
+ }
516
+ /** Every event an output renders, closed over its five kinds. */
517
+ export type CliEvent = CliEventStart | CliEventStep | CliEventLog | CliEventResult | CliEventNamed;
518
+ /**
519
+ * Where a command's output goes: a terminal's text, or NDJSON events a
520
+ * service or the TUI reads. Phase 1 routes the sibling's `console.log`
521
+ * calls through it.
522
+ */
523
+ export interface Output {
524
+ /** A message at `info`. */
525
+ info: (message: string) => void;
526
+ /** A message at `warn`. */
527
+ warn: (message: string) => void;
528
+ /** A message at `error`. */
529
+ error: (message: string) => void;
530
+ /** A message at `debug`. */
531
+ debug: (message: string) => void;
532
+ /** One event, rendered as the adapter renders its kind. */
533
+ emit: (event: CliEvent) => void;
534
+ /** The command's answer. */
535
+ result: (payload: unknown) => void;
536
+ }
537
+ /** One request to a service and what came back, as metadata only: never a body. */
538
+ export interface ApiExchange {
539
+ /** The service called, such as `hub`. */
540
+ service: string;
541
+ /** The request method, such as `GET`. */
542
+ method: string;
543
+ /** The request URL; an adapter filters it before writing it. */
544
+ url: string;
545
+ /** The response status; absent when no response arrived. */
546
+ status?: number;
547
+ /** How long the exchange took, in milliseconds. */
548
+ durationMs?: number;
549
+ /** The size of the request body, in bytes. */
550
+ requestBytes?: number;
551
+ /** The size of the response body, in bytes. */
552
+ responseBytes?: number;
553
+ /** The id the service gave the request, when it gives one. */
554
+ requestId?: string;
555
+ /** Response headers; an adapter writes only the ones its allow-list names. */
556
+ headers?: Readonly<Record<string, string>>;
557
+ }
558
+ /** One diagnostic line, with what is known about where it comes from. */
559
+ export interface LogEntry {
560
+ /** How loud the entry is, or `api`. */
561
+ level: LogLevel;
562
+ /** What happened, as one sentence. */
563
+ message: string;
564
+ /** The module writing it, as a child logger binds it. */
565
+ module?: string;
566
+ /** The action or step running. */
567
+ action?: string;
568
+ /** A cause code, `<family>:<leaf>` (`packages/rafa/src/errors/codes.ts`). */
569
+ code?: string;
570
+ /** The recommended next action. */
571
+ hint?: string;
572
+ /** Anything else worth keeping, for an adapter that stores fields. */
573
+ data?: Readonly<Record<string, unknown>>;
574
+ /** The error behind the entry, kept whole so its own stack survives. */
575
+ error?: unknown;
576
+ /** The exchange an `api` entry is about. */
577
+ api?: ApiExchange;
578
+ }
579
+ /** What a child logger adds to every entry it writes. */
580
+ export interface LogBindings {
581
+ /** The module every entry of the child comes from. */
582
+ module?: string;
583
+ /** The action every entry of the child belongs to. */
584
+ action?: string;
585
+ }
586
+ /**
587
+ * Where diagnostics go: errors, warnings, debug lines and `api`
588
+ * exchanges, each with its module, cause code and hint when known. A
589
+ * command's own text and its result stay on {@link Output}.
590
+ */
591
+ export interface Logger {
592
+ /** Writes `entry`, unless its level is off for its module. */
593
+ log: (entry: LogEntry) => void;
594
+ /** A logger adding `bindings` to every entry; an entry's own fields win. */
595
+ child: (bindings: LogBindings) => Logger;
596
+ /** Whether an entry at `level` from `module` would be written. */
597
+ enabled: (level: LogLevel, module?: string) => boolean;
598
+ }
599
+ /** The version of the {@link Logger} port this entry declares. */
600
+ export type LoggerPortVersion = 1;
601
+ /**
602
+ * One plan to generate, from one spec. The adapter is bound to a
603
+ * repository when it is made, as a store is opened on a root, so the
604
+ * paths here are that repository's.
605
+ */
606
+ export interface PlanRequest {
607
+ /** The spec, as `--spec=` names it: repository-relative or absolute. */
608
+ specPath: string;
609
+ /** The plan's stub. `rafa plan` defaults it to the spec's basename. */
610
+ stub: string;
611
+ }
612
+ /**
613
+ * Where a planner left the plan it generated: repository-relative, or
614
+ * absolute when the directory it writes plans into is.
615
+ */
616
+ export interface GeneratedPlan {
617
+ /** The plan: `PLAN-<stub>.md` in `plan.dir` under today's planner. */
618
+ planPath: string;
619
+ /**
620
+ * The setup steps the planner found no task could automate, or null
621
+ * when it wrote none: `PREREQUISITES-<stub>.md` in `plan.dir` today.
622
+ */
623
+ prerequisitesPath: string | null;
624
+ /**
625
+ * What the planner's own session said about the spec BEFORE it
626
+ * planned, read out of that session's output: the `rafa:spec-review`
627
+ * block of the readiness gate's check 3
628
+ * (`src/board/spec-review.ts`).
629
+ *
630
+ * Optional, because a planner that reads no session output has judged
631
+ * nothing and must still satisfy this port — the fixture planners the
632
+ * command's tests resolve, and the `webhook` adapter phase 6 adds.
633
+ * The `claude` adapter carries one on every plan it answers, `absent`
634
+ * when its session returned no block. Nothing here acts on it: the
635
+ * gate is `rafa plan`'s, which is what lets `--skip-review` weigh a
636
+ * verdict the planner still read.
637
+ */
638
+ review?: SpecReviewReading;
639
+ }
640
+ /**
641
+ * Generates a plan from a spec: core's `claude` adapter runs a Claude
642
+ * Code session, as `rafa plan` does, and phase 6's `webhook` adapter
643
+ * hands the spec to hyperloop's plan-to-branch.
644
+ */
645
+ export interface Planner {
646
+ /**
647
+ * Generates the plan. Rejects when none was generated: the session
648
+ * failed, or it finished without writing the plan.
649
+ */
650
+ create: (request: PlanRequest) => Promise<GeneratedPlan>;
651
+ }
652
+ /**
653
+ * Where a push leaves this store's rows.
654
+ *
655
+ * `to` is the directory the `file` strategy copies the store into, as
656
+ * `rafa effort copy --to=<dir>` names it, and null for a strategy that
657
+ * sends the rows on by itself or sends none.
658
+ */
659
+ export interface SyncPushRequest {
660
+ readonly to: string | null;
661
+ }
662
+ /**
663
+ * Where a pull reads another device's rows from, and whether it swaps
664
+ * the merge in.
665
+ */
666
+ export interface SyncPullRequest {
667
+ /**
668
+ * The other device's store file the `file` strategy merges, the
669
+ * `effort.sqlite` a copy carried, and null for a strategy that fetches
670
+ * the rows by itself or fetches none.
671
+ */
672
+ readonly from: string | null;
673
+ /**
674
+ * Build and check the merged store, then delete it rather than swap it
675
+ * in, as `mergeStore`'s own `dryRun` does.
676
+ */
677
+ readonly dryRun: boolean;
678
+ /**
679
+ * The session id of the loop run pulling, which a strategy passes on as
680
+ * `mergeStore`'s `sessionId` so the live-loop guard passes the run's own
681
+ * record; absent or null for a pull no run makes.
682
+ */
683
+ readonly sessionId?: string | null;
684
+ }
685
+ /** What a push did. */
686
+ export type SyncPushResult =
687
+ /** The strategy moves no rows: `local`. */
688
+ {
689
+ readonly status: 'nothing-to-sync';
690
+ }
691
+ /**
692
+ * The rows were sent. `path` is the file the person carries to the
693
+ * other device, the copy's `effort.sqlite` under `file`, and null for
694
+ * a strategy that delivered the rows itself.
695
+ */
696
+ | {
697
+ readonly status: 'pushed';
698
+ readonly path: string | null;
699
+ };
700
+ /** What a pull did. */
701
+ export type SyncPullResult =
702
+ /** The strategy moves no rows: `local`. */
703
+ {
704
+ readonly status: 'nothing-to-sync';
705
+ }
706
+ /**
707
+ * The rows were merged by `mergeStore`, whose result says whether the
708
+ * merge was swapped in or, under `dryRun`, built and deleted.
709
+ */
710
+ | {
711
+ readonly status: 'pulled';
712
+ readonly merge: StoreMergeResult;
713
+ };
714
+ /**
715
+ * How a project's effort store travels between devices: the strategy
716
+ * `effort.sync` selects. `local` and `file` are core's strategies and
717
+ * `git`, `service` and `p2p` modules'; the adapter registry holds core's
718
+ * as `sync/local` (`src/effort/sync/select.ts`) and `sync/file`
719
+ * (`src/effort/sync/file.ts`). Push and pull are directions,
720
+ * not strategies: in every strategy a device pushes the rows it wrote
721
+ * and pulls the rows others wrote.
722
+ */
723
+ export interface Sync {
724
+ readonly kind: 'local' | 'file' | 'git' | 'service' | 'p2p';
725
+ /** Sends the rows this store wrote. */
726
+ push: (request: SyncPushRequest) => Promise<SyncPushResult>;
727
+ /**
728
+ * Merges the rows another device wrote into this store. Rejects with
729
+ * the merge's own refusal when `mergeStore` refuses.
730
+ */
731
+ pull: (request: SyncPullRequest) => Promise<SyncPullResult>;
732
+ }