@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.
- package/README.md +255 -23
- package/dist/PROMPT.md +18 -1
- package/dist/adapters/logger/settings.d.ts +132 -0
- package/dist/bugs/closest.d.ts +19 -0
- package/dist/bugs/fenced.d.ts +8 -0
- package/dist/bugs/places.d.ts +20 -0
- package/dist/bugs/record.d.ts +145 -0
- package/dist/bugs/title.d.ts +83 -0
- package/dist/bundled/agents/doc-updater.md +17 -3
- package/dist/bundled/agents/loop-implementer.md +14 -2
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
- package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +101 -18
- package/dist/cli.js +74726 -62960
- package/dist/config/entry.d.ts +119 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.js +51 -0
- package/dist/config/shape.d.ts +232 -0
- package/dist/config-registry.d.ts +127 -0
- package/dist/config-schema-board-project.d.ts +157 -0
- package/dist/config-schema-bugs.d.ts +71 -0
- package/dist/config-schema-errors.d.ts +54 -0
- package/dist/config-schema-features.d.ts +64 -0
- package/dist/config-schema-hub.d.ts +150 -0
- package/dist/config-schema-issues.d.ts +95 -0
- package/dist/config-schema-logger.d.ts +91 -0
- package/dist/config-schema-loop-continue.d.ts +122 -0
- package/dist/config-schema-loop-retries.d.ts +94 -0
- package/dist/config-schema-release.d.ts +307 -0
- package/dist/config-schema-tests.d.ts +170 -0
- package/dist/config-schema-triage.d.ts +142 -0
- package/dist/config-schema-wrap-up.d.ts +96 -0
- package/dist/core/command/command.d.ts +228 -0
- package/dist/core/command/confirm.d.ts +67 -0
- package/dist/core/command/index.d.ts +112 -0
- package/dist/core/command/lazy.d.ts +54 -0
- package/dist/core/command/registry-types.d.ts +96 -0
- package/dist/core/command/running.d.ts +96 -0
- package/dist/core/command/spends.d.ts +39 -0
- package/dist/core/command/types.d.ts +92 -0
- package/dist/core/command/version.d.ts +4 -0
- package/dist/core/config/branch-template.d.ts +20 -0
- package/dist/core/config/config-items.d.ts +86 -0
- package/dist/core/config/config-load.d.ts +47 -0
- package/dist/core/config/config-origins.d.ts +57 -0
- package/dist/core/config/config-readers.d.ts +167 -0
- package/dist/core/config/config-schema.d.ts +207 -0
- package/dist/core/config/config-sections.d.ts +493 -0
- package/dist/core/config/config.d.ts +320 -0
- package/dist/core/config/core-section.d.ts +39 -0
- package/dist/core/config/dangerous.d.ts +58 -0
- package/dist/core/config/default-routing.d.ts +16 -0
- package/dist/core/config/index.d.ts +141 -0
- package/dist/core/config/scope.d.ts +42 -0
- package/dist/core/config/section.d.ts +206 -0
- package/dist/core/config/sections.d.ts +45 -0
- package/dist/core/config/standard-schema.d.ts +27 -0
- package/dist/core/git/commit.d.ts +232 -0
- package/dist/core/git/gh-runner.d.ts +24 -0
- package/dist/core/git/git.d.ts +22 -0
- package/dist/core/git/index.d.ts +85 -0
- package/dist/core/loop/claude.d.ts +388 -0
- package/dist/core/loop/hop-record.d.ts +52 -0
- package/dist/core/loop/index.d.ts +179 -0
- package/dist/core/loop/session-env.d.ts +20 -0
- package/dist/core/loop/session-record-parse.d.ts +63 -0
- package/dist/core/loop/sessions.d.ts +408 -0
- package/dist/core/loop/suite-failure.d.ts +25 -0
- package/dist/core/output/active.d.ts +100 -0
- package/dist/core/output/stream.d.ts +20 -0
- package/dist/core/output/text.d.ts +55 -0
- package/dist/core/plan/block-reader.d.ts +46 -0
- package/dist/core/plan/blocks.d.ts +139 -0
- package/dist/core/plan/declaration.d.ts +366 -0
- package/dist/core/plan/plan-stamp.d.ts +75 -0
- package/dist/core/runtime/identity.d.ts +21 -0
- package/dist/core/runtime/index.d.ts +46 -0
- package/dist/effort/store/bring-forward.d.ts +131 -0
- package/dist/effort/store/check-rows.d.ts +31 -0
- package/dist/effort/store/commit-stats.d.ts +42 -0
- package/dist/effort/store/copy.d.ts +35 -0
- package/dist/effort/store/development-build.d.ts +105 -0
- package/dist/effort/store/effort-levels.d.ts +13 -0
- package/dist/effort/store/findings.d.ts +214 -0
- package/dist/effort/store/fix-schema.d.ts +184 -0
- package/dist/effort/store/index.js +10 -7
- package/dist/effort/store/location.d.ts +44 -0
- package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
- package/dist/effort/store/merge-conflicts.d.ts +91 -0
- package/dist/effort/store/merge-rules.d.ts +82 -0
- package/dist/effort/store/merge-store.d.ts +211 -0
- package/dist/effort/store/merge-union.d.ts +92 -0
- package/dist/effort/store/migrate.d.ts +146 -0
- package/dist/effort/store/migrations.d.ts +92 -0
- package/dist/effort/store/ndjson-files.d.ts +114 -0
- package/dist/effort/store/origins.d.ts +116 -0
- package/dist/effort/store/plan-stubs.d.ts +79 -0
- package/dist/effort/store/rebuild-aside.d.ts +172 -0
- package/dist/effort/store/schema-plan.d.ts +102 -0
- package/dist/effort/store/session-row-types.d.ts +64 -0
- package/dist/effort/store/settings.d.ts +13 -0
- package/dist/effort/store/sqlite.d.ts +340 -0
- package/dist/effort/store/store-generation.d.ts +17 -0
- package/dist/effort/store/store-identity.d.ts +220 -0
- package/dist/effort/store/store-meta.d.ts +305 -0
- package/dist/effort/store/store-replaced.d.ts +72 -0
- package/dist/effort/store/types.d.ts +223 -0
- package/dist/errors/codes.d.ts +56 -0
- package/dist/errors/rafa-codes.d.ts +36 -0
- package/dist/index-2a7nb3an.js +125 -0
- package/dist/{index-r28fzsvc.js → index-39kbcw68.js} +350 -165
- package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
- package/dist/index-cy98y87p.js +2597 -0
- package/dist/index-gqz4jn6j.js +13447 -0
- package/dist/index-gvek4r8m.js +710 -0
- package/dist/index-j4k2vdz3.js +1125 -0
- package/dist/index-jh1r4kcn.js +1067 -0
- package/dist/{index-mgh2s9bj.js → index-ykggr043.js} +58 -455
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index.js +14715 -21516
- package/dist/learning/index.js +5 -6
- package/dist/plan/index.js +12 -11
- package/dist/plan-prompt.md +3 -1
- package/dist/ports/index.d.ts +732 -0
- package/dist/ports/learning.d.ts +101 -0
- package/dist/ports/log-level.d.ts +13 -0
- package/dist/ports/spec-review.d.ts +66 -0
- package/dist/pr/checks.d.ts +133 -0
- package/dist/pr/types.d.ts +422 -0
- package/dist/ready-4f8edezt.js +2016 -0
- package/dist/release-templates/release-bot-fragment.yml +153 -0
- package/dist/release-templates/release-publish.yml +110 -0
- package/dist/release-templates/release-settle.yml +122 -0
- package/dist/release-templates/release-tag.yml +102 -0
- package/dist/report/parse.d.ts +415 -0
- package/dist/schema/project-id.d.ts +79 -0
- package/dist/schema/tiers.d.ts +107 -0
- package/package.json +41 -3
- package/dist/index-hf2k497d.js +0 -16
- package/dist/index-nsr78ct2.js +0 -8219
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The store's own identity row: reading what `store_meta` records,
|
|
3
|
+
* minting a new origin into it on a writing open that needs one,
|
|
4
|
+
* settling on that mint which ids `store_replaced` holds, and rotating
|
|
5
|
+
* the store's generation on every writing open and on every rebuild
|
|
6
|
+
* that keeps the store's origin.
|
|
7
|
+
*
|
|
8
|
+
* ## What a writing open writes
|
|
9
|
+
*
|
|
10
|
+
* `withSqliteStore` (`sqlite.ts`) calls {@link settleStoreIdentity}
|
|
11
|
+
* after `bringForward`, so the table and its `generation` column exist,
|
|
12
|
+
* and before any caller reads or writes. `decideStoreIdentity`
|
|
13
|
+
* (`store-identity.ts`) makes the decision: a `read` open answers
|
|
14
|
+
* `none` and this module neither reads the row, observes the file nor
|
|
15
|
+
* touches the side record, so a read writes nothing. A `write` open
|
|
16
|
+
* reads the row, observes the host, the real path, the file's device
|
|
17
|
+
* and inode and the side record's generation, and mints when the row is
|
|
18
|
+
* absent, any of the three facts moved, or the row holds a generation
|
|
19
|
+
* the side record does not. A row's generation is read as NULL when its
|
|
20
|
+
* store has no `generation` column yet, and a NULL one is judged by the
|
|
21
|
+
* three facts alone.
|
|
22
|
+
*
|
|
23
|
+
* Every writing open then takes the write lock with `BEGIN IMMEDIATE`,
|
|
24
|
+
* reads the row and decides again under it, since another process may
|
|
25
|
+
* have minted or rotated the same file while this one waited, and
|
|
26
|
+
* writes a new random generation (`newGeneration`, a UUID) in that
|
|
27
|
+
* transaction: on a keep, into `store_meta.generation`; on a mint, into
|
|
28
|
+
* the new row (`id = 1`) with the new origin. Either way it then writes
|
|
29
|
+
* the same generation to the side record (`store-generation.ts`),
|
|
30
|
+
* `<real path of the store file>.generation` beside it, and commits. So
|
|
31
|
+
* a store written without a generation keeps its origin on its first
|
|
32
|
+
* write and holds one after it, two processes that open one unminted
|
|
33
|
+
* store at once record one origin, and a copy of the file, which
|
|
34
|
+
* carries the row and leaves the side record behind, mints on its first
|
|
35
|
+
* write once the original has been written since the copy was taken.
|
|
36
|
+
*
|
|
37
|
+
* The row is written first and the side record second, both before the
|
|
38
|
+
* commit, because a writer that waits for the lock decides only once it
|
|
39
|
+
* holds it and so always sees the two agree. A side record that fails
|
|
40
|
+
* to write throws and rolls the row back. A crash after the side
|
|
41
|
+
* record's rename and before the commit leaves the side record one
|
|
42
|
+
* rotation ahead of the row; the next writing open mints once, which is
|
|
43
|
+
* safe, and keeps on the write after. A side record restored from
|
|
44
|
+
* before the last write is one rotation behind and mints the same way.
|
|
45
|
+
*
|
|
46
|
+
* What the generation does not catch: a restore of the store's whole
|
|
47
|
+
* directory, the store file and its side record together, brings back a
|
|
48
|
+
* row and a side record that agree. When the file comes back at its old
|
|
49
|
+
* inode, as a `cp` over the existing file writes it, all three facts
|
|
50
|
+
* match as well and nothing mints; the merge's collision check is what
|
|
51
|
+
* catches that copy afterwards.
|
|
52
|
+
*
|
|
53
|
+
* ## The project a mint records
|
|
54
|
+
*
|
|
55
|
+
* `store_meta.project_root_commit` is `NOT NULL`. The project is read
|
|
56
|
+
* through git in the store file's directory (`readProjectIdentity`), so
|
|
57
|
+
* `<root>/.rafa/effort/` and a copy under `<root>/.rafa/scratch/` both
|
|
58
|
+
* name the project they sit in. When git finds no root commit there, a
|
|
59
|
+
* mint takes the project the row already records, since a copy moved
|
|
60
|
+
* outside its checkout is still that project's store. When neither
|
|
61
|
+
* names one, as for a store under `tmpdir()` outside any repository
|
|
62
|
+
* with commits, nothing is written and the outcome is `no-project`: the
|
|
63
|
+
* store stays unminted, no generation is written to the row or the side
|
|
64
|
+
* record, and the next writing open asks again. That costs the git
|
|
65
|
+
* reads on each such open and loses no origin, since an unminted store
|
|
66
|
+
* stamps none. Git is only asked on a mint, never on a write that keeps
|
|
67
|
+
* the origin, and before the lock is taken: under it, git is asked only
|
|
68
|
+
* for a store that came due a mint while this open waited.
|
|
69
|
+
*
|
|
70
|
+
* ## The ids a mint replaces
|
|
71
|
+
*
|
|
72
|
+
* `store_meta` holds one row, so a mint writes the new origin over the
|
|
73
|
+
* old one. A claim names the id of the store that made it, and a store
|
|
74
|
+
* file given a new device or inode under its own host and path is still
|
|
75
|
+
* this device's store. So a mint settles `store_replaced`
|
|
76
|
+
* (`store-replaced.ts`) by its reasons, in the transaction that writes
|
|
77
|
+
* the new row:
|
|
78
|
+
*
|
|
79
|
+
* - `file`, `generation`, or both: the host and the path were kept.
|
|
80
|
+
* One host and one path hold one store file at a time, so this is
|
|
81
|
+
* the device's own store, moved or rolled back. The outgoing id is
|
|
82
|
+
* recorded with the time the new row takes as `minted_at`, read from
|
|
83
|
+
* the clock once, and every id recorded before it stays. `generation`
|
|
84
|
+
* alone is what a crash between the side record's rename and the
|
|
85
|
+
* commit leaves, and both together what a backup renamed back over
|
|
86
|
+
* the store after a write leaves.
|
|
87
|
+
* - `host` or `path` among them, whatever else moved: the file is a
|
|
88
|
+
* copy, and a copy carries the original's table inside it. The table
|
|
89
|
+
* is emptied and the outgoing id is not recorded, so a copy inherits
|
|
90
|
+
* no claim.
|
|
91
|
+
* - `unminted`: no id is outgoing, and nothing is written.
|
|
92
|
+
*
|
|
93
|
+
* A write that keeps the origin, and a mint that knows no project and
|
|
94
|
+
* writes nothing, leave the table as it was. The record and the row
|
|
95
|
+
* commit together: a side record that fails to write rolls both back.
|
|
96
|
+
*
|
|
97
|
+
* `withSqliteStore` settles after `bringForward`, which has applied
|
|
98
|
+
* `store-replaced` by then, so the table is there on every writing open.
|
|
99
|
+
* A mint that keeps the host and the path over a store without it
|
|
100
|
+
* throws SQLite's `no such table: store_replaced` and writes nothing,
|
|
101
|
+
* since an id that cannot be kept is not reported as kept. A mint an
|
|
102
|
+
* older runtime made recorded nothing, and neither did any mint from
|
|
103
|
+
* before the table: those ids are in no store.
|
|
104
|
+
*
|
|
105
|
+
* ## Device and inode as SQLite integers
|
|
106
|
+
*
|
|
107
|
+
* Both are unsigned 64-bit values, and SQLite's INTEGER is signed.
|
|
108
|
+
* Measured with Bun 1.4.2: binding `2n ** 63n` raises nothing and
|
|
109
|
+
* stores `-9223372036854775808`, and a plain read would answer a
|
|
110
|
+
* rounded number. So {@link toStoredInteger} writes the two's
|
|
111
|
+
* complement explicitly, the row is read through `CAST(… AS TEXT)`,
|
|
112
|
+
* and {@link fromStoredInteger} turns it back unsigned. Every value
|
|
113
|
+
* below 2^63 is written and read as itself.
|
|
114
|
+
*
|
|
115
|
+
* ## Carrying the identity onto a file that replaces the store
|
|
116
|
+
*
|
|
117
|
+
* A rebuild beside the store (`rebuild-aside.ts`) builds a parallel file
|
|
118
|
+
* that holds the store's own `store_meta` row, since `store_meta` is
|
|
119
|
+
* `local` in `MERGE_RULES`, and then renames it over the store. The
|
|
120
|
+
* renamed file has the store's path and host and the parallel file's
|
|
121
|
+
* inode, so its next writing open would mint although it is the same
|
|
122
|
+
* store. {@link carryStoreIdentity} runs before that rename. It reads
|
|
123
|
+
* the live file's row and asks `decideStoreIdentity` what a write to the
|
|
124
|
+
* live file would do. Only on a keep does it write the parallel file's
|
|
125
|
+
* own device and inode, and a new generation (`newGeneration`), into the
|
|
126
|
+
* parallel file's row, and it answers that generation in `carried`.
|
|
127
|
+
* `swapIn` then writes it to the live store's side record immediately
|
|
128
|
+
* before the rename. After the rename the parallel file sits at the live
|
|
129
|
+
* path beside that side record, so all three facts and the generation
|
|
130
|
+
* agree and the next writing open keeps the origin.
|
|
131
|
+
*
|
|
132
|
+
* The generation rotates on the carry, rather than staying the one the
|
|
133
|
+
* live side record held, so that nothing the rebuild leaves behind
|
|
134
|
+
* matches the side record afterwards. The backup and the replaced live
|
|
135
|
+
* file both hold the old generation in their rows: either renamed back
|
|
136
|
+
* over the store mints on its first write, even when it lands on an
|
|
137
|
+
* inode number the host reused. A rename that fails after the side
|
|
138
|
+
* record was written leaves the live file holding the old generation
|
|
139
|
+
* beside the new one, so it mints once on its next write, the safe
|
|
140
|
+
* side, and keeps on the write after. A parallel file whose
|
|
141
|
+
* `store_meta` has no `generation` column, as for a store written
|
|
142
|
+
* before `store-meta-generation`, is given its device and inode only;
|
|
143
|
+
* `carried` then holds a null generation and nothing is written to the
|
|
144
|
+
* side record, and the renamed file is judged by the three facts alone.
|
|
145
|
+
*
|
|
146
|
+
* Every other answer carries nothing, so the renamed file mints on its
|
|
147
|
+
* next write, which is the safe side. That covers a live file with no
|
|
148
|
+
* `store_meta` table or no row (an unminted store has no origin to
|
|
149
|
+
* carry), a live file that would mint itself (another host, path,
|
|
150
|
+
* device or inode, or a row generation its side record does not hold),
|
|
151
|
+
* and a parallel file whose row is not the live store's. On each of
|
|
152
|
+
* them the parallel file's row is still given a fresh random generation
|
|
153
|
+
* (`newGeneration`), where it has a row and a `generation` column, and
|
|
154
|
+
* the answer reports it in `spoiled`; nothing else in the row changes
|
|
155
|
+
* and nothing is written to the side record. Without it, the renamed
|
|
156
|
+
* file would hold the generation the side record holds, since the
|
|
157
|
+
* parallel file is built from the live one, and would be judged by the
|
|
158
|
+
* three facts alone; a file that lands at the path on the inode number
|
|
159
|
+
* its row names, which a host that reuses a freed number can give it,
|
|
160
|
+
* would then keep an origin that was due a mint. With the fresh
|
|
161
|
+
* generation the side record does not hold it, and the renamed file
|
|
162
|
+
* mints however the inode numbers fall. The row is never given a null
|
|
163
|
+
* generation, which would be judged by the three facts alone as well. A
|
|
164
|
+
* parallel file with no row or no `generation` column is left
|
|
165
|
+
* untouched, and `spoiled` is null. A write to the
|
|
166
|
+
* live store between the side record's write and the rename rotates the
|
|
167
|
+
* side record past the generation carried, so the renamed file mints as
|
|
168
|
+
* well. A write between the carry and the side record's write is
|
|
169
|
+
* overwritten in the side record by the generation carried, so the
|
|
170
|
+
* renamed file keeps the origin; the rows such a write adds were never
|
|
171
|
+
* in the parallel file, as for any write after the rebuild began.
|
|
172
|
+
*/
|
|
173
|
+
import type { StoreAccess } from './schema-plan.js';
|
|
174
|
+
import type { MintReason, ProjectIdentity, RecordedIdentity } from './store-identity.js';
|
|
175
|
+
import { Database } from 'bun:sqlite';
|
|
176
|
+
/**
|
|
177
|
+
* What a store open reads the machine and the clock through. Every
|
|
178
|
+
* field falls back to the real one; tests pass the host id and the
|
|
179
|
+
* project and never read this machine's.
|
|
180
|
+
*/
|
|
181
|
+
export interface StoreIdentitySeams {
|
|
182
|
+
/** The host id. `readHostId` when absent. */
|
|
183
|
+
readonly readHostId?: () => string;
|
|
184
|
+
/** The project of the repository holding `dir`. `readProjectIdentity` when absent. */
|
|
185
|
+
readonly readProject?: (dir: string) => ProjectIdentity;
|
|
186
|
+
/** A new store id. `crypto.randomUUID` when absent. */
|
|
187
|
+
readonly newStoreId?: () => string;
|
|
188
|
+
/** A new generation. `crypto.randomUUID` when absent. */
|
|
189
|
+
readonly newGeneration?: () => string;
|
|
190
|
+
/** The time a mint records, in the row and for the id it replaces. `new Date()` when absent. */
|
|
191
|
+
readonly now?: () => Date;
|
|
192
|
+
}
|
|
193
|
+
/** The whole `store_meta` row. */
|
|
194
|
+
export interface StoreMeta extends RecordedIdentity {
|
|
195
|
+
/** The project's smallest root commit. */
|
|
196
|
+
readonly projectRootCommit: string;
|
|
197
|
+
/** Its normalised `origin` remote, or null when it had none. */
|
|
198
|
+
readonly projectRemote: string | null;
|
|
199
|
+
/** The device the file was on, unsigned. */
|
|
200
|
+
readonly fileDev: bigint;
|
|
201
|
+
/** The file's inode, unsigned. */
|
|
202
|
+
readonly fileIno: bigint;
|
|
203
|
+
/** When the origin was minted, as an ISO string. */
|
|
204
|
+
readonly mintedAt: string;
|
|
205
|
+
}
|
|
206
|
+
/** What {@link settleStoreIdentity} did. */
|
|
207
|
+
export type IdentityOutcome =
|
|
208
|
+
/** A read: nothing was read, observed or written. */
|
|
209
|
+
{
|
|
210
|
+
readonly action: 'none';
|
|
211
|
+
}
|
|
212
|
+
/** A write to the store its origin was minted for: only a new generation written. */
|
|
213
|
+
| {
|
|
214
|
+
readonly action: 'keep';
|
|
215
|
+
readonly storeId: string;
|
|
216
|
+
}
|
|
217
|
+
/** A write that recorded the new origin `storeId` and a new generation, and settled `store_replaced`. */
|
|
218
|
+
| {
|
|
219
|
+
readonly action: 'mint';
|
|
220
|
+
readonly storeId: string;
|
|
221
|
+
readonly reasons: readonly MintReason[];
|
|
222
|
+
}
|
|
223
|
+
/** A write that was due a mint and wrote nothing, knowing no project. */
|
|
224
|
+
| {
|
|
225
|
+
readonly action: 'no-project';
|
|
226
|
+
readonly reasons: readonly MintReason[];
|
|
227
|
+
};
|
|
228
|
+
/** An unsigned 64-bit value as the signed INTEGER SQLite stores. */
|
|
229
|
+
export declare function toStoredInteger(value: bigint): bigint;
|
|
230
|
+
/** The unsigned value {@link toStoredInteger} stored as `text`. */
|
|
231
|
+
export declare function fromStoredInteger(text: string): bigint;
|
|
232
|
+
/**
|
|
233
|
+
* The `store_meta` row of the store `db` holds, or null when it holds
|
|
234
|
+
* none. The generation is null for a row whose store has no
|
|
235
|
+
* `generation` column yet, as for one written before it.
|
|
236
|
+
*/
|
|
237
|
+
export declare function readStoreMeta(db: Database): StoreMeta | null;
|
|
238
|
+
/**
|
|
239
|
+
* Settles the identity of the store at `path`, open as `db`, for an open
|
|
240
|
+
* with `access`: nothing on a read; on a write, under the write lock,
|
|
241
|
+
* the recorded origin kept for the same file or a new one minted for an
|
|
242
|
+
* unminted store or a copy, and a new generation written to the row and
|
|
243
|
+
* the side record either way. A mint over a recorded origin also
|
|
244
|
+
* settles `store_replaced`: the outgoing id recorded when the host and
|
|
245
|
+
* the path were kept, the table emptied when either moved. See the
|
|
246
|
+
* module note.
|
|
247
|
+
*/
|
|
248
|
+
export declare function settleStoreIdentity(db: Database, path: string, access: StoreAccess, seams?: StoreIdentitySeams): IdentityOutcome;
|
|
249
|
+
/**
|
|
250
|
+
* What {@link carryStoreIdentity} did. Every answer but `carried` reports
|
|
251
|
+
* in `spoiled` the fresh generation it wrote into the parallel file's
|
|
252
|
+
* row, the only thing it wrote there, or null when that file holds no
|
|
253
|
+
* `store_meta` row with a `generation` column and nothing was written.
|
|
254
|
+
*/
|
|
255
|
+
export type CarryOutcome =
|
|
256
|
+
/** The live file holds no `store_meta` table. */
|
|
257
|
+
{
|
|
258
|
+
readonly action: 'no-table';
|
|
259
|
+
readonly spoiled: string | null;
|
|
260
|
+
}
|
|
261
|
+
/** The live file's `store_meta` holds no row: an unminted store. */
|
|
262
|
+
| {
|
|
263
|
+
readonly action: 'no-row';
|
|
264
|
+
readonly spoiled: string | null;
|
|
265
|
+
}
|
|
266
|
+
/** A write to the live file would mint, for `reasons`. */
|
|
267
|
+
| {
|
|
268
|
+
readonly action: 'mint';
|
|
269
|
+
readonly reasons: readonly MintReason[];
|
|
270
|
+
readonly spoiled: string | null;
|
|
271
|
+
}
|
|
272
|
+
/** The parallel file holds no `store_meta` row of the live store's id. */
|
|
273
|
+
| {
|
|
274
|
+
readonly action: 'other-row';
|
|
275
|
+
readonly spoiled: string | null;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The parallel file's row now records its own device and inode and the
|
|
279
|
+
* new `generation`, which the side record has yet to be given; null when
|
|
280
|
+
* its `store_meta` has no `generation` column, so none was written.
|
|
281
|
+
*/
|
|
282
|
+
| {
|
|
283
|
+
readonly action: 'carried';
|
|
284
|
+
readonly storeId: string;
|
|
285
|
+
readonly generation: string | null;
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* Carries the identity of the live store at `livePath` onto the file at
|
|
289
|
+
* `parallelPath` that is about to be renamed over it. When
|
|
290
|
+
* `decideStoreIdentity('write', …)` answers keep for the live file and
|
|
291
|
+
* the parallel file holds a row of the live store's id, the parallel
|
|
292
|
+
* file's own device and inode and a new generation from
|
|
293
|
+
* `seams.newGeneration` (`crypto.randomUUID` when absent) are written
|
|
294
|
+
* into that row, and its store id, path, host and every other column are
|
|
295
|
+
* kept. The answer `carried` holds that generation, which the caller
|
|
296
|
+
* writes to the live store's side record immediately before the rename
|
|
297
|
+
* (`swapIn`, `rebuild-aside.ts`); this function writes no side record.
|
|
298
|
+
* On every other answer a new generation from the same seam is written
|
|
299
|
+
* into the parallel file's row, where it has one with a `generation`
|
|
300
|
+
* column, and nothing else, and the answer reports it in `spoiled`. The
|
|
301
|
+
* host id is read through `seams.readHostId` (`readHostId` when absent).
|
|
302
|
+
* Throws the filesystem's own error, which names the path, when either
|
|
303
|
+
* file does not exist. See the module note.
|
|
304
|
+
*/
|
|
305
|
+
export declare function carryStoreIdentity(livePath: string, parallelPath: string, seams?: StoreIdentitySeams): CarryOutcome;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ids a store carried before its present one: reading the table
|
|
3
|
+
* `store_replaced`, recording one id into it with the time it was
|
|
4
|
+
* replaced, and emptying it.
|
|
5
|
+
*
|
|
6
|
+
* ## What the table holds
|
|
7
|
+
*
|
|
8
|
+
* Migration `store-replaced` creates `store_replaced`, one row per id:
|
|
9
|
+
* `store_id`, its primary key, and `replaced_at`, an ISO string. Like
|
|
10
|
+
* `store_meta` it is SQLite-only, outside the port's row map, and
|
|
11
|
+
* `local` in `MERGE_RULES`, so it never leaves the machine. A store
|
|
12
|
+
* file found under the host and path its `store_meta` row names is this
|
|
13
|
+
* device's own store, moved or rolled back, and a claim made under an
|
|
14
|
+
* id held here is still this device's.
|
|
15
|
+
*
|
|
16
|
+
* ## What this module does not decide
|
|
17
|
+
*
|
|
18
|
+
* It reads and writes rows and nothing else. Which mint records the
|
|
19
|
+
* outgoing id and which one empties the table is the caller's decision,
|
|
20
|
+
* as is the transaction either write runs in: every function takes the
|
|
21
|
+
* open handle and begins none of its own, so a write made inside the
|
|
22
|
+
* caller's transaction commits or rolls back with it. No function opens
|
|
23
|
+
* a store; a caller passes the handle `withSqliteStore` (`sqlite.ts`)
|
|
24
|
+
* gave it.
|
|
25
|
+
*
|
|
26
|
+
* ## A store without the table
|
|
27
|
+
*
|
|
28
|
+
* A store an older runtime left has no `store_replaced`.
|
|
29
|
+
* {@link readReplacedStoreIds}
|
|
30
|
+
* answers none for it, the same answer as for an empty table, and
|
|
31
|
+
* creates nothing. {@link clearReplacedStoreIds} writes nothing to it,
|
|
32
|
+
* since a table that does not exist already holds no id.
|
|
33
|
+
* {@link recordReplacedStoreId} is the one that throws there, with
|
|
34
|
+
* SQLite's own `no such table: store_replaced`: an id that cannot be
|
|
35
|
+
* kept is not reported as kept.
|
|
36
|
+
*
|
|
37
|
+
* ## An id recorded twice
|
|
38
|
+
*
|
|
39
|
+
* The id is the primary key. Recording one the table already holds
|
|
40
|
+
* adds no row and keeps the time first recorded, because that is when
|
|
41
|
+
* the store stopped carrying the id.
|
|
42
|
+
*/
|
|
43
|
+
import type { Database } from 'bun:sqlite';
|
|
44
|
+
/** One id a store carried before its present one. */
|
|
45
|
+
export interface ReplacedStoreId {
|
|
46
|
+
/** The id the store carried. */
|
|
47
|
+
readonly storeId: string;
|
|
48
|
+
/** When the store stopped carrying it, as an ISO string. */
|
|
49
|
+
readonly replacedAt: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The ids the store `db` holds replaced, each with the time it was
|
|
53
|
+
* replaced, the earliest first and by id within one instant. Answers
|
|
54
|
+
* none for a store without the table and for one with it empty, and
|
|
55
|
+
* writes nothing either way.
|
|
56
|
+
*/
|
|
57
|
+
export declare function readReplacedStoreIds(db: Database): readonly ReplacedStoreId[];
|
|
58
|
+
/**
|
|
59
|
+
* Records that the store `db` holds stopped carrying `storeId` at
|
|
60
|
+
* `replacedAt`, written as an ISO string. An id the table already holds
|
|
61
|
+
* keeps its first time and adds no row. Throws, writing nothing, when
|
|
62
|
+
* the id is empty or the time is no valid date, and throws SQLite's own
|
|
63
|
+
* error when the store has no `store_replaced` table. Begins no
|
|
64
|
+
* transaction; see the module note.
|
|
65
|
+
*/
|
|
66
|
+
export declare function recordReplacedStoreId(db: Database, storeId: string, replacedAt: Date): void;
|
|
67
|
+
/**
|
|
68
|
+
* Removes every row of `store_replaced` from the store `db` holds and
|
|
69
|
+
* keeps the table. Writes nothing to a store without the table, and
|
|
70
|
+
* does not create it. Begins no transaction; see the module note.
|
|
71
|
+
*/
|
|
72
|
+
export declare function clearReplacedStoreIds(db: Database): void;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effort store's port: the contract both backends implement, and
|
|
3
|
+
* the one value they share.
|
|
4
|
+
*
|
|
5
|
+
* The sibling's store was bound to one file and one key projection per
|
|
6
|
+
* instance. This port is parameterised by row kind instead, per the
|
|
7
|
+
* spec's signature, so one store holds every kind and each call names
|
|
8
|
+
* the kind it acts on. Left there, that would reopen the failure the
|
|
9
|
+
* binding existed to prevent: a collector skipping by one projection of
|
|
10
|
+
* a row while the append dedupes by another, so work is read again or
|
|
11
|
+
* rows are duplicated, and both look like a store that is working. So
|
|
12
|
+
* the projection is not the caller's to pass. No method takes one;
|
|
13
|
+
* {@link EFFORT_KEY_PROJECTIONS} is a closed record a backend reads for
|
|
14
|
+
* `keys` and `append` alike.
|
|
15
|
+
*
|
|
16
|
+
* ## What every backend owes
|
|
17
|
+
*
|
|
18
|
+
* These are the sibling's NDJSON store's rules, carried over unchanged
|
|
19
|
+
* in behaviour, and the SQLite backend owes them too, so that nothing a
|
|
20
|
+
* caller typed against {@link EffortStore} relies on holds for one
|
|
21
|
+
* backend and not the other.
|
|
22
|
+
*
|
|
23
|
+
* - Append-only. Nothing a store holds is rewritten or removed.
|
|
24
|
+
* - `keys` is read back from the rows, never from a sidecar index,
|
|
25
|
+
* which would be a second authority free to disagree with them. A
|
|
26
|
+
* stored row with no key contributes nothing, rather than a
|
|
27
|
+
* placeholder every other keyless row would collide with.
|
|
28
|
+
* - `append` adds only the rows whose key is not already held,
|
|
29
|
+
* whether it is held in storage or earlier in the same batch.
|
|
30
|
+
* - A row with no key REFUSES the whole batch: the append throws
|
|
31
|
+
* before anything is written. Such a row could never be
|
|
32
|
+
* deduplicated, so appending it would duplicate it on every run.
|
|
33
|
+
* - Writing nothing writes NOTHING. An append left with no row to add
|
|
34
|
+
* creates no file and no directory, and `read` and `keys` on a
|
|
35
|
+
* store that does not exist answer empty and create nothing either:
|
|
36
|
+
* absence is the first-run case. Only absence is absorbed. A store
|
|
37
|
+
* that exists and cannot be read throws, so it cannot pass for an
|
|
38
|
+
* empty one and have everything appended again.
|
|
39
|
+
*
|
|
40
|
+
* ## What the port leaves out
|
|
41
|
+
*
|
|
42
|
+
* Readings only one storage format can make, such as the NDJSON line
|
|
43
|
+
* arithmetic, its unparsed-line count, or the newline it writes ahead
|
|
44
|
+
* of a partial last line, are not the port's. A backend reports those
|
|
45
|
+
* on a wider type of its own; {@link AppendResult} and `read` carry
|
|
46
|
+
* only what both backends can answer.
|
|
47
|
+
*
|
|
48
|
+
* There is no `close`, because the spec's port has no lifecycle method.
|
|
49
|
+
* A backend acquires and releases what it needs within each call; a
|
|
50
|
+
* handle held across calls would have no owner to release it.
|
|
51
|
+
*
|
|
52
|
+
* The port is synchronous because both backends can be. The sibling's
|
|
53
|
+
* NDJSON store reads and appends through `node:fs`'s synchronous calls,
|
|
54
|
+
* and `bun:sqlite`'s `run` and `all` return plain values rather than
|
|
55
|
+
* promises, measured on bun 1.3.14.
|
|
56
|
+
*/
|
|
57
|
+
import type { CommitStats } from './commit-stats.js';
|
|
58
|
+
import type { PlanStubMatch } from './plan-stubs.js';
|
|
59
|
+
import type { SessionKind, SessionStats } from './session-row-types.js';
|
|
60
|
+
/**
|
|
61
|
+
* Where a session ran, as its row records it.
|
|
62
|
+
*
|
|
63
|
+
* `local` is the only member in this phase, and the collector writes it
|
|
64
|
+
* on every row. The spec reserves `remote` for phase 6, the phase that
|
|
65
|
+
* brings the remote transport, and the member joins this union then and
|
|
66
|
+
* not before: a union holding it now would let a row of this phase
|
|
67
|
+
* claim a mode no session of this phase can have.
|
|
68
|
+
*/
|
|
69
|
+
export type SessionMode = 'local';
|
|
70
|
+
/**
|
|
71
|
+
* One stats row widened with what attribution answered and with the
|
|
72
|
+
* mode the session ran in — the row the collector's session half writes.
|
|
73
|
+
*
|
|
74
|
+
* Declared here rather than beside the collector that builds it, so the
|
|
75
|
+
* port depends only on the modules the row is made of and never on its
|
|
76
|
+
* own caller. `effort/collect.ts` re-exports it.
|
|
77
|
+
*
|
|
78
|
+
* Attribution reaches the row field by field, and one of its answers
|
|
79
|
+
* stays off it: `issueIdentifierSource`, where the identifier was read.
|
|
80
|
+
* The reconciled schema names the identifier alone.
|
|
81
|
+
*
|
|
82
|
+
* {@link SessionEffortRow.mode} and {@link SessionEffortRow.issueIdentifier}
|
|
83
|
+
* are the reconciled schema's widening, and a row collected before it
|
|
84
|
+
* carries neither. An append-only store never rewrites such a row, and
|
|
85
|
+
* the sibling's own store is made of them: measured on 2026-09-11, not
|
|
86
|
+
* one of its session rows carries either field. The row type is the
|
|
87
|
+
* port's assertion over a row read back, as it is for every other
|
|
88
|
+
* field, so a reader of that store meets the two fields ABSENT, which
|
|
89
|
+
* is neither `local` nor null.
|
|
90
|
+
*/
|
|
91
|
+
export interface SessionEffortRow extends SessionStats {
|
|
92
|
+
/** What the session was dispatched to do. */
|
|
93
|
+
kind: SessionKind;
|
|
94
|
+
/** Where the session ran: `local`, on every row of this phase. */
|
|
95
|
+
mode: SessionMode;
|
|
96
|
+
/** The modal branch of the session's records, or null. */
|
|
97
|
+
branch: string | null;
|
|
98
|
+
/** Records carrying it, and how many distinct branches were seen. */
|
|
99
|
+
branchRecordCount: number;
|
|
100
|
+
distinctBranchCount: number;
|
|
101
|
+
/** The branch split; the stub is not yet a plan stub. */
|
|
102
|
+
branchType: string | null;
|
|
103
|
+
branchStub: string | null;
|
|
104
|
+
/** The resolved plan, or null — never the branch stub as a fallback. */
|
|
105
|
+
planStub: string | null;
|
|
106
|
+
planStubMatch: PlanStubMatch;
|
|
107
|
+
/**
|
|
108
|
+
* The issue the session resolved to, upper-cased, or null — never the
|
|
109
|
+
* branch stub as a fallback. Read from the resolved plan stub, else
|
|
110
|
+
* from the whole branch name, by `effort/attribution.ts`.
|
|
111
|
+
*/
|
|
112
|
+
issueIdentifier: string | null;
|
|
113
|
+
/** The dispatched task sentence, for a task session only. */
|
|
114
|
+
taskText: string | null;
|
|
115
|
+
/** Index of the enqueue among parsed records, or null if none. */
|
|
116
|
+
enqueueRecordIndex: number | null;
|
|
117
|
+
/** File size at collection; short of the file means a frozen row. */
|
|
118
|
+
sizeBytes: number;
|
|
119
|
+
/** File mtime at collection, ISO 8601. */
|
|
120
|
+
modifiedAt: string;
|
|
121
|
+
/**
|
|
122
|
+
* The worktree the session ran in, as an absolute path, or null for a
|
|
123
|
+
* session of the main checkout. A row collected before the field
|
|
124
|
+
* carries it ABSENT, as the module note says of `mode`; the SQLite
|
|
125
|
+
* backend's `worktree` column (migration `session-worktree`) reads
|
|
126
|
+
* NULL for both.
|
|
127
|
+
*/
|
|
128
|
+
worktree: string | null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* One commit's row: the commit parser's answer, stored as parsed.
|
|
132
|
+
*
|
|
133
|
+
* An alias rather than a widening. The commit half appends exactly
|
|
134
|
+
* what `effort/commits.ts` answers, so the row is declared there.
|
|
135
|
+
*/
|
|
136
|
+
export type CommitEffortRow = CommitStats;
|
|
137
|
+
/**
|
|
138
|
+
* The row each kind stores — the closed set every per-kind record in
|
|
139
|
+
* this module is typed against.
|
|
140
|
+
*
|
|
141
|
+
* A type alias rather than an interface, on purpose. An interface can
|
|
142
|
+
* be widened from outside this file by declaration merging and a type
|
|
143
|
+
* alias cannot: measured, a module augmentation re-declaring an alias
|
|
144
|
+
* is refused as a duplicate identifier, where the same merge into an
|
|
145
|
+
* interface compiles silently. A kind therefore enters the store here
|
|
146
|
+
* or nowhere, and entering it here is a compile error until
|
|
147
|
+
* {@link EFFORT_KEY_PROJECTIONS} names its key.
|
|
148
|
+
*/
|
|
149
|
+
export type EffortRowByKind = {
|
|
150
|
+
sessions: SessionEffortRow;
|
|
151
|
+
commits: CommitEffortRow;
|
|
152
|
+
};
|
|
153
|
+
/** The row kinds a store holds. */
|
|
154
|
+
export type EffortRowKind = keyof EffortRowByKind;
|
|
155
|
+
/** The row one kind stores. */
|
|
156
|
+
export type EffortRow<K extends EffortRowKind> = EffortRowByKind[K];
|
|
157
|
+
/** What one append did. */
|
|
158
|
+
export interface AppendResult {
|
|
159
|
+
/**
|
|
160
|
+
* The file the append targeted, whether or not anything was written
|
|
161
|
+
* to it: the kind's own file, or one shared by every kind, as the
|
|
162
|
+
* backend lays its store out.
|
|
163
|
+
*/
|
|
164
|
+
path: string;
|
|
165
|
+
/** Rows appended. Zero writes nothing and creates nothing. */
|
|
166
|
+
appended: number;
|
|
167
|
+
/**
|
|
168
|
+
* Rows whose key was already held, in storage or earlier in the
|
|
169
|
+
* batch. A batch that was not refused has no third outcome, so
|
|
170
|
+
* `appended + skipped` is its length.
|
|
171
|
+
*/
|
|
172
|
+
skipped: number;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* An effort store holding every row kind, under the rules in the
|
|
176
|
+
* module note.
|
|
177
|
+
*
|
|
178
|
+
* Property signatures rather than method signatures, as the sibling's
|
|
179
|
+
* store interface spelled its own. TypeScript compares a method's
|
|
180
|
+
* parameters bivariantly and a function-typed property's strictly, so
|
|
181
|
+
* a backend whose `keys` accepted only one of the kinds compiles
|
|
182
|
+
* against a method signature and is refused against this one.
|
|
183
|
+
*/
|
|
184
|
+
export interface EffortStore {
|
|
185
|
+
/**
|
|
186
|
+
* Appends the rows of one kind the store does not already hold.
|
|
187
|
+
*
|
|
188
|
+
* Throws, having written nothing, when any row of the batch carries
|
|
189
|
+
* no key under {@link EFFORT_KEY_PROJECTIONS}.
|
|
190
|
+
*/
|
|
191
|
+
append: <K extends EffortRowKind>(kind: K, rows: readonly EffortRow<K>[]) => AppendResult;
|
|
192
|
+
/**
|
|
193
|
+
* The keys one kind already holds, which is what a collector skips.
|
|
194
|
+
* Read back from the rows, into a fresh set the caller owns.
|
|
195
|
+
*/
|
|
196
|
+
keys: (kind: EffortRowKind) => Set<string>;
|
|
197
|
+
/** Every row one kind holds, in the order they were appended. */
|
|
198
|
+
read: <K extends EffortRowKind>(kind: K) => EffortRow<K>[];
|
|
199
|
+
}
|
|
200
|
+
/** Each kind's key projection, typed against that kind's own row. */
|
|
201
|
+
export type EffortKeyProjections = {
|
|
202
|
+
readonly [K in EffortRowKind]: (row: EffortRow<K>) => string | null;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* The key each kind is deduplicated by: `sessions` by session id,
|
|
206
|
+
* `commits` by sha.
|
|
207
|
+
*
|
|
208
|
+
* Closed over {@link EffortRowByKind}. A kind added there without a key
|
|
209
|
+
* here is a compile error, and so is a projection reading a field its
|
|
210
|
+
* kind's row does not carry. A backend answers `keys` and dedupes an
|
|
211
|
+
* `append` through this one record, with no projection of its own to
|
|
212
|
+
* substitute, and that is what keeps the set a collector skips by and
|
|
213
|
+
* the set an append dedupes by the same set.
|
|
214
|
+
*
|
|
215
|
+
* A projection answers null for a row that carries no key: the field
|
|
216
|
+
* absent, not a string, or empty. That puts the keyless rule a batch
|
|
217
|
+
* is refused by in one place too. The check is made on the value
|
|
218
|
+
* rather than trusted to the row's type, because a row read back from
|
|
219
|
+
* storage is only as typed as the file it came from. Only the empty
|
|
220
|
+
* string is empty — a key is not trimmed — which is the sibling store's
|
|
221
|
+
* rule, so the two agree on every stored row's key.
|
|
222
|
+
*/
|
|
223
|
+
export declare const EFFORT_KEY_PROJECTIONS: EffortKeyProjections;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cause codes: the `<family>:<leaf>` names of known error contexts, and
|
|
3
|
+
* the checks every list of them passes (#949).
|
|
4
|
+
*
|
|
5
|
+
* A code marks which gate tripped: the family names the gate or tool
|
|
6
|
+
* (`git`, `tsc`, `fs`), the leaf the condition. Two bugs sharing a code
|
|
7
|
+
* may still fail in different things, so a code raises a match score and
|
|
8
|
+
* never decides it.
|
|
9
|
+
*
|
|
10
|
+
* Every family holds `<family>:new-context` without declaring it, for a
|
|
11
|
+
* cause no leaf fits yet; `unknown:new-context` is the last resort when
|
|
12
|
+
* even the family is unknown. Both reserved words are refused as declared
|
|
13
|
+
* leaves, and `unknown` as a declared family, so a listed code always
|
|
14
|
+
* names a real context. `context/error-codes.md` is the page for the list.
|
|
15
|
+
*/
|
|
16
|
+
/** How loud an error of the code is. */
|
|
17
|
+
export type ErrorLevel = 'error' | 'warn';
|
|
18
|
+
/** Every level, in the order a refusal names them. */
|
|
19
|
+
export declare const ERROR_LEVELS: readonly ErrorLevel[];
|
|
20
|
+
/** One known error context. */
|
|
21
|
+
export interface ErrorCodeEntry {
|
|
22
|
+
/** `<family>:<leaf>`, both kebab-case. */
|
|
23
|
+
readonly code: string;
|
|
24
|
+
/** One line: what the context is. */
|
|
25
|
+
readonly description: string;
|
|
26
|
+
/** The recommended next action. */
|
|
27
|
+
readonly hint: string;
|
|
28
|
+
/** How loud an error of the code is. */
|
|
29
|
+
readonly level: ErrorLevel;
|
|
30
|
+
/**
|
|
31
|
+
* The version or issue that added the code. rafa's own list always
|
|
32
|
+
* names it ({@link defineErrorCodes} refuses an entry without one); a
|
|
33
|
+
* project's entry may leave it out.
|
|
34
|
+
*/
|
|
35
|
+
readonly since?: string;
|
|
36
|
+
/** The spelling an older per-module union gives the same context. */
|
|
37
|
+
readonly legacy?: string;
|
|
38
|
+
}
|
|
39
|
+
/** A declarable code: kebab family, colon, kebab leaf. */
|
|
40
|
+
export declare const CODE_PATTERN: RegExp;
|
|
41
|
+
/** The leaf every family holds for a cause no leaf fits yet. */
|
|
42
|
+
export declare const NEW_CONTEXT_LEAF = "new-context";
|
|
43
|
+
/** The family of the last resort, and a leaf no entry may take. */
|
|
44
|
+
export declare const UNKNOWN_FAMILY = "unknown";
|
|
45
|
+
/** The part of `code` before its colon. */
|
|
46
|
+
export declare function familyOf(code: string): string;
|
|
47
|
+
/** Why `code` cannot be declared, or null when it can. */
|
|
48
|
+
export declare function codeProblem(code: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* `entries`, frozen, once each passes the shape check and no code is
|
|
51
|
+
* declared twice. Throws a `TypeError` naming the first fault: the list
|
|
52
|
+
* is source, so a fault is a build error, never a reading to recover.
|
|
53
|
+
*/
|
|
54
|
+
export declare function defineErrorCodes(entries: readonly ErrorCodeEntry[]): readonly ErrorCodeEntry[];
|
|
55
|
+
/** True for a declared code, a declared family's new-context, or `unknown:new-context`. */
|
|
56
|
+
export declare function isKnownCode(code: string, entries: readonly ErrorCodeEntry[]): boolean;
|