@bookedsolid/rea 0.48.1 → 0.49.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/THREAT_MODEL.md +70 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +241 -0
- package/dist/cli/init.d.ts +12 -0
- package/dist/cli/init.js +161 -0
- package/dist/cli/install/self-pin.d.ts +440 -0
- package/dist/cli/install/self-pin.js +853 -0
- package/dist/cli/upgrade.js +134 -0
- package/dist/policy/loader.d.ts +13 -0
- package/dist/policy/loader.js +36 -0
- package/dist/policy/profiles.d.ts +13 -0
- package/dist/policy/profiles.js +12 -0
- package/dist/policy/types.d.ts +38 -0
- package/hooks/_lib/bootstrap-allowlist.sh +1075 -0
- package/hooks/blocked-paths-bash-gate.sh +35 -12
- package/hooks/protected-paths-bash-gate.sh +30 -12
- package/package.json +3 -1
- package/profiles/bst-internal-no-codex.yaml +4 -0
- package/profiles/bst-internal.yaml +28 -0
- package/profiles/client-engagement.yaml +9 -0
- package/profiles/lit-wc.yaml +6 -0
- package/profiles/minimal.yaml +11 -0
- package/profiles/open-source-no-codex.yaml +4 -0
- package/profiles/open-source.yaml +11 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rea init` self-pin (0.49.0).
|
|
3
|
+
*
|
|
4
|
+
* # Problem
|
|
5
|
+
*
|
|
6
|
+
* `rea init` writes hook shims under `.claude/hooks/` that depend on the
|
|
7
|
+
* `@bookedsolid/rea` CLI being resolvable from `node_modules/`. The
|
|
8
|
+
* pre-0.49.0 init flow did NOT add the dep to the consumer's
|
|
9
|
+
* `package.json`, so any fresh clone + `pnpm install` produced a repo
|
|
10
|
+
* where the shims found no CLI and (correctly) refused every Bash
|
|
11
|
+
* call — including the very `pnpm add -D @bookedsolid/rea` that would
|
|
12
|
+
* recover the install. The bash-gate bootstrap allowlist (Fix B) is the
|
|
13
|
+
* paired safety net; this module is the structural fix.
|
|
14
|
+
*
|
|
15
|
+
* # Contract
|
|
16
|
+
*
|
|
17
|
+
* - Caret-pinned (`^<current-CLI-version>`) entry in `devDependencies`.
|
|
18
|
+
* - Lands in workspace ROOT `package.json`. Walks up from `targetDir`
|
|
19
|
+
* until a `package.json` is found; refuses to mutate a parent that
|
|
20
|
+
* the operator did not explicitly target (we only mutate when the
|
|
21
|
+
* FIRST `package.json` we find IS the target).
|
|
22
|
+
* - Existing different version: warn + skip (do NOT mutate). The
|
|
23
|
+
* operator owns their pin.
|
|
24
|
+
* - Idempotent: re-runs are byte-identical when the pin already
|
|
25
|
+
* matches. Detects and preserves indent / EOL / trailing-newline so
|
|
26
|
+
* no spurious diff churn lands in the consumer's repo.
|
|
27
|
+
* - Dogfood short-circuit: when the consumer's `pkg.name` is
|
|
28
|
+
* `@bookedsolid/rea` itself, skip silently — the dogfood install
|
|
29
|
+
* pins the version via the build, not via the manifest.
|
|
30
|
+
*
|
|
31
|
+
* # Why caret
|
|
32
|
+
*
|
|
33
|
+
* A caret pin (`^0.49.0` → satisfies 0.49.x AND 0.50.0+) gives
|
|
34
|
+
* consumers automatic minor-version uptake without breaking when the
|
|
35
|
+
* shim ABI bumps. Major bumps remain a deliberate operator action.
|
|
36
|
+
*
|
|
37
|
+
* # Why warn-and-skip on existing different version
|
|
38
|
+
*
|
|
39
|
+
* Three scenarios where this matters:
|
|
40
|
+
*
|
|
41
|
+
* 1. Operator explicitly pinned an exact version (`"0.48.1"`) for
|
|
42
|
+
* reproducibility — `rea init` overwriting that to caret would
|
|
43
|
+
* silently widen their pin.
|
|
44
|
+
* 2. Operator is running an OLDER `rea init` against a `package.json`
|
|
45
|
+
* that has a NEWER `rea` pin. Downgrading the pin would brick the
|
|
46
|
+
* install once the operator's lockfile resolves against it.
|
|
47
|
+
* 3. Operator deliberately pinned a workspace-relative path (`"workspace:^"`,
|
|
48
|
+
* `"file:../rea"`). Replacing that with a registry pin breaks the
|
|
49
|
+
* monorepo wiring.
|
|
50
|
+
*
|
|
51
|
+
* Warn-and-skip preserves operator intent in all three cases.
|
|
52
|
+
*/
|
|
53
|
+
/** Package name we self-pin. */
|
|
54
|
+
export declare const REA_PACKAGE_NAME = "@bookedsolid/rea";
|
|
55
|
+
export interface SelfPinResult {
|
|
56
|
+
/**
|
|
57
|
+
* Outcome of the operation. Single value so the caller can format a
|
|
58
|
+
* one-line console message uniformly.
|
|
59
|
+
*
|
|
60
|
+
* - `'wrote'` — package.json was mutated (new dep added OR an
|
|
61
|
+
* existing matching pin re-serialized identically).
|
|
62
|
+
* - `'bumped'` — `mode: 'upgrade'` only. Existing pin was a
|
|
63
|
+
* managed-caret form on the SAME major as the new
|
|
64
|
+
* CLI but did not admit the new minor (e.g.
|
|
65
|
+
* `^0.49.0` + new CLI `0.50.0` — `^0.49.0` rejects
|
|
66
|
+
* `0.50.0` because pre-1.0 caret behaves like
|
|
67
|
+
* tilde). We re-write the pin to the new caret
|
|
68
|
+
* form. Operator-facing message includes the
|
|
69
|
+
* "bumped from X to Y" delta so the change is
|
|
70
|
+
* visible in the upgrade log.
|
|
71
|
+
* - `'skipped-same'` — the existing pin already matches what we
|
|
72
|
+
* would write (idempotent re-run, byte-identical).
|
|
73
|
+
* - `'skipped-different'` — the existing pin differs from ours and
|
|
74
|
+
* we refuse to mutate (operator owns the pin).
|
|
75
|
+
* - `'skipped-dogfood'` — `pkg.name === '@bookedsolid/rea'`, the
|
|
76
|
+
* self-host case; we never self-pin.
|
|
77
|
+
* - `'skipped-no-package-json'` — no `package.json` in the explicit
|
|
78
|
+
* target directory. P2-4: we never
|
|
79
|
+
* walk upward — invocation from a
|
|
80
|
+
* pkg-less subdir refuses rather
|
|
81
|
+
* than silently mutating the parent.
|
|
82
|
+
* - `'skipped-malformed-package-json'` — `package.json` exists but
|
|
83
|
+
* is not valid JSON or not an
|
|
84
|
+
* object; we refuse to mutate
|
|
85
|
+
* a file we do not understand.
|
|
86
|
+
* - `'skipped-symlink-package-json'` — R10-P2 (codex round 10):
|
|
87
|
+
* `package.json` is a symlink.
|
|
88
|
+
* DRY-RUN only — live mode
|
|
89
|
+
* THROWS rather than returning
|
|
90
|
+
* this. The skip-shape exists
|
|
91
|
+
* so `rea upgrade --dry-run`
|
|
92
|
+
* / `--check` can complete a
|
|
93
|
+
* preview even when the
|
|
94
|
+
* symlink would block the
|
|
95
|
+
* live run.
|
|
96
|
+
*/
|
|
97
|
+
action: 'wrote' | 'bumped' | 'skipped-same' | 'skipped-different' | 'skipped-dogfood' | 'skipped-no-package-json' | 'skipped-malformed-package-json' | 'skipped-symlink-package-json';
|
|
98
|
+
/** Absolute path to the package.json we resolved (or null when none found). */
|
|
99
|
+
packageJsonPath: string | null;
|
|
100
|
+
/** Caret-pinned version range we wrote (e.g. `^0.49.0`). Empty when no write happened. */
|
|
101
|
+
pinnedRange: string;
|
|
102
|
+
/** Existing range when the action was `skipped-different`. */
|
|
103
|
+
existingRange?: string;
|
|
104
|
+
/** Operator-facing message (one line, no newline). */
|
|
105
|
+
message: string;
|
|
106
|
+
}
|
|
107
|
+
export interface SelfPinOptions {
|
|
108
|
+
/**
|
|
109
|
+
* Starting directory for the upward `package.json` walk. The walk
|
|
110
|
+
* stops at the first `package.json` found OR at the filesystem root.
|
|
111
|
+
*/
|
|
112
|
+
cwd: string;
|
|
113
|
+
/**
|
|
114
|
+
* The currently-running `@bookedsolid/rea` CLI version (e.g.
|
|
115
|
+
* `'0.49.0'`). The written pin is `^<version>`.
|
|
116
|
+
*/
|
|
117
|
+
cliVersion: string;
|
|
118
|
+
/**
|
|
119
|
+
* When true, never log to stderr — the caller will surface the result
|
|
120
|
+
* structurally. Used by `rea upgrade` which composes its own output.
|
|
121
|
+
*/
|
|
122
|
+
silent?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* R3-P2 (codex round 3): when true, perform the read + decision
|
|
125
|
+
* logic but skip the on-disk write. The returned `action`
|
|
126
|
+
* discriminant is the SAME as the live run would produce
|
|
127
|
+
* (`'wrote'`, `'bumped'`, `'skipped-same'`, `'skipped-different'`,
|
|
128
|
+
* etc.) so the caller can preview exactly what the live run will
|
|
129
|
+
* do. `message` carries a `would-` prefix on the actions that
|
|
130
|
+
* would have mutated the file (`wrote` / `bumped`) so the caller's
|
|
131
|
+
* console output is unambiguous.
|
|
132
|
+
*
|
|
133
|
+
* Pre-fix, `rea upgrade --dry-run` short-circuited around the entire
|
|
134
|
+
* `selfPinRea` call, hiding the planned self-pin action from the
|
|
135
|
+
* dry-run preview. Operators ran dry-run, saw zero pin-related
|
|
136
|
+
* lines, then ran the live upgrade and got a surprise mutation of
|
|
137
|
+
* their package.json.
|
|
138
|
+
*
|
|
139
|
+
* Default: `false` (write path active — preserves existing behavior
|
|
140
|
+
* for every caller that does not opt in).
|
|
141
|
+
*/
|
|
142
|
+
dryRun?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Call-site discriminator (P1-1 / codex round 2).
|
|
145
|
+
*
|
|
146
|
+
* - `'init'` (default) — `rea init` semantics: warn-and-skip on every
|
|
147
|
+
* existing-different-version pin. Respects whatever pin the
|
|
148
|
+
* operator already chose; we never overwrite on a fresh install.
|
|
149
|
+
*
|
|
150
|
+
* - `'upgrade'` — `rea upgrade` semantics: when the existing pin is
|
|
151
|
+
* a managed-caret form (a caret pin we previously wrote, with no
|
|
152
|
+
* operator-authored shape laundering) AND the new CLI is on the
|
|
153
|
+
* SAME major as the existing pin AND the existing caret does NOT
|
|
154
|
+
* admit the new CLI version, BUMP the pin to the new caret. This
|
|
155
|
+
* closes the pre-1.0 caret tightness trap: `^0.49.0` does NOT
|
|
156
|
+
* admit `0.50.0` (pre-1.0 caret is npm-spec'd to behave like
|
|
157
|
+
* tilde), so without auto-bump a 0.49.x → 0.50.x upgrade would
|
|
158
|
+
* copy the newer hooks but leave the old CLI pinned, recreating
|
|
159
|
+
* the hook/CLI skew this whole feature exists to prevent.
|
|
160
|
+
*
|
|
161
|
+
* Auto-bump shape gate: existing range matches a strict managed-
|
|
162
|
+
* caret regex (`^\^\d+\.\d+(\.\d+)?(-prerelease)?$`). Anything
|
|
163
|
+
* else (`workspace:*`, `file:..`, git URLs, `next`, exact pins,
|
|
164
|
+
* tildes, complex ranges) is operator-authored and we hands-off.
|
|
165
|
+
* Cross-major bumps (`^0.x` → `1.x` or `^1.x` → `0.x`) are
|
|
166
|
+
* ALSO operator-authored decisions and we hands-off — major
|
|
167
|
+
* changes are meaningful and should not be silent.
|
|
168
|
+
*
|
|
169
|
+
* Default: `'init'`. R13-P1 (codex round 13) update: BOTH the
|
|
170
|
+
* `rea init` and `rea upgrade` call sites now pass `mode:
|
|
171
|
+
* 'upgrade'` explicitly. The R11-P1 preflight (init) +
|
|
172
|
+
* R9-P1 preflight (upgrade) filter out non-managed-caret cases
|
|
173
|
+
* BEFORE `selfPinRea` runs, so the only thing that reaches the
|
|
174
|
+
* write path is either a fresh write OR a managed-caret bump —
|
|
175
|
+
* and `mode: 'upgrade'` is the correct semantics for both.
|
|
176
|
+
*
|
|
177
|
+
* The `'init'` default is preserved for backwards-compat with any
|
|
178
|
+
* external caller of this exported function. New rea-internal
|
|
179
|
+
* call sites should pass `mode: 'upgrade'` explicitly.
|
|
180
|
+
*/
|
|
181
|
+
mode?: 'init' | 'upgrade';
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Strip a leading UTF-8 BOM (U+FEFF / EF BB BF) from a string, returning the
|
|
185
|
+
* rest. No-op when no BOM is present.
|
|
186
|
+
*
|
|
187
|
+
* Some Windows operators commit `package.json` with a leading BOM; `JSON.parse`
|
|
188
|
+
* rejects it (the spec says JSON.parse must error on a leading BOM). We
|
|
189
|
+
* silently strip it before parse — npm and pnpm both tolerate either form
|
|
190
|
+
* when writing back, so dropping the BOM on save is the simpler, more
|
|
191
|
+
* invariant choice. The alternative (detect-and-preserve) would need an
|
|
192
|
+
* extra field on `FileShape` plus a re-prepend in `serialize`, and the cost
|
|
193
|
+
* (one operator who deliberately wanted a BOM no longer has one) is much
|
|
194
|
+
* lower than the cost of an unrecoverable false-positive
|
|
195
|
+
* `skipped-malformed-package-json` on every BOM-bearing manifest.
|
|
196
|
+
*
|
|
197
|
+
* P3-1 (codex round 1): extracted into a shared helper so the same canonical
|
|
198
|
+
* BOM-strip applies to BOTH `readPackageJson` (the write path used by
|
|
199
|
+
* `selfPinRea`) AND `checkSelfPinDeclaredSync` (the doctor brick-state
|
|
200
|
+
* detector). Pre-extraction, only the write path stripped — doctor would
|
|
201
|
+
* report `fail-malformed` for a BOM-prefixed manifest that self-pin
|
|
202
|
+
* tolerated fine, which is the asymmetric-fix class we explicitly want
|
|
203
|
+
* to defend against.
|
|
204
|
+
*/
|
|
205
|
+
export declare function stripUtf8Bom(input: string): string;
|
|
206
|
+
/**
|
|
207
|
+
* Decide whether `rea upgrade` should auto-bump an existing pin to the
|
|
208
|
+
* newly-written caret. Returns `true` only when:
|
|
209
|
+
*
|
|
210
|
+
* 1. `existing` matches the managed-caret shape (we wrote it; it
|
|
211
|
+
* isn't a workspace/file/git/tag/exact pin).
|
|
212
|
+
* 2. `newRange` ALSO matches the managed-caret shape (sanity — the
|
|
213
|
+
* function should never be called with a non-caret target, but
|
|
214
|
+
* the predicate stays self-contained).
|
|
215
|
+
* 3. Both ranges share the same major version. Cross-major bumps
|
|
216
|
+
* are intentional operator decisions and we hands-off.
|
|
217
|
+
* 4. The existing caret does NOT already admit the version the new
|
|
218
|
+
* range would resolve to. We extract the floor version from
|
|
219
|
+
* `newRange` (strip the leading `^`) and ask
|
|
220
|
+
* `semver.satisfies(floor, existing)`:
|
|
221
|
+
*
|
|
222
|
+
* - `^0.49.0` + `^0.49.5` → 0.49.5 satisfies ^0.49.0 → NO bump
|
|
223
|
+
* - `^0.49.0` + `^0.50.0` → 0.50.0 does NOT satisfy → BUMP
|
|
224
|
+
* - `^0.49.0` + `^0.49.1-beta.0` → prerelease does NOT satisfy
|
|
225
|
+
* a non-prerelease range (npm spec) → BUMP (R4-P2)
|
|
226
|
+
* - `^1.0.0` + `^1.5.0` → 1.5.0 satisfies ^1.0.0 → NO bump
|
|
227
|
+
* - `^1.0.0` + `^1.1.0-beta.0` → prerelease does NOT satisfy
|
|
228
|
+
* → BUMP (R4-P2)
|
|
229
|
+
*
|
|
230
|
+
* The pre-fix predicate compared major/minor by hand, which
|
|
231
|
+
* mis-classified prerelease bumps as already-covered. Switching to
|
|
232
|
+
* `semver.satisfies` gives us npm-spec-correct semver behavior in
|
|
233
|
+
* one place.
|
|
234
|
+
*/
|
|
235
|
+
export declare function shouldBumpManagedCaret(existing: string, newRange: string): boolean;
|
|
236
|
+
/**
|
|
237
|
+
* R9-P1 (codex round 9 / 0.49.0) — `rea upgrade` blocking-pin check.
|
|
238
|
+
*
|
|
239
|
+
* # Why this exists
|
|
240
|
+
*
|
|
241
|
+
* `rea init` and `rea upgrade` write the consumer's `.rea/policy.yaml`
|
|
242
|
+
* with the new `bootstrap_allowlist:` top-level key (added in 0.49.0).
|
|
243
|
+
* `src/policy/loader.ts::PolicySchema` is `.strict()` — older CLIs
|
|
244
|
+
* (≤ 0.48.x) cannot parse a policy file with that key and throw on
|
|
245
|
+
* load. So if `rea upgrade` writes 0.49 hooks + policy artifacts on
|
|
246
|
+
* top of a `package.json` that still pins an OLD `@bookedsolid/rea`
|
|
247
|
+
* version, the hooks resolve the OLD CLI from `node_modules/` on the
|
|
248
|
+
* next fire and that CLI refuses every payload (policy.yaml strict
|
|
249
|
+
* parse fails). The seemingly-successful upgrade leaves the consumer
|
|
250
|
+
* with non-functional gates.
|
|
251
|
+
*
|
|
252
|
+
* R2-P1-1 closed the managed-caret-bump case (we write the new pin in
|
|
253
|
+
* place). R9-P1 closes the gap for EVERY other shape: workspace:*,
|
|
254
|
+
* file:.., git URLs, dist-tags like `next`, exact pins like `0.48.0`,
|
|
255
|
+
* and managed-caret-cross-major. The fix: abort the upgrade BEFORE
|
|
256
|
+
* any artifacts hit disk when the existing pin would not admit the
|
|
257
|
+
* new CLI version.
|
|
258
|
+
*
|
|
259
|
+
* # Contract
|
|
260
|
+
*
|
|
261
|
+
* Returns a discriminated result:
|
|
262
|
+
*
|
|
263
|
+
* - `kind: 'ok'` — proceed with upgrade. Either:
|
|
264
|
+
* * no existing pin (will write fresh), OR
|
|
265
|
+
* * existing pin admits the new CLI version (semver.satisfies), OR
|
|
266
|
+
* * existing pin is a managed-caret that bumps cleanly (R2-P1-1).
|
|
267
|
+
* - `kind: 'no-pkg-json'` — no package.json in cwd; proceed.
|
|
268
|
+
* `selfPinRea` will return `skipped-no-package-json` later.
|
|
269
|
+
* - `kind: 'malformed-pkg-json'` — same; `selfPinRea` will return
|
|
270
|
+
* `skipped-malformed-package-json` later.
|
|
271
|
+
* - `kind: 'dogfood'` — pkg.name === '@bookedsolid/rea';
|
|
272
|
+
* proceed (dogfood install never mutates the manifest).
|
|
273
|
+
* - `kind: 'block'` — existing pin won't admit the new
|
|
274
|
+
* CLI; the caller MUST abort before writing artifacts.
|
|
275
|
+
* Carries the operator-facing reason string.
|
|
276
|
+
*
|
|
277
|
+
* # Why a separate function?
|
|
278
|
+
*
|
|
279
|
+
* `selfPinRea` is the WRITE-path helper. `checkUpgradeBlockingPin` is
|
|
280
|
+
* a READ-only preflight that gives the caller a yes/no answer before
|
|
281
|
+
* any disk mutation. We do NOT fold the abort logic into `selfPinRea`
|
|
282
|
+
* because:
|
|
283
|
+
* 1. Other callers of `selfPinRea` (rea init) want the warn-and-skip
|
|
284
|
+
* posture, not abort.
|
|
285
|
+
* 2. The upgrade entry needs to run this check BEFORE the canonical
|
|
286
|
+
* file-write loop, well upstream of the existing `selfPinRea`
|
|
287
|
+
* invocation.
|
|
288
|
+
* 3. Keeping the check stateless and read-only makes it testable in
|
|
289
|
+
* isolation without filesystem side effects beyond reading
|
|
290
|
+
* package.json (and even those are bounded — single read).
|
|
291
|
+
*/
|
|
292
|
+
export type UpgradeBlockingPinCheckResult = {
|
|
293
|
+
kind: 'ok';
|
|
294
|
+
packageJsonPath: string | null;
|
|
295
|
+
existingRange?: string | undefined;
|
|
296
|
+
} | {
|
|
297
|
+
kind: 'no-pkg-json';
|
|
298
|
+
} | {
|
|
299
|
+
kind: 'malformed-pkg-json';
|
|
300
|
+
packageJsonPath: string;
|
|
301
|
+
} | {
|
|
302
|
+
kind: 'dogfood';
|
|
303
|
+
packageJsonPath: string;
|
|
304
|
+
} | {
|
|
305
|
+
kind: 'block';
|
|
306
|
+
packageJsonPath: string;
|
|
307
|
+
existingRange: string;
|
|
308
|
+
newCliVersion: string;
|
|
309
|
+
newPinnedRange: string;
|
|
310
|
+
reason: string;
|
|
311
|
+
} | {
|
|
312
|
+
kind: 'block-symlink';
|
|
313
|
+
packageJsonPath: string;
|
|
314
|
+
newCliVersion: string;
|
|
315
|
+
newPinnedRange: string;
|
|
316
|
+
reason: string;
|
|
317
|
+
};
|
|
318
|
+
export interface UpgradeBlockingPinCheckOptions {
|
|
319
|
+
cwd: string;
|
|
320
|
+
cliVersion: string;
|
|
321
|
+
/**
|
|
322
|
+
* R11-P1 (codex round 11): which call site is invoking the
|
|
323
|
+
* pre-flight. The check logic is identical for both modes — what
|
|
324
|
+
* changes is the operator-facing message prefix:
|
|
325
|
+
*
|
|
326
|
+
* - `'upgrade'` (default) → `rea upgrade refusing: ...`
|
|
327
|
+
* - `'init'` → `rea init refusing: ...`
|
|
328
|
+
*
|
|
329
|
+
* Both `runInit` and `runUpgrade` write the same 0.49 hooks +
|
|
330
|
+
* policy artifacts, so the skew-creation risk is identical and
|
|
331
|
+
* the pre-flight needs to fire on both surfaces. Pre-R11 the
|
|
332
|
+
* pre-flight was upgrade-only; `rea init` on an existing-install
|
|
333
|
+
* scenario could still leave the bash gates non-functional.
|
|
334
|
+
*/
|
|
335
|
+
mode?: 'init' | 'upgrade';
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Determine whether the existing `@bookedsolid/rea` pin would block
|
|
339
|
+
* `rea init` / `rea upgrade` from writing 0.49 artifacts safely.
|
|
340
|
+
* See type doc.
|
|
341
|
+
*
|
|
342
|
+
* R11-P1 (codex round 11): the check applies to BOTH `rea init` and
|
|
343
|
+
* `rea upgrade`. The implementation is unchanged; only the
|
|
344
|
+
* operator-facing message prefix varies with `mode`.
|
|
345
|
+
*/
|
|
346
|
+
export declare function checkUpgradeBlockingPin(options: UpgradeBlockingPinCheckOptions): Promise<UpgradeBlockingPinCheckResult>;
|
|
347
|
+
/**
|
|
348
|
+
* Idempotent self-pin step. See module header for the full contract.
|
|
349
|
+
*/
|
|
350
|
+
export declare function selfPinRea(options: SelfPinOptions): Promise<SelfPinResult>;
|
|
351
|
+
/**
|
|
352
|
+
* `rea doctor` check: FAIL when hook shims are present but no self-pin
|
|
353
|
+
* is declared. This is the "brick state" detector — a fresh clone of a
|
|
354
|
+
* consumer repo whose `.claude/hooks/` exists but whose `package.json`
|
|
355
|
+
* declares no `@bookedsolid/rea` dep is exactly the scenario the bash
|
|
356
|
+
* allowlist (Fix B) recovers from. Doctor surfaces it loudly so the
|
|
357
|
+
* operator knows to run `rea upgrade` (which re-runs the self-pin
|
|
358
|
+
* step) instead of fighting the gates.
|
|
359
|
+
*
|
|
360
|
+
* Returns a discriminated result:
|
|
361
|
+
* - `kind: 'pass'` — hooks + self-pin both present.
|
|
362
|
+
* - `kind: 'pass-no-hooks'` — no `.claude/hooks/` directory; the
|
|
363
|
+
* check is N/A (caller emits an `info`
|
|
364
|
+
* row instead of a check row).
|
|
365
|
+
* - `kind: 'pass-no-pkg'` — no `package.json` in the doctor's
|
|
366
|
+
* target directory (P2-4: no upward
|
|
367
|
+
* walk — doctor reports the absence
|
|
368
|
+
* rather than scanning the parent
|
|
369
|
+
* chain). Doctor treats this as a
|
|
370
|
+
* `warn` not a `fail` because the
|
|
371
|
+
* bootstrap allowlist refuses pkg-less
|
|
372
|
+
* projects anyway.
|
|
373
|
+
* - `kind: 'pass-dogfood'` — `pkg.name === '@bookedsolid/rea'`.
|
|
374
|
+
* - `kind: 'fail'` — hooks present, package.json present,
|
|
375
|
+
* no self-pin declared. Caller emits
|
|
376
|
+
* a `fail` row with the recovery
|
|
377
|
+
* instruction.
|
|
378
|
+
* - `kind: 'fail-malformed'` — package.json exists but is malformed
|
|
379
|
+
* or not an object. Caller emits a
|
|
380
|
+
* `fail` row naming the file.
|
|
381
|
+
* - `kind: 'fail-symlink'` — R10-P2 (codex round 10):
|
|
382
|
+
* package.json is a symlink. Doctor
|
|
383
|
+
* emits a `fail` row mirroring the
|
|
384
|
+
* write path's refusal so operators
|
|
385
|
+
* discover the misconfiguration before
|
|
386
|
+
* running `rea upgrade`.
|
|
387
|
+
*/
|
|
388
|
+
export type SelfPinCheckResult = {
|
|
389
|
+
kind: 'pass';
|
|
390
|
+
packageJsonPath: string;
|
|
391
|
+
declaredRange: string;
|
|
392
|
+
declaredIn: 'dependencies' | 'devDependencies';
|
|
393
|
+
} | {
|
|
394
|
+
kind: 'pass-no-hooks';
|
|
395
|
+
} | {
|
|
396
|
+
kind: 'pass-no-pkg';
|
|
397
|
+
hooksDir: string;
|
|
398
|
+
} | {
|
|
399
|
+
kind: 'pass-dogfood';
|
|
400
|
+
packageJsonPath: string;
|
|
401
|
+
} | {
|
|
402
|
+
kind: 'fail';
|
|
403
|
+
packageJsonPath: string;
|
|
404
|
+
hooksDir: string;
|
|
405
|
+
} | {
|
|
406
|
+
kind: 'fail-malformed';
|
|
407
|
+
packageJsonPath: string;
|
|
408
|
+
} | {
|
|
409
|
+
kind: 'fail-symlink';
|
|
410
|
+
packageJsonPath: string;
|
|
411
|
+
reason: string;
|
|
412
|
+
} | {
|
|
413
|
+
kind: 'fail-incompatible';
|
|
414
|
+
packageJsonPath: string;
|
|
415
|
+
declaredRange: string;
|
|
416
|
+
declaredIn: 'dependencies' | 'devDependencies';
|
|
417
|
+
currentCliVersion: string;
|
|
418
|
+
reason: string;
|
|
419
|
+
} | {
|
|
420
|
+
kind: 'fail-non-semver';
|
|
421
|
+
packageJsonPath: string;
|
|
422
|
+
declaredRange: string;
|
|
423
|
+
declaredIn: 'dependencies' | 'devDependencies';
|
|
424
|
+
reason: string;
|
|
425
|
+
};
|
|
426
|
+
export declare function checkSelfPinDeclared(baseDir: string, cliVersion?: string): Promise<SelfPinCheckResult>;
|
|
427
|
+
/**
|
|
428
|
+
* Synchronous variant of {@link checkSelfPinDeclared}. `rea doctor`
|
|
429
|
+
* runs all checks sync; the read+parse cost here is microseconds so
|
|
430
|
+
* the sync form is acceptable.
|
|
431
|
+
*
|
|
432
|
+
* R11-P3 (codex round 11): when `cliVersion` is provided, this check
|
|
433
|
+
* also verifies that the declared range admits the running CLI
|
|
434
|
+
* version (semver.satisfies). Without `cliVersion` the check
|
|
435
|
+
* reverts to presence-only behavior (backwards-compat for callers
|
|
436
|
+
* that don't yet pass the version). The doctor wrapper
|
|
437
|
+
* (`checkSelfPinDeclaredCheck`) passes `getPkgVersion()` so the
|
|
438
|
+
* skew detection always runs in the doctor surface.
|
|
439
|
+
*/
|
|
440
|
+
export declare function checkSelfPinDeclaredSync(baseDir: string, cliVersion?: string): SelfPinCheckResult;
|