@open-tomato/rafa 0.45.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +255 -23
  2. package/dist/PROMPT.md +18 -1
  3. package/dist/adapters/logger/settings.d.ts +132 -0
  4. package/dist/bugs/closest.d.ts +19 -0
  5. package/dist/bugs/fenced.d.ts +8 -0
  6. package/dist/bugs/places.d.ts +20 -0
  7. package/dist/bugs/record.d.ts +145 -0
  8. package/dist/bugs/title.d.ts +83 -0
  9. package/dist/bundled/agents/doc-updater.md +17 -3
  10. package/dist/bundled/agents/loop-implementer.md +14 -2
  11. package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
  12. package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
  13. package/dist/bundled/skills/dev-planner/SKILL.md +101 -18
  14. package/dist/cli.js +74726 -62960
  15. package/dist/config/entry.d.ts +119 -0
  16. package/dist/config/index.d.ts +36 -0
  17. package/dist/config/index.js +51 -0
  18. package/dist/config/shape.d.ts +232 -0
  19. package/dist/config-registry.d.ts +127 -0
  20. package/dist/config-schema-board-project.d.ts +157 -0
  21. package/dist/config-schema-bugs.d.ts +71 -0
  22. package/dist/config-schema-errors.d.ts +54 -0
  23. package/dist/config-schema-features.d.ts +64 -0
  24. package/dist/config-schema-hub.d.ts +150 -0
  25. package/dist/config-schema-issues.d.ts +95 -0
  26. package/dist/config-schema-logger.d.ts +91 -0
  27. package/dist/config-schema-loop-continue.d.ts +122 -0
  28. package/dist/config-schema-loop-retries.d.ts +94 -0
  29. package/dist/config-schema-release.d.ts +307 -0
  30. package/dist/config-schema-tests.d.ts +170 -0
  31. package/dist/config-schema-triage.d.ts +142 -0
  32. package/dist/config-schema-wrap-up.d.ts +96 -0
  33. package/dist/core/command/command.d.ts +228 -0
  34. package/dist/core/command/confirm.d.ts +67 -0
  35. package/dist/core/command/index.d.ts +112 -0
  36. package/dist/core/command/lazy.d.ts +54 -0
  37. package/dist/core/command/registry-types.d.ts +96 -0
  38. package/dist/core/command/running.d.ts +96 -0
  39. package/dist/core/command/spends.d.ts +39 -0
  40. package/dist/core/command/types.d.ts +92 -0
  41. package/dist/core/command/version.d.ts +4 -0
  42. package/dist/core/config/branch-template.d.ts +20 -0
  43. package/dist/core/config/config-items.d.ts +86 -0
  44. package/dist/core/config/config-load.d.ts +47 -0
  45. package/dist/core/config/config-origins.d.ts +57 -0
  46. package/dist/core/config/config-readers.d.ts +167 -0
  47. package/dist/core/config/config-schema.d.ts +207 -0
  48. package/dist/core/config/config-sections.d.ts +493 -0
  49. package/dist/core/config/config.d.ts +320 -0
  50. package/dist/core/config/core-section.d.ts +39 -0
  51. package/dist/core/config/dangerous.d.ts +58 -0
  52. package/dist/core/config/default-routing.d.ts +16 -0
  53. package/dist/core/config/index.d.ts +141 -0
  54. package/dist/core/config/scope.d.ts +42 -0
  55. package/dist/core/config/section.d.ts +206 -0
  56. package/dist/core/config/sections.d.ts +45 -0
  57. package/dist/core/config/standard-schema.d.ts +27 -0
  58. package/dist/core/git/commit.d.ts +232 -0
  59. package/dist/core/git/gh-runner.d.ts +24 -0
  60. package/dist/core/git/git.d.ts +22 -0
  61. package/dist/core/git/index.d.ts +85 -0
  62. package/dist/core/loop/claude.d.ts +388 -0
  63. package/dist/core/loop/hop-record.d.ts +52 -0
  64. package/dist/core/loop/index.d.ts +179 -0
  65. package/dist/core/loop/session-env.d.ts +20 -0
  66. package/dist/core/loop/session-record-parse.d.ts +63 -0
  67. package/dist/core/loop/sessions.d.ts +408 -0
  68. package/dist/core/loop/suite-failure.d.ts +25 -0
  69. package/dist/core/output/active.d.ts +100 -0
  70. package/dist/core/output/stream.d.ts +20 -0
  71. package/dist/core/output/text.d.ts +55 -0
  72. package/dist/core/plan/block-reader.d.ts +46 -0
  73. package/dist/core/plan/blocks.d.ts +139 -0
  74. package/dist/core/plan/declaration.d.ts +366 -0
  75. package/dist/core/plan/plan-stamp.d.ts +75 -0
  76. package/dist/core/runtime/identity.d.ts +21 -0
  77. package/dist/core/runtime/index.d.ts +46 -0
  78. package/dist/effort/store/bring-forward.d.ts +131 -0
  79. package/dist/effort/store/check-rows.d.ts +31 -0
  80. package/dist/effort/store/commit-stats.d.ts +42 -0
  81. package/dist/effort/store/copy.d.ts +35 -0
  82. package/dist/effort/store/development-build.d.ts +105 -0
  83. package/dist/effort/store/effort-levels.d.ts +13 -0
  84. package/dist/effort/store/findings.d.ts +214 -0
  85. package/dist/effort/store/fix-schema.d.ts +184 -0
  86. package/dist/effort/store/index.js +10 -7
  87. package/dist/effort/store/location.d.ts +44 -0
  88. package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
  89. package/dist/effort/store/merge-conflicts.d.ts +91 -0
  90. package/dist/effort/store/merge-rules.d.ts +82 -0
  91. package/dist/effort/store/merge-store.d.ts +211 -0
  92. package/dist/effort/store/merge-union.d.ts +92 -0
  93. package/dist/effort/store/migrate.d.ts +146 -0
  94. package/dist/effort/store/migrations.d.ts +92 -0
  95. package/dist/effort/store/ndjson-files.d.ts +114 -0
  96. package/dist/effort/store/origins.d.ts +116 -0
  97. package/dist/effort/store/plan-stubs.d.ts +79 -0
  98. package/dist/effort/store/rebuild-aside.d.ts +172 -0
  99. package/dist/effort/store/schema-plan.d.ts +102 -0
  100. package/dist/effort/store/session-row-types.d.ts +64 -0
  101. package/dist/effort/store/settings.d.ts +13 -0
  102. package/dist/effort/store/sqlite.d.ts +340 -0
  103. package/dist/effort/store/store-generation.d.ts +17 -0
  104. package/dist/effort/store/store-identity.d.ts +220 -0
  105. package/dist/effort/store/store-meta.d.ts +305 -0
  106. package/dist/effort/store/store-replaced.d.ts +72 -0
  107. package/dist/effort/store/types.d.ts +223 -0
  108. package/dist/errors/codes.d.ts +56 -0
  109. package/dist/errors/rafa-codes.d.ts +36 -0
  110. package/dist/index-2a7nb3an.js +125 -0
  111. package/dist/{index-r28fzsvc.js → index-39kbcw68.js} +350 -165
  112. package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
  113. package/dist/index-cy98y87p.js +2597 -0
  114. package/dist/index-gqz4jn6j.js +13447 -0
  115. package/dist/index-gvek4r8m.js +710 -0
  116. package/dist/index-j4k2vdz3.js +1125 -0
  117. package/dist/index-jh1r4kcn.js +1067 -0
  118. package/dist/{index-mgh2s9bj.js → index-ykggr043.js} +58 -455
  119. package/dist/index-z1w83f81.js +4 -0
  120. package/dist/index.js +14715 -21516
  121. package/dist/learning/index.js +5 -6
  122. package/dist/plan/index.js +12 -11
  123. package/dist/plan-prompt.md +3 -1
  124. package/dist/ports/index.d.ts +732 -0
  125. package/dist/ports/learning.d.ts +101 -0
  126. package/dist/ports/log-level.d.ts +13 -0
  127. package/dist/ports/spec-review.d.ts +66 -0
  128. package/dist/pr/checks.d.ts +133 -0
  129. package/dist/pr/types.d.ts +422 -0
  130. package/dist/ready-4f8edezt.js +2016 -0
  131. package/dist/release-templates/release-bot-fragment.yml +153 -0
  132. package/dist/release-templates/release-publish.yml +110 -0
  133. package/dist/release-templates/release-settle.yml +122 -0
  134. package/dist/release-templates/release-tag.yml +102 -0
  135. package/dist/report/parse.d.ts +415 -0
  136. package/dist/schema/project-id.d.ts +79 -0
  137. package/dist/schema/tiers.d.ts +107 -0
  138. package/package.json +41 -3
  139. package/dist/index-hf2k497d.js +0 -16
  140. package/dist/index-nsr78ct2.js +0 -8219
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The store directory, relative to the repo root.
3
+ *
4
+ * Spelled once on purpose — see the module note. `join` rather than a
5
+ * literal so the separator is the platform's, which keeps a path built
6
+ * here comparable to one a caller built with `join` of its own.
7
+ */
8
+ export declare const EFFORT_STORE_DIR: string;
9
+ /** The row kinds the collector writes, one store file each. */
10
+ export type EffortRowKind = 'sessions' | 'commits';
11
+ /** What one read of a store file found. */
12
+ export interface StoreReadResult<T> {
13
+ /** Every plain-object line, in the order it was appended. */
14
+ rows: T[];
15
+ /** Lines the file held, excluding the trailing newline's empty tail. */
16
+ lineCount: number;
17
+ /** Lines that produced no row: blank, malformed, or not an object. */
18
+ unparsedLineCount: number;
19
+ }
20
+ /** What one append did. */
21
+ export interface AppendResult {
22
+ /** The file written, whether or not anything was written to it. */
23
+ path: string;
24
+ /** Rows appended. Zero writes nothing and creates no file. */
25
+ appended: number;
26
+ /** Rows whose key was already held, on disk or earlier in the batch. */
27
+ skipped: number;
28
+ /**
29
+ * True when the file did not end in a newline and this append wrote
30
+ * one before its first row. The partial line is left in place and
31
+ * reads as unparsed forever, which is what makes the row it half
32
+ * held get collected again.
33
+ */
34
+ separatedPartialLine: boolean;
35
+ }
36
+ /** A store bound to one file and one key projection. */
37
+ export interface EffortStore<T> {
38
+ /** The file this store reads and appends to. */
39
+ readonly path: string;
40
+ /** Every row currently on disk, with the line arithmetic. */
41
+ readRows: () => StoreReadResult<T>;
42
+ /** The keys already held — what a collector skips. */
43
+ collectedKeys: () => Set<string>;
44
+ /** Appends the rows this store does not already hold. */
45
+ append: (rows: readonly T[]) => AppendResult;
46
+ }
47
+ /** The name of one row kind's store file, without its directory. */
48
+ export declare function effortStoreFileName(kind: EffortRowKind): string;
49
+ /**
50
+ * Resolves one store file under a repo root, always under
51
+ * {@link EFFORT_STORE_DIR}. The NDJSON backend resolves its files
52
+ * through `store/location.ts` instead, which `RAFA_EFFORT_DIR` can move.
53
+ */
54
+ export declare function effortStorePath(repoRoot: string, kind: EffortRowKind): string;
55
+ /**
56
+ * Parses store text into rows.
57
+ *
58
+ * The trailing empty element a final newline produces is dropped —
59
+ * exactly one, so a genuinely blank last line is still counted.
60
+ * Everything else that yields no row is counted as unparsed, which is
61
+ * what keeps `rows + unparsed === lineCount` exact and makes a
62
+ * truncated final line visible instead of silent.
63
+ *
64
+ * Splitting on `/\r?\n/` rather than the bare newline is currently
65
+ * UNOBSERVABLE and kept anyway. Measured: the bare split leaves a
66
+ * carriage return on the end of every line and every case still
67
+ * passes, because `JSON.parse` tolerates trailing whitespace and a
68
+ * lone `\r` trims to empty. That equivalence is two downstream
69
+ * tolerances rather than a property of this function, and the next
70
+ * check added here — a length test, a prefix test — would silently
71
+ * depend on neither having changed. Taking the carriage return off at
72
+ * the split is what keeps that from mattering.
73
+ *
74
+ * The `T` is the caller's assertion. The only structural check made
75
+ * here is that the line parsed to a plain object.
76
+ */
77
+ export declare function parseStoreText<T>(text: string | null): StoreReadResult<T>;
78
+ /** Reads every row a store file holds, with its line arithmetic. */
79
+ export declare function readStoreRows<T>(path: string): StoreReadResult<T>;
80
+ /**
81
+ * Projects rows onto their keys.
82
+ *
83
+ * A row whose key is absent or empty contributes nothing rather than
84
+ * seeding the set with a placeholder every other keyless row would
85
+ * then collide with. {@link appendRows} refuses to write one in the
86
+ * first place; this tolerates the ones a hand-edited store might hold.
87
+ */
88
+ export declare function keysOfRows<T>(rows: readonly T[], keyOf: (row: T) => string | null | undefined): Set<string>;
89
+ /** The keys a store file already holds — what a collector skips. */
90
+ export declare function collectedKeys<T>(path: string, keyOf: (row: T) => string | null | undefined): Set<string>;
91
+ /**
92
+ * Appends the rows a store file does not already hold.
93
+ *
94
+ * The batch is key-validated before anything is opened, so a keyless
95
+ * row leaves the file untouched rather than half-appended. Rows are
96
+ * serialised one per line: `JSON.stringify` escapes a newline inside
97
+ * a string value, so no row content can ever split a line in two.
98
+ *
99
+ * Writing nothing writes NOTHING — no file, no directory, no trailing
100
+ * newline. An empty first collect leaves no trace, which is the only
101
+ * answer consistent with a store whose absence means "empty".
102
+ */
103
+ export declare function appendRows<T>(path: string, rows: readonly T[], keyOf: (row: T) => string | null | undefined): AppendResult;
104
+ /**
105
+ * Binds a store file to the key projection it is deduplicated by.
106
+ *
107
+ * The binding is the point rather than the ergonomics. Incrementality
108
+ * fails silently when the set a collector SKIPS by and the set an
109
+ * append DEDUPES by are built from two different projections of a
110
+ * row — the collector re-reads work it already holds, or the store
111
+ * grows a duplicate per run, and both look like a store that is
112
+ * simply working. One `keyOf` per store makes that unrepresentable.
113
+ */
114
+ export declare function openEffortStore<T>(path: string, keyOf: (row: T) => string | null | undefined): EffortStore<T>;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The origin pair a production insert stamps on a new row it writes:
3
+ * `origin_store`, the store id `store_meta` records, and `origin_seq`,
4
+ * the row's own `seq`. An insert that carries a row over from another
5
+ * store or file keeps the pair that row holds; {@link ORIGIN_TABLES}
6
+ * names the three.
7
+ *
8
+ * ## One counter, not two
9
+ *
10
+ * `origin_seq` is the row's local `seq` at insert, never a second
11
+ * counter. So a store that mints a new origin, being a copy, keeps
12
+ * counting `seq` from the rows it holds, and its first row under the
13
+ * new origin carries the next `seq` rather than 1. Two stores that
14
+ * share rows then disagree on `origin_store` for everything written
15
+ * after the copy, which is what keeps their pairs apart.
16
+ *
17
+ * Every stamped insert names `seq` itself, as `COALESCE(MAX(seq), 0) + 1`
18
+ * over its own table, and writes that same value into `origin_seq`, so
19
+ * the two cannot differ. SQLite would pick the same rowid for an
20
+ * `INTEGER PRIMARY KEY` left out, but only while the largest rowid is
21
+ * below 2^63 - 1: measured with Bun 1.4.2, a row inserted after one
22
+ * at `9223372036854775807` got `4005337119280845049`, a random pick
23
+ * no `MAX(seq)` read beforehand predicts. Both subqueries sit in the one
24
+ * `INSERT` statement, which SQLite runs atomically under the write lock
25
+ * it takes, so a row inserted by another process cannot land between
26
+ * the reading of `MAX(seq)` and the insert. Nothing here reads a row
27
+ * back or updates one after the insert.
28
+ *
29
+ * ## Where the origin comes from
30
+ *
31
+ * The insert reads `store_id` from `store_meta` (`id = 1`) in the same
32
+ * statement. `withSqliteStore` settles the store's identity before any
33
+ * writer runs (`store-meta.ts`), so on a `write` open the row holds the
34
+ * origin this very file was minted for: the one recorded, kept, or the
35
+ * one just minted for a copy. A store with no `store_meta` row, as one
36
+ * under `tmpdir()` outside any repository with commits is left, stamps
37
+ * NULL in both columns, as a runtime before `row-origins` does. It
38
+ * never stamps a `seq` without a store: a pair is both or neither.
39
+ *
40
+ * ## No trigger, and no `UPDATE`
41
+ *
42
+ * A trigger filling the columns would break `writers` as
43
+ * `classifyMigration` reads it, since an older runtime's insert would
44
+ * fire it. A second statement updating the pair after the insert would
45
+ * be a production `UPDATE … SET origin_store` that the merge's registry
46
+ * would have to give a rule. The pair is therefore written by the
47
+ * insert's own column list, which {@link STAMPED_COLUMNS} and
48
+ * {@link stampedValues} spell for every writer alike.
49
+ *
50
+ * ## Readers
51
+ *
52
+ * No reader filters on either column, so a row an older runtime
53
+ * inserts, NULL in both, is read as every stamped row is. The partial
54
+ * unique index `<table>_by_origin` covers only rows whose
55
+ * `origin_store` is not NULL, so any number of such rows coexist.
56
+ *
57
+ * A reader that answers rows in order and may run over a merged store
58
+ * sorts by {@link ACROSS_STORES_ORDER}, which names both columns. `seq`
59
+ * alone cannot be that order: a merge inserts the other store's rows
60
+ * under new local `seq` values after its own, so the two sides of one
61
+ * merge would read the same rows in two orders.
62
+ */
63
+ /**
64
+ * The tables a merge unions, each carrying `origin_store` and
65
+ * `origin_seq`: since migration `row-origins` for the twelve tables
66
+ * older than it, and from the migration that creates it for
67
+ * `stale_docs` and `bug_routes`. A production insert that writes a new
68
+ * row into one of them is stamped. Three inserts carry rows that exist
69
+ * already and leave the pair as the row holds it:
70
+ * the copy in `fix-schema.ts`, column for column from the live store;
71
+ * the union in `merge-union.ts`, which gives another store's row a new
72
+ * local `seq` and keeps its origin; and the restore in
73
+ * `fixture-extract.ts`, which builds a store from an extract's rows.
74
+ * `origins.test.ts` reads every insert from source and spells both
75
+ * lists. `schema_migrations`, `store_meta`, `store_replaced`, `merges`
76
+ * and `merge_conflicts` are the store's own bookkeeping and carry no
77
+ * origin.
78
+ */
79
+ export declare const ORIGIN_TABLES: readonly ["sessions", "commits", "findings", "blockers", "out_of_scope_bugs", "report_absences", "task_reports", "preflight", "dispatches", "changes", "skill_invocations", "plan_ci", "stale_docs", "bug_routes"];
80
+ /** One of the {@link ORIGIN_TABLES}. */
81
+ export type OriginTable = typeof ORIGIN_TABLES[number];
82
+ /**
83
+ * The `ORDER BY` terms of a reader that answers the rows of a table
84
+ * with `collected_at` in the same order on every store holding them:
85
+ * the write's time first, then the origin pair, then the local `seq`.
86
+ *
87
+ * - `collected_at` orders rows written at different times, whichever
88
+ * store wrote them and whichever side ran a merge.
89
+ * - The origin pair breaks a tie. One write stamps every row it
90
+ * inserts with one time, and `origin_seq` is the `seq` each row got
91
+ * in the store that wrote it, so one device's rows of one write keep
92
+ * the order that device appended them in, on any store, and two
93
+ * devices' rows at one time are ordered by their `origin_store`.
94
+ * - `seq` breaks what remains: rows with a NULL origin, which SQLite
95
+ * sorts before every origin. Among those at one time the order is
96
+ * the store's own append order, which is not the same on both sides
97
+ * of a merge.
98
+ *
99
+ * A clock that goes backwards reorders rows here; the append order no
100
+ * longer wins over the stamp.
101
+ */
102
+ export declare const ACROSS_STORES_ORDER = "collected_at, origin_store, origin_seq, seq";
103
+ /**
104
+ * The columns a stamped insert names after its own, in the order
105
+ * {@link stampedValues} answers their values.
106
+ */
107
+ export declare const STAMPED_COLUMNS = "seq, origin_store, origin_seq";
108
+ /**
109
+ * The SQL values of {@link STAMPED_COLUMNS} for an insert into `table`:
110
+ * the next `seq`, the store's origin, and that `seq` again as
111
+ * `origin_seq` when the store has an origin, NULL beside a NULL origin.
112
+ *
113
+ * `table` is interpolated into the SQL, which is safe because the type
114
+ * admits only the names in {@link ORIGIN_TABLES}.
115
+ */
116
+ export declare function stampedValues(table: OriginTable): string;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Branch and plan-stub resolution: how a branch name splits into a type
3
+ * and a stub, how plan stubs are read out of a directory listing, and
4
+ * how a branch stub resolves against them.
5
+ *
6
+ * Moved down from `src/effort/attribution.ts`, which attributes a
7
+ * session to its plan, issue and task text and re-exports these names
8
+ * under the same names: the store's row types (`types.ts`) and its plan
9
+ * CI reader (`plan-ci.ts`) need the resolution but not the session
10
+ * attribution, so they import it here and the store imports nothing
11
+ * above core for it. The module note of `attribution.ts` still explains
12
+ * why a branch stub is not a plan stub and why the queue id is matched
13
+ * whole.
14
+ */
15
+ /** One branch name split into the two parts attribution reads. */
16
+ export interface BranchAttribution {
17
+ /** The branch as recorded, trimmed, or null when there was none. */
18
+ branch: string | null;
19
+ /** The segment before the first slash, or null when there is none. */
20
+ type: string | null;
21
+ /** The segment after it, or null when the branch carries no stub. */
22
+ stub: string | null;
23
+ }
24
+ /**
25
+ * How a session was matched to a plan.
26
+ *
27
+ * `stamped` is the only member that does not come from a branch name:
28
+ * the loop writes the stub into every prompt it dispatches, so it is
29
+ * read back directly and outranks the branch. The rest describe the
30
+ * branch-stub path, which is the fallback for a session recorded
31
+ * before stamping existed, or one the loop did not dispatch.
32
+ */
33
+ export type PlanStubMatch = 'stamped' | 'exact' | 'queue-id' | 'none' | 'ambiguous';
34
+ /** The plan a branch stub resolved to, and how it got there. */
35
+ export interface PlanStubResolution {
36
+ /** The resolved plan stub, or null unless the match is decisive. */
37
+ stub: string | null;
38
+ match: PlanStubMatch;
39
+ /**
40
+ * Every plan stub the queue id reached. One member on a `queue-id`
41
+ * match, the branch stub itself on an `exact` one, and the whole
42
+ * contested set on an `ambiguous` one — which is what makes an
43
+ * ambiguity reportable rather than merely refused.
44
+ */
45
+ candidates: readonly string[];
46
+ }
47
+ /**
48
+ * Splits a branch name into its type prefix and its plan stub.
49
+ *
50
+ * A name with no slash has no stub — that is the `main` and `pw-align`
51
+ * case and it is the expected answer, not a degraded one. A stub that
52
+ * would itself contain a slash is refused for a narrower reason: no
53
+ * plan file can be named after it, `plan.ts` writing
54
+ * `PLAN-<stub>.md` into one flat directory.
55
+ */
56
+ export declare function attributeBranch(branch: string | null | undefined): BranchAttribution;
57
+ /**
58
+ * Reads plan stubs out of a directory listing.
59
+ *
60
+ * Takes names rather than a directory so the naming convention lives in
61
+ * one place while the read stays with the caller — the plans directory,
62
+ * `plan.dir`, is untracked by default and can be absent entirely, which
63
+ * is the caller's problem to report and not this module's to guess at.
64
+ *
65
+ * Duplicates are collapsed: two listings concatenated would otherwise
66
+ * make every queue-id match ambiguous against a stub and itself.
67
+ */
68
+ export declare function planStubsFromFileNames(fileNames: readonly string[]): string[];
69
+ /** The leading queue id of a stub, or null when it carries none. */
70
+ export declare function queueIdOf(stub: string | null | undefined): string | null;
71
+ /**
72
+ * Resolves a branch stub against the known plan stubs.
73
+ *
74
+ * Exact first, then a queue id that reaches exactly one plan. Anything
75
+ * else answers `none` or `ambiguous` with its candidates — a report
76
+ * would rather show an unattributed bucket than attribute a plan's
77
+ * spend to the wrong plan.
78
+ */
79
+ export declare function resolvePlanStub(branchStub: string | null | undefined, planStubs: readonly string[]): PlanStubResolution;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * The build-aside convention: a SQLite store is never changed in place.
3
+ * A new file is built beside it, checked, and swapped in behind a
4
+ * backup of the whole store, or deleted under a dry run.
5
+ *
6
+ * ## The steps
7
+ *
8
+ * 1. Refuse while a rollback journal or write-ahead log sits beside the
9
+ * store ({@link refuseInFlight}): either means a write in flight or
10
+ * interrupted, and a file built from the store then would miss it.
11
+ * 2. Refuse when the parallel file or the backup is already there
12
+ * ({@link rebuildAside}), so two runs never overwrite each other.
13
+ * 3. Build the parallel file. What goes into it is the caller's: the
14
+ * repair (`fix-schema.ts`) brings a fresh file to this rafa's
15
+ * migrations through the log-aware apply and copies the known tables
16
+ * into it, and the forward move (`migrate.ts`) writes the store out
17
+ * with `VACUUM INTO` and applies its pending migrations there, and
18
+ * the merge (`merge-store.ts`) writes the store out the same way and
19
+ * unions another store's rows into it. The caller checks it with
20
+ * {@link checkedCounts}, every table's row count against the
21
+ * store's plus the rows the caller says it added, and
22
+ * {@link refuseCorrupt}, SQLite's `integrity_check`.
23
+ * 4. Any failure while building removes the parallel file and its
24
+ * journal, and leaves the store untouched.
25
+ * 5. Under a dry run the parallel file is then deleted, so the run can
26
+ * be repeated and leaves the directory as it found it. Otherwise the
27
+ * parallel file is swapped in ({@link swapIn}) in four steps:
28
+ * a. The store is written out to the backup with `VACUUM INTO` on a
29
+ * read-only connection (`vacuumInto`, `copy.ts`), and the backup
30
+ * is flushed with `fsync`, since SQLite's documentation says
31
+ * `VACUUM INTO` does not sync the file it writes.
32
+ * b. The store's identity is carried onto the parallel file
33
+ * (`carryStoreIdentity`, `store-meta.ts`): when a write to the
34
+ * live store would keep its origin, the parallel file's row is
35
+ * given the parallel file's own device and inode and a new
36
+ * generation. Otherwise nothing is carried: the parallel file's
37
+ * row is given only a fresh generation the side record does not
38
+ * hold, and the swapped-in store mints on its next write.
39
+ * c. When the carry answered `carried` with a generation, that
40
+ * generation is written to the store's side record
41
+ * (`writeStoreGeneration`, `store-generation.ts`), immediately
42
+ * before the rename. A carry onto a file with no `generation`
43
+ * column answers none, and this step is skipped.
44
+ * d. The parallel file is renamed over the store, which replaces it
45
+ * in one step.
46
+ *
47
+ * ## A failed swap
48
+ *
49
+ * The store's file is only read until the rename, and a rename that
50
+ * fails replaces nothing, so a failure at any of the four steps leaves
51
+ * the store's file as it was. The swap then deletes nothing: it throws a
52
+ * {@link SwapFailure} naming the step, the rebuilt file and the backup,
53
+ * and leaves both beside the store. After a failed backup the backup
54
+ * may be partial or absent; after a failed carry, side record write or
55
+ * rename it holds the whole store. A run with another stamp names other
56
+ * files, so it never overwrites either.
57
+ *
58
+ * The side record is the one thing beside the store a swap writes
59
+ * before the rename. A side record write that fails leaves the record
60
+ * as it was (`writeStoreGeneration` replaces it whole or not at all), so
61
+ * the store keeps its origin on its next write. A rename that fails
62
+ * after it leaves the store's file holding the old generation beside a
63
+ * side record holding the new one, so the store mints once on its next
64
+ * write, which is the safe side, and keeps that id on the write after.
65
+ *
66
+ * ## The backup is a copy, and takes a new origin
67
+ *
68
+ * The backup holds every row the store held, with its schema and
69
+ * `user_version`, but it is a new file: its bytes need not equal the
70
+ * original's, and it has an inode of its own, since it was written while
71
+ * the original still existed. Its `store_meta` row still names the
72
+ * original's inode, so a backup renamed back over the store mints a new
73
+ * origin on its next writing open, as any restored copy must
74
+ * (`store-identity.ts`). It holds the generation the side record held
75
+ * before the swap, which the swap rotated past, so it mints even when
76
+ * the host gives it back the original's inode number. The swapped-in
77
+ * file keeps the store's origin, since the carry recorded the inode the
78
+ * rename brings to the store's path and the generation the swap wrote to
79
+ * its side record.
80
+ *
81
+ * The caller names both files, so each command keeps its own spelling
82
+ * of them beside the store. The checks work over a handle the caller
83
+ * opened; the swap opens the store read-only for the backup and the
84
+ * parallel file for the carry.
85
+ */
86
+ import type { CarryOutcome } from './store-meta.js';
87
+ import type { Database } from 'bun:sqlite';
88
+ /** A build-aside step refused before the live store was changed. */
89
+ export declare class RebuildRefusal extends Error {
90
+ constructor(message: string, options?: ErrorOptions);
91
+ }
92
+ /** A table with the rows it holds. */
93
+ export interface TableRows {
94
+ readonly table: string;
95
+ readonly rows: number;
96
+ }
97
+ /** A name quoted as an SQL identifier. */
98
+ export declare function quoted(name: string): string;
99
+ /** One count query's answer. */
100
+ export declare function count(db: Database, sql: string): number;
101
+ /** Refuses while a journal or log beside `path` says a write is in flight or was interrupted. */
102
+ export declare function refuseInFlight(path: string): void;
103
+ /**
104
+ * Every table in `tables` with its rows in `main`, refusing one whose
105
+ * count differs from the same table in the attached `original` schema
106
+ * plus the rows `added` names for it, none for a table it does not name.
107
+ * A repair or a migration adds none; a merge names what it inserted.
108
+ */
109
+ export declare function checkedCounts(db: Database, tables: readonly string[], original: string, added?: Readonly<Record<string, number>>): TableRows[];
110
+ /** Refuses a rebuilt file SQLite does not vouch for. */
111
+ export declare function refuseCorrupt(db: Database): void;
112
+ /** A step of {@link swapIn}, in the order it runs them. */
113
+ export type SwapStep = 'backup' | 'carry' | 'record' | 'rename';
114
+ /** The files one swap works with. */
115
+ interface SwapFiles {
116
+ readonly path: string;
117
+ readonly parallelPath: string;
118
+ readonly backupPath: string;
119
+ }
120
+ /**
121
+ * A swap that failed before the rename replaced the store: the store is
122
+ * unchanged, and the rebuilt file and whatever the backup step wrote
123
+ * are left beside it. The failure the step threw is the `cause`.
124
+ */
125
+ export declare class SwapFailure extends RebuildRefusal {
126
+ readonly step: SwapStep;
127
+ readonly parallelPath: string;
128
+ readonly backupPath: string;
129
+ constructor(step: SwapStep, files: SwapFiles, cause: unknown);
130
+ }
131
+ /** The four steps of a swap, which {@link swapIn} runs in this order. */
132
+ export interface SwapSteps {
133
+ /** Writes the store at `path` out to `backupPath`. */
134
+ readonly backup: (path: string, backupPath: string) => void;
135
+ /** Carries the identity of the store at `path` onto the file at `parallelPath`, answering what it wrote. */
136
+ readonly carry: (path: string, parallelPath: string) => CarryOutcome;
137
+ /** Writes `generation` to the side record of the store at `path`. */
138
+ readonly record: (path: string, generation: string) => void;
139
+ /** Renames `parallelPath` over `path`. */
140
+ readonly rename: (parallelPath: string, path: string) => void;
141
+ }
142
+ /**
143
+ * Swaps the parallel file in over the store at `path`: writes the
144
+ * backup, carries the store's identity, writes the generation the carry
145
+ * answered to the store's side record when it answered `carried` with
146
+ * one, and renames the parallel file over the store. A failed step
147
+ * throws a {@link SwapFailure} and leaves the store's file unchanged, and
148
+ * the backup and the parallel file where they are. `steps` replaces any
149
+ * of the four, so a test can fail one. See the module note.
150
+ */
151
+ export declare function swapIn(path: string, parallelPath: string, backupPath: string, steps?: Partial<SwapSteps>): void;
152
+ /** What one build-aside run is handed. */
153
+ export interface RebuildAsideOptions<Built> {
154
+ /** The live store's file. */
155
+ readonly path: string;
156
+ /** The file built beside it. */
157
+ readonly parallelPath: string;
158
+ /** Where the backup of the store is written when the rebuild is swapped in. */
159
+ readonly backupPath: string;
160
+ /** Build and check, then delete the parallel file instead of swapping it in. */
161
+ readonly dryRun: boolean;
162
+ /** Builds and checks the parallel file, throwing to refuse it. */
163
+ readonly build: (parallelPath: string) => Built;
164
+ }
165
+ /** What the build answered, and where the backup was written: null under a dry run. */
166
+ export interface RebuildAsideResult<Built> {
167
+ readonly built: Built;
168
+ readonly backupPath: string | null;
169
+ }
170
+ /** Runs the build-aside steps around `options.build`. See the module note. */
171
+ export declare function rebuildAside<Built>(options: RebuildAsideOptions<Built>): RebuildAsideResult<Built>;
172
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The compatibility decision: whether this rafa may use a store, given
3
+ * the named migrations the store holds and the ones this build knows.
4
+ *
5
+ * {@link planSchema} is a pure function over plain values. It imports
6
+ * nothing from `bun:sqlite` or `sqlite.ts`, so the decision never
7
+ * depends on the backend that holds the store. The caller reads the
8
+ * store's migration log and `user_version` and passes them in. It
9
+ * answers either "use", with what an open has to adopt and apply and
10
+ * the `user_version` the store ends at, or "refuse", with one of the
11
+ * seven {@link RefusalReason}s.
12
+ *
13
+ * ## How it decides
14
+ *
15
+ * A store with no log is a store a release before the log wrote, and
16
+ * its `user_version` counts the legacy entries it holds. From 1 to
17
+ * {@link LEGACY_GATE_OPEN} it is adopted: those entries are recorded
18
+ * with this build's checksums, which match what ran because a shipped
19
+ * entry is frozen. A version of 0 is a fresh store. Anything else was
20
+ * written by no released rafa.
21
+ *
22
+ * A store with a log is judged from the log, never from a number. A
23
+ * logged id this build knows must carry the checksum this build holds.
24
+ * A logged id this build does not know is read by its `breaks`:
25
+ * nothing lets this rafa use the store as it is; `writers` lets it
26
+ * read and refuses a write; `readers`, or a word this rafa does not
27
+ * know, refuses the store. An open that has migrations to apply writes
28
+ * the store, whatever its caller asked for, so it counts as a write
29
+ * here too.
30
+ *
31
+ * A pending migration this build knows is applied late when it is
32
+ * additive, which is how two branches that each append one merge in
33
+ * either order. A pending breaking migration applies only in order,
34
+ * and only through `rafa effort migrate`.
35
+ *
36
+ * The seven rows are tried in {@link REFUSAL_REASONS} order and the
37
+ * first that matches decides. Every refusal's message ends with its
38
+ * {@link SchemaRefusal.nextStep}, the one command to run next.
39
+ */
40
+ import type { MigrationSpec, SqliteMigration } from './migrations.js';
41
+ /** Whether an open reads the store only, or also writes it. */
42
+ export type StoreAccess = 'read' | 'write';
43
+ /** Who asks: an ordinary open, or `rafa effort migrate`. */
44
+ export type SchemaCaller = 'open' | 'migrate';
45
+ /** A migration this build knows, with the checksum of its body. */
46
+ export interface CatalogueEntry extends MigrationSpec {
47
+ /** Lowercase sha256 hex of the body the backend runs. */
48
+ readonly sha256: string;
49
+ }
50
+ /** One row of a store's migration log, as the caller read it. */
51
+ export interface LoggedMigration {
52
+ readonly id: string;
53
+ readonly sha256: string;
54
+ /** The words recorded, which may include one this rafa does not know. */
55
+ readonly breaks: readonly string[];
56
+ readonly appliedAt: string;
57
+ readonly appliedBy: string;
58
+ }
59
+ /** What the caller read from the store before any write. */
60
+ export interface StoreSchema {
61
+ /** The store's path, as every refusal names it. */
62
+ readonly path: string;
63
+ /** `PRAGMA user_version`: the legacy gate, or a pre-log store's count. */
64
+ readonly userVersion: number;
65
+ /** The migration log in apply order, or null when the store has none. */
66
+ readonly log: readonly LoggedMigration[] | null;
67
+ }
68
+ /** The seven reasons a store is refused, in the order they are tried. */
69
+ export declare const REFUSAL_REASONS: readonly ["pre-log-unreleased", "gate-mismatch", "edited", "unknown-breaks-readers", "unknown-breaks-writers", "breaking-out-of-order", "breaking-pending"];
70
+ /** Why this rafa refuses a store. */
71
+ export type RefusalReason = typeof REFUSAL_REASONS[number];
72
+ /** This rafa may use the store once it has adopted and applied what is listed. */
73
+ export interface SchemaUse {
74
+ readonly verdict: 'use';
75
+ /** Legacy entries a log-less store holds, to be logged without running. */
76
+ readonly adopted: readonly CatalogueEntry[];
77
+ /** Entries to apply, in catalogue order. */
78
+ readonly pending: readonly CatalogueEntry[];
79
+ /** Logged migrations this build does not know; each is additive, or breaks writers only on a read. */
80
+ readonly unknown: readonly LoggedMigration[];
81
+ /** The `user_version` the store holds once everything listed is applied. */
82
+ readonly gate: number;
83
+ }
84
+ /** This rafa refuses the store, and says why and what to run next. */
85
+ export interface SchemaRefusal {
86
+ readonly verdict: 'refuse';
87
+ readonly reason: RefusalReason;
88
+ /** The refusal text, ending with `Next safe step: <nextStep>`. */
89
+ readonly message: string;
90
+ /** The one command to run next. */
91
+ readonly nextStep: string;
92
+ }
93
+ /** What {@link planSchema} answers. */
94
+ export type SchemaPlan = SchemaUse | SchemaRefusal;
95
+ /** A migration's SQL entries with the checksums {@link planSchema} compares. */
96
+ export declare function sqliteCatalogue(migrations: readonly SqliteMigration[]): readonly CatalogueEntry[];
97
+ /**
98
+ * Decide whether this rafa may use `store` with this build's
99
+ * `catalogue`, for an open with `access` from `caller`. The module note
100
+ * says how.
101
+ */
102
+ export declare function planSchema(store: StoreSchema, catalogue: readonly CatalogueEntry[], access: StoreAccess, caller: SchemaCaller): SchemaPlan;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The session row's own types: what a session was dispatched to do and
3
+ * the stats accumulated over its log.
4
+ *
5
+ * Moved down from `src/effort/classify.ts` (`SessionKind`) and
6
+ * `src/effort/session-log.ts` (`SessionStats` and the
7
+ * `SessionUsageTotals` it names), which classify and read session logs
8
+ * and re-export these names under the same names: the store's row types
9
+ * (`types.ts`) need the shapes but not the classifier or the reader, so
10
+ * they import them here and the store imports nothing above core for
11
+ * them. The module notes of `classify.ts` and `session-log.ts` still
12
+ * describe how each value is derived from a log.
13
+ */
14
+ /** What a session was dispatched to do. */
15
+ export type SessionKind = 'task' | 'plan-generation' | 'wrap-up' | 'ci-repair' | 'search' | 'other';
16
+ /**
17
+ * Every numeric counter under a record's `message.usage`, summed over
18
+ * the whole session. Names are the camelCase of the wire spelling;
19
+ * the two `ephemeral*` members are the `cache_creation` split and the
20
+ * two `*Requests` members the `server_tool_use` one.
21
+ */
22
+ export interface SessionUsageTotals {
23
+ inputTokens: number;
24
+ outputTokens: number;
25
+ cacheCreationInputTokens: number;
26
+ cacheReadInputTokens: number;
27
+ ephemeral1hInputTokens: number;
28
+ ephemeral5mInputTokens: number;
29
+ webSearchRequests: number;
30
+ webFetchRequests: number;
31
+ thinkingTokens: number;
32
+ }
33
+ /** One accumulated stats row per session log. */
34
+ export interface SessionStats {
35
+ /** Log basename without its extension, which is the session uuid. */
36
+ sessionId: string;
37
+ /** The path the row was accumulated from. */
38
+ filePath: string;
39
+ /** Non-empty lines read. */
40
+ lineCount: number;
41
+ /** Lines that parsed to a JSON object. */
42
+ recordCount: number;
43
+ /** Lines that did not — malformed JSON, or JSON that is not an object. */
44
+ unparsedLineCount: number;
45
+ /** Records by top-level `type`; an absent type is keyed as `unknown`. */
46
+ recordTypeCounts: Record<string, number>;
47
+ /** Records with `type: assistant`, which are the billable turns. */
48
+ assistantRecordCount: number;
49
+ /** Earliest and latest `timestamp` by instant, not by file order. */
50
+ firstTimestamp: string | null;
51
+ lastTimestamp: string | null;
52
+ /** Records per top-level `gitBranch`, the plan-attribution field. */
53
+ gitBranchCounts: Record<string, number>;
54
+ /** Records per `entrypoint`; separates loop from hand-driven traffic. */
55
+ entrypointCounts: Record<string, number>;
56
+ /** Records per top-level `effort`; what a session actually ran at. */
57
+ effortCounts: Record<string, number>;
58
+ /** Records carrying `isSidechain: true`, which are subagent turns. */
59
+ sidechainRecordCount: number;
60
+ /** Records per `message.model`. */
61
+ modelCounts: Record<string, number>;
62
+ /** Summed `message.usage` counters. */
63
+ usage: SessionUsageTotals;
64
+ }
@@ -0,0 +1,13 @@
1
+ /** What every effort store open reads. */
2
+ export interface StoreSettings {
3
+ /** How long an open waits for another process's write lock, in ms. */
4
+ readonly busyTimeoutMs: number;
5
+ }
6
+ /**
7
+ * Sets the settings every later open reads; `null` puts the default
8
+ * back. Throws a `RangeError`, setting nothing, for a busy timeout
9
+ * outside the whole numbers the config accepts.
10
+ */
11
+ export declare function setActiveStoreSettings(settings: StoreSettings | null): void;
12
+ /** The settings every open reads now; see the module note. */
13
+ export declare function activeStoreSettings(): StoreSettings;