@bookedsolid/rea 0.10.3 → 0.11.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/.husky/pre-push +22 -167
- package/agents/codex-adversarial.md +5 -3
- package/commands/codex-review.md +3 -5
- package/dist/audit/append.d.ts +7 -32
- package/dist/audit/append.js +7 -35
- package/dist/cli/audit.d.ts +0 -31
- package/dist/cli/audit.js +5 -74
- package/dist/cli/doctor.js +6 -16
- package/dist/cli/hook.d.ts +48 -0
- package/dist/cli/hook.js +127 -0
- package/dist/cli/index.js +5 -80
- package/dist/cli/init.js +1 -1
- package/dist/cli/install/gitignore.d.ts +2 -2
- package/dist/cli/install/gitignore.js +3 -3
- package/dist/cli/install/pre-push.d.ts +146 -271
- package/dist/cli/install/pre-push.js +471 -2633
- package/dist/cli/install/settings-merge.d.ts +17 -0
- package/dist/cli/install/settings-merge.js +48 -1
- package/dist/cli/upgrade.js +131 -3
- package/dist/config/tier-map.js +18 -25
- package/dist/hooks/push-gate/base.d.ts +57 -0
- package/dist/hooks/push-gate/base.js +77 -0
- package/dist/hooks/push-gate/codex-runner.d.ts +126 -0
- package/dist/hooks/push-gate/codex-runner.js +223 -0
- package/dist/hooks/push-gate/findings.d.ts +68 -0
- package/dist/hooks/push-gate/findings.js +142 -0
- package/dist/hooks/push-gate/halt.d.ts +28 -0
- package/dist/hooks/push-gate/halt.js +49 -0
- package/dist/hooks/push-gate/index.d.ts +90 -0
- package/dist/hooks/push-gate/index.js +351 -0
- package/dist/hooks/push-gate/policy.d.ts +41 -0
- package/dist/hooks/push-gate/policy.js +55 -0
- package/dist/hooks/push-gate/report.d.ts +89 -0
- package/dist/hooks/push-gate/report.js +140 -0
- package/dist/policy/loader.d.ts +10 -10
- package/dist/policy/loader.js +7 -6
- package/dist/policy/types.d.ts +31 -22
- package/package.json +1 -1
- package/dist/cache/review-cache.d.ts +0 -115
- package/dist/cache/review-cache.js +0 -200
- package/dist/cli/cache.d.ts +0 -84
- package/dist/cli/cache.js +0 -150
- package/dist/hooks/review-gate/args.d.ts +0 -126
- package/dist/hooks/review-gate/args.js +0 -315
- package/dist/hooks/review-gate/audit.d.ts +0 -131
- package/dist/hooks/review-gate/audit.js +0 -181
- package/dist/hooks/review-gate/banner.d.ts +0 -97
- package/dist/hooks/review-gate/banner.js +0 -172
- package/dist/hooks/review-gate/base-resolve.d.ts +0 -155
- package/dist/hooks/review-gate/base-resolve.js +0 -247
- package/dist/hooks/review-gate/cache-key.d.ts +0 -55
- package/dist/hooks/review-gate/cache-key.js +0 -41
- package/dist/hooks/review-gate/cache.d.ts +0 -108
- package/dist/hooks/review-gate/cache.js +0 -120
- package/dist/hooks/review-gate/constants.d.ts +0 -26
- package/dist/hooks/review-gate/constants.js +0 -34
- package/dist/hooks/review-gate/diff.d.ts +0 -181
- package/dist/hooks/review-gate/diff.js +0 -232
- package/dist/hooks/review-gate/errors.d.ts +0 -72
- package/dist/hooks/review-gate/errors.js +0 -100
- package/dist/hooks/review-gate/hash.d.ts +0 -43
- package/dist/hooks/review-gate/hash.js +0 -46
- package/dist/hooks/review-gate/index.d.ts +0 -31
- package/dist/hooks/review-gate/index.js +0 -35
- package/dist/hooks/review-gate/metadata.d.ts +0 -98
- package/dist/hooks/review-gate/metadata.js +0 -158
- package/dist/hooks/review-gate/policy.d.ts +0 -55
- package/dist/hooks/review-gate/policy.js +0 -71
- package/dist/hooks/review-gate/protected-paths.d.ts +0 -46
- package/dist/hooks/review-gate/protected-paths.js +0 -76
- package/hooks/_lib/push-review-core.sh +0 -1250
- package/hooks/commit-review-gate.sh +0 -330
- package/hooks/push-review-gate-git.sh +0 -94
- package/hooks/push-review-gate.sh +0 -92
|
@@ -1,335 +1,210 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pre-push hook installer (0.11.0 stateless push-gate).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* (`core.hooksPath` points at `.husky/`). Consumers who have never run
|
|
9
|
-
* `husky install`, or who have disabled husky entirely, would otherwise get
|
|
10
|
-
* ZERO pre-push enforcement — and the protected-path gate is exactly the
|
|
11
|
-
* thing we cannot let silently lapse.
|
|
4
|
+
* The 0.11.0 push-gate is a single 15-line shell stub that delegates to
|
|
5
|
+
* `rea hook push-gate` — no structural parsing of a bash body, no audit-log
|
|
6
|
+
* grep, no cache lookup. This module writes that stub into the right
|
|
7
|
+
* location and refuses to stomp foreign hooks.
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
* `push-review-gate.sh` logic the Claude Code hook already runs. The gate
|
|
15
|
-
* itself is shared — we do NOT duplicate its 700 lines.
|
|
9
|
+
* ## Install policy (decision tree)
|
|
16
10
|
*
|
|
17
|
-
*
|
|
11
|
+
* 1. `core.hooksPath` unset (vanilla git):
|
|
12
|
+
* → Install `.git/hooks/pre-push` (via `git rev-parse --git-path`). The
|
|
13
|
+
* `.husky/pre-push` file is shipped by `rea init` as a source-of-truth
|
|
14
|
+
* copy but is not consulted by git unless `core.hooksPath=.husky` is
|
|
15
|
+
* set.
|
|
18
16
|
*
|
|
19
|
-
*
|
|
20
|
-
* fallback
|
|
17
|
+
* 2. `core.hooksPath=.husky` (typical Husky 9 install):
|
|
18
|
+
* → Do NOT install the `.git/hooks/pre-push` fallback. `.husky/pre-push`
|
|
19
|
+
* is already rea's canonical gate and lives under the canonical copy
|
|
20
|
+
* module (see `src/cli/install/copy.ts`). `rea upgrade` refreshes it
|
|
21
|
+
* there.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* consulted by git directly.
|
|
23
|
+
* 3. `core.hooksPath` set to anything else, and a foreign pre-push lives
|
|
24
|
+
* under it:
|
|
25
|
+
* → Leave it alone, warn the operator, let `rea doctor` flag the gap.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* → Do NOT install. A hook is "governance-carrying" when it either
|
|
30
|
-
* carries our `FALLBACK_MARKER` (rea-managed) or execs / invokes
|
|
31
|
-
* `.claude/hooks/push-review-gate.sh` (consumer-wired delegation).
|
|
32
|
-
* This is the happy path for any project running husky 9+ that has
|
|
33
|
-
* wired the gate.
|
|
27
|
+
* 4. `core.hooksPath` set but the target directory has no pre-push:
|
|
28
|
+
* → Install the stub there — that's where git will look.
|
|
34
29
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
30
|
+
* Idempotency: every install writes a stable marker header. Re-running
|
|
31
|
+
* `rea init` / `rea upgrade` refreshes files carrying the marker and
|
|
32
|
+
* NEVER overwrites a hook without one. The marker comparison is
|
|
33
|
+
* anchored at byte 0 (exact line after the shebang), not a substring
|
|
34
|
+
* match — otherwise a comment or log output that happens to contain
|
|
35
|
+
* the marker text could cause a foreign hook to be reclassified as
|
|
36
|
+
* rea-managed and silently stomped.
|
|
40
37
|
*
|
|
41
|
-
*
|
|
42
|
-
* → Install into the configured hooksPath (as `pre-push`). This is the
|
|
43
|
-
* "hooksPath is set but nothing lives there yet" case. The active
|
|
44
|
-
* hook directory has changed; we install where git will actually look.
|
|
38
|
+
* ## Stub body
|
|
45
39
|
*
|
|
46
|
-
*
|
|
47
|
-
* (`# rea:pre-push-fallback v1`). Re-running `rea init` detects the header
|
|
48
|
-
* by ANCHORED match (exact second line after the shebang) and refreshes in
|
|
49
|
-
* place; it NEVER overwrites a hook without our marker — if the consumer
|
|
50
|
-
* has their own pre-push already, we warn and skip. Substring matches are
|
|
51
|
-
* deliberately rejected: a consumer comment, a grep log, or copy-pasted
|
|
52
|
-
* snippet containing the sentinel must not reclassify a foreign file as
|
|
53
|
-
* rea-managed.
|
|
40
|
+
* The body is 15 lines of POSIX sh:
|
|
54
41
|
*
|
|
55
|
-
*
|
|
42
|
+
* - If `.rea/HALT` exists, print the reason and exit 1.
|
|
43
|
+
* - Otherwise `exec rea hook push-gate`, which runs `codex exec review`
|
|
44
|
+
* against the diff and exits 0/1/2 accordingly.
|
|
56
45
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* still points at `.git/hooks/`. No enforcement.
|
|
61
|
-
* - Consumer deliberately uses `core.hooksPath=./custom-hooks` with a
|
|
62
|
-
* different tool. `.husky/pre-push` is dead weight.
|
|
63
|
-
* - CI or release automation disables husky via `HUSKY=0`. Again, no
|
|
64
|
-
* enforcement at push time.
|
|
65
|
-
*
|
|
66
|
-
* The protected-path Codex audit requirement is too important to let any
|
|
67
|
-
* of those slip through silently. See THREAT_MODEL.md §Governance for the
|
|
68
|
-
* full rationale.
|
|
46
|
+
* All real work lives in `src/hooks/push-gate/index.ts`. Keeping the
|
|
47
|
+
* shell body minimal means the only things that could regress are HALT
|
|
48
|
+
* detection and the exec path — both trivially testable.
|
|
69
49
|
*/
|
|
70
50
|
/**
|
|
71
|
-
* Marker baked into every rea-installed fallback pre-push hook.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
51
|
+
* Marker baked into every rea-installed fallback pre-push hook. Anchored on
|
|
52
|
+
* the second line of the file (immediately after the shebang) for
|
|
53
|
+
* classification. Bump the version suffix whenever the body semantics
|
|
54
|
+
* change so upgrades can migrate old installs cleanly.
|
|
74
55
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* see `isReaManagedFallback` for the anchored form required to classify
|
|
78
|
-
* a file as rea-managed.
|
|
56
|
+
* v2 — 0.11.0 stateless push-gate body (no bash core, no audit grep).
|
|
57
|
+
* v1 — 0.10.x and prior, delegated to `.claude/hooks/push-review-gate.sh`.
|
|
79
58
|
*/
|
|
80
|
-
export declare const FALLBACK_MARKER = "# rea:pre-push-fallback
|
|
59
|
+
export declare const FALLBACK_MARKER = "# rea:pre-push-fallback v2";
|
|
60
|
+
/** Legacy v1 marker — used by upgrade migration to detect old installs. */
|
|
61
|
+
export declare const LEGACY_FALLBACK_MARKER_V1 = "# rea:pre-push-fallback v1";
|
|
81
62
|
/**
|
|
82
|
-
* Marker present in the shipped `.husky/pre-push` governance gate.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* as rea-managed and then silently overwritten. See `isReaManagedHuskyGate`
|
|
87
|
-
* for the anchored check.
|
|
63
|
+
* Marker present in the shipped `.husky/pre-push` governance gate. The
|
|
64
|
+
* second line of the shipped husky hook is this marker — rea upgrade
|
|
65
|
+
* detects it to refresh in-place. Bump the suffix whenever the body
|
|
66
|
+
* changes; pre-0.11 markers live in `LEGACY_HUSKY_GATE_MARKER_V1`.
|
|
88
67
|
*/
|
|
89
|
-
export declare const HUSKY_GATE_MARKER = "# rea:husky-pre-push-gate
|
|
68
|
+
export declare const HUSKY_GATE_MARKER = "# rea:husky-pre-push-gate v2";
|
|
69
|
+
/** Legacy v1 husky marker for migration. */
|
|
70
|
+
export declare const LEGACY_HUSKY_GATE_MARKER_V1 = "# rea:husky-pre-push-gate v1";
|
|
90
71
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* A genuine rea Husky gate always carries both. The marker is versioned so it
|
|
95
|
-
* can be bumped if the gate implementation changes significantly.
|
|
72
|
+
* Body-level marker so a hook that carries the header marker but has an
|
|
73
|
+
* empty body (stubbed out by a consumer) is NOT classified as rea-managed.
|
|
74
|
+
* A real rea hook always carries both markers.
|
|
96
75
|
*/
|
|
97
|
-
export declare const HUSKY_GATE_BODY_MARKER = "# rea:gate-body-
|
|
76
|
+
export declare const HUSKY_GATE_BODY_MARKER = "# rea:gate-body-v2";
|
|
77
|
+
/** Legacy body marker — used by upgrade migration detection. */
|
|
78
|
+
export declare const LEGACY_HUSKY_GATE_BODY_MARKER_V1 = "# rea:gate-body-v1";
|
|
79
|
+
/** Fallback hook body — `.git/hooks/pre-push` in vanilla-git installs. */
|
|
80
|
+
export declare function fallbackHookContent(): string;
|
|
81
|
+
/** Husky hook body — `.husky/pre-push` when hooksPath=.husky. */
|
|
82
|
+
export declare function huskyHookContent(): string;
|
|
98
83
|
/**
|
|
99
|
-
* True when `content` starts with the exact rea fallback prelude
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* and no interposed blank lines. Anything else is foreign.
|
|
103
|
-
*
|
|
104
|
-
* Rejecting a substring match is what stops a consumer comment like
|
|
105
|
-
* `# Hint: the old rea:pre-push-fallback v1 marker moved into .husky/` from
|
|
106
|
-
* accidentally classifying a user's own hook as rea-managed and then
|
|
107
|
-
* getting overwritten on the next `rea init`.
|
|
84
|
+
* True when `content` starts with the exact rea fallback prelude (shebang
|
|
85
|
+
* + v2 marker). Strict: the marker must be on line 2, nothing interposed,
|
|
86
|
+
* no leading whitespace. Substring matches are deliberately rejected.
|
|
108
87
|
*/
|
|
109
88
|
export declare function isReaManagedFallback(content: string): boolean;
|
|
110
89
|
/**
|
|
111
|
-
* True when `content`
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
90
|
+
* True when `content` is the legacy v1 fallback (`.git/hooks/pre-push`
|
|
91
|
+
* that delegated to `.claude/hooks/push-review-gate.sh`). Used by `rea
|
|
92
|
+
* upgrade` to migrate — we overwrite these unconditionally because we
|
|
93
|
+
* control the entire body shape.
|
|
94
|
+
*/
|
|
95
|
+
export declare function isLegacyReaManagedFallback(content: string): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* True when `content` carries the rea Husky gate markers in the canonical
|
|
98
|
+
* positions — shebang on line 1, `HUSKY_GATE_MARKER` on line 2,
|
|
99
|
+
* `HUSKY_GATE_BODY_MARKER` on line 3.
|
|
100
|
+
*
|
|
101
|
+
* Why three anchored lines instead of a substring search: the 0.10.x
|
|
102
|
+
* implementation lived in ~2000 lines of structural parser because the old
|
|
103
|
+
* body varied. The 0.11.0 body is hand-templated and stable — anchored
|
|
104
|
+
* matching on three fixed lines closes the classification question with
|
|
105
|
+
* six comparisons.
|
|
125
106
|
*/
|
|
126
107
|
export declare function isReaManagedHuskyGate(content: string): boolean;
|
|
127
108
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* (`# rea:husky-pre-push-gate v1` / `# rea:gate-body-v1`) introduced in
|
|
131
|
-
* 0.4.
|
|
132
|
-
*
|
|
133
|
-
* Codex R21 F1: without this detector, any consumer upgrading from a rea
|
|
134
|
-
* release that shipped the pre-marker hook fell into `foreign/no-marker`.
|
|
135
|
-
* `classifyPrePushInstall` mapped that to `skip/foreign-pre-push` and
|
|
136
|
-
* `rea init` refused to touch the file. `rea doctor` reported
|
|
137
|
-
* `activeForeign=true`. Users had no self-heal path short of manually
|
|
138
|
-
* deleting the hook — which is a bad migration story for a governance
|
|
139
|
-
* primitive that they are supposed to trust.
|
|
140
|
-
*
|
|
141
|
-
* Shape-level detection:
|
|
142
|
-
* 1. Line 2 is the canonical pre-0.4 filename header
|
|
143
|
-
* `# .husky/pre-push — rea governance gate for terminal-initiated pushes.`
|
|
144
|
-
* This header shipped verbatim across the 0.2.x/0.3.x rea releases.
|
|
145
|
-
* 2. Real governance still present — `hasHaltEnforcement(content)` AND
|
|
146
|
-
* `hasAuditCheck(content)` both pass. A stub that only matches the
|
|
147
|
-
* header comment (no enforcement) fails the shape check and stays
|
|
148
|
-
* classified as foreign.
|
|
149
|
-
*
|
|
150
|
-
* Classification consequence: `classifyExistingHook` returns
|
|
151
|
-
* `rea-managed-husky` for legacy matches. `classifyPrePushInstall` maps
|
|
152
|
-
* that to `skip/active-pre-push-present` — `rea init` does not touch the
|
|
153
|
-
* hook (correctness: the file IS still functional governance), but
|
|
154
|
-
* `inspectPrePushState` reports `ok=true, activeForeign=false` so doctor
|
|
155
|
-
* stops flagging it. The canonical-manifest-driven upgrade path
|
|
156
|
-
* (`rea upgrade`) detects the hash mismatch against the packaged
|
|
157
|
-
* `.husky/pre-push` and surfaces the legacy shape as drift, letting the
|
|
158
|
-
* operator opt into the refresh explicitly.
|
|
109
|
+
* True when `content` is the legacy v1 Husky gate (`.husky/pre-push` from
|
|
110
|
+
* 0.10.x and earlier). Used to trigger the upgrade migration.
|
|
159
111
|
*/
|
|
160
112
|
export declare function isLegacyReaManagedHuskyGate(content: string): boolean;
|
|
161
113
|
/**
|
|
162
|
-
* True when `content`
|
|
163
|
-
* `push-
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* - Quoted/expanded path prefix: `exec "$REA_ROOT"/.claude/hooks/push-review-gate.sh "$@"`
|
|
175
|
-
* — double- or single-quoted variable expansions before the literal path
|
|
176
|
-
* are treated as part of the path, not as a mention context.
|
|
177
|
-
* - Trailing `;` after `exec <gate>`: `exec gate.sh "$@";` — exec replaces
|
|
178
|
-
* the shell, so the `;` and anything after it never runs; gate exit IS
|
|
179
|
-
* the hook's exit status.
|
|
180
|
-
* - Variable indirection: `GATE=<path-containing-gate>` on one line plus
|
|
181
|
-
* `exec "$GATE"` / `. "$GATE"` / etc. on a later line.
|
|
182
|
-
*
|
|
183
|
-
* Rejects:
|
|
184
|
-
* - Comment lines starting with `#`
|
|
185
|
-
* - Shell tests: `[ -x .claude/hooks/push-review-gate.sh ]`
|
|
186
|
-
* - File tests: `test -f .claude/hooks/push-review-gate.sh`
|
|
187
|
-
* - Chmod / cp / mv / cat / printf / echo mentioning the path
|
|
188
|
-
* - String literals inside quoted arguments to non-invocation commands
|
|
189
|
-
* - Invocations inside `if`/`for`/`while`/`case` blocks (conditional —
|
|
190
|
-
* not guaranteed to run)
|
|
191
|
-
* - Invocations after an unconditional top-level `exit`
|
|
192
|
-
* - Non-`exec` invocations followed by `||`, `&&`, `;`, or trailing `&`
|
|
193
|
-
* (status-swallowing operators)
|
|
194
|
-
*
|
|
195
|
-
* This is a pragmatic heuristic, not a full shell parser. R12 F2 broadened
|
|
196
|
-
* the allowlist to match the forms Codex flagged as valid but previously
|
|
197
|
-
* rejected; narrower patterns silently hard-failed `rea doctor` on
|
|
198
|
-
* correctly-governed consumer repos.
|
|
114
|
+
* True when `content` looks like a user-authored pre-push that still
|
|
115
|
+
* invokes `rea hook push-gate` (a legitimate governance-carrying custom
|
|
116
|
+
* hook). We don't attempt to parse control flow — the 0.10.x attempt at
|
|
117
|
+
* that produced 800 lines of heuristics that still had gaps. Instead, we
|
|
118
|
+
* match only on the substring `rea hook push-gate` preceded by one of
|
|
119
|
+
* `exec`, `$(`, \``, `;`, or line-start whitespace. A comment containing
|
|
120
|
+
* the phrase does NOT qualify (leading `#`).
|
|
121
|
+
*
|
|
122
|
+
* Governance-carrying is a soft signal: `rea doctor` uses it to print
|
|
123
|
+
* "external (delegates to rea hook push-gate)" rather than "foreign".
|
|
124
|
+
* `classifyPrePushInstall` maps it to "skip / active-pre-push-present"
|
|
125
|
+
* — we don't overwrite consumer-authored hooks that respect the gate.
|
|
199
126
|
*/
|
|
200
127
|
export declare function referencesReviewGate(content: string): boolean;
|
|
201
|
-
/**
|
|
202
|
-
* Resolve a configured `core.hooksPath` (possibly relative) to an absolute
|
|
203
|
-
* path relative to `targetDir`, or `null` if the key is unset.
|
|
204
|
-
*/
|
|
205
128
|
export declare function resolveHooksDir(targetDir: string): Promise<{
|
|
206
129
|
dir: string | null;
|
|
207
130
|
configured: boolean;
|
|
208
131
|
}>;
|
|
209
|
-
export type
|
|
210
|
-
|
|
211
|
-
{
|
|
132
|
+
export type ClassifyExistingHook = {
|
|
133
|
+
kind: 'absent';
|
|
134
|
+
} | {
|
|
135
|
+
kind: 'rea-managed';
|
|
136
|
+
} | {
|
|
137
|
+
kind: 'rea-managed-legacy-v1';
|
|
138
|
+
} | {
|
|
139
|
+
kind: 'rea-managed-husky';
|
|
140
|
+
} | {
|
|
141
|
+
kind: 'rea-managed-husky-legacy-v1';
|
|
142
|
+
} | {
|
|
143
|
+
kind: 'gate-delegating';
|
|
144
|
+
} | {
|
|
145
|
+
kind: 'foreign';
|
|
146
|
+
reason: string;
|
|
147
|
+
};
|
|
148
|
+
export declare function classifyExistingHook(hookPath: string): Promise<ClassifyExistingHook>;
|
|
149
|
+
export type InstallDecision = {
|
|
212
150
|
action: 'skip';
|
|
213
151
|
reason: 'active-pre-push-present';
|
|
214
152
|
hookPath: string;
|
|
215
|
-
}
|
|
216
|
-
/** Consumer owns a non-rea pre-push; refusing to stomp it. */
|
|
217
|
-
| {
|
|
153
|
+
} | {
|
|
218
154
|
action: 'skip';
|
|
219
155
|
reason: 'foreign-pre-push';
|
|
220
156
|
hookPath: string;
|
|
221
|
-
}
|
|
222
|
-
/** Write a fresh hook. */
|
|
223
|
-
| {
|
|
157
|
+
} | {
|
|
224
158
|
action: 'install';
|
|
225
159
|
hookPath: string;
|
|
226
|
-
}
|
|
227
|
-
/** Refresh an existing rea-managed hook (marker match). */
|
|
228
|
-
| {
|
|
160
|
+
} | {
|
|
229
161
|
action: 'refresh';
|
|
230
162
|
hookPath: string;
|
|
231
163
|
};
|
|
232
|
-
/**
|
|
233
|
-
* Classify what we should do at `targetDir` based on current state. Pure —
|
|
234
|
-
* reads the filesystem and git config but performs no writes. Split out so
|
|
235
|
-
* tests can drive every branch without going through the write path.
|
|
236
|
-
*
|
|
237
|
-
* NOTE: The result is a snapshot. `installPrePushFallback` re-resolves and
|
|
238
|
-
* re-classifies immediately before writing to defend against a husky
|
|
239
|
-
* install or concurrent `rea init` running between classify and write.
|
|
240
|
-
*/
|
|
241
164
|
export declare function classifyPrePushInstall(targetDir: string): Promise<InstallDecision>;
|
|
242
165
|
export interface PrePushInstallResult {
|
|
243
166
|
decision: InstallDecision;
|
|
244
|
-
/** Absolute path of the file written, if any. */
|
|
245
167
|
written?: string;
|
|
246
|
-
/** User-facing warnings accumulated during install. */
|
|
247
168
|
warnings: string[];
|
|
248
169
|
}
|
|
249
|
-
export interface
|
|
250
|
-
|
|
251
|
-
* R25 F2 — set to true when the install path had to use a non-atomic
|
|
252
|
-
* fallback (copyFile after link() refused). Callers surface this as a
|
|
253
|
-
* warning to the operator so they know publication was best-effort on
|
|
254
|
-
* this filesystem rather than atomic.
|
|
255
|
-
*/
|
|
256
|
-
degradedFromAtomic: boolean;
|
|
170
|
+
export interface InstallPrePushFallbackOptions {
|
|
171
|
+
targetDir: string;
|
|
257
172
|
}
|
|
258
173
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
174
|
+
* Install (or refresh) the `.git/hooks/pre-push` stub when there is no
|
|
175
|
+
* active governance-carrying hook. Never overwrites foreign hooks.
|
|
176
|
+
*
|
|
177
|
+
* Concurrency: a proper-lockfile-based advisory lock on the git common-dir
|
|
178
|
+
* serializes concurrent installs (two `rea init` runs in the same repo).
|
|
179
|
+
* Atomicity: fresh installs use `link(2)` (atomic create-or-fail); refresh
|
|
180
|
+
* uses `rename(2)` with a dev+ino+mtime guard against mid-write
|
|
181
|
+
* replacement.
|
|
261
182
|
*/
|
|
262
|
-
export
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
* partner drop a file in between those two steps so we can assert on
|
|
274
|
-
* the re-check behavior. Invoked with the classified target path.
|
|
275
|
-
* Production callers never set this.
|
|
276
|
-
*/
|
|
277
|
-
onBeforeReresolve?: (hookPath: string) => Promise<void> | void;
|
|
278
|
-
/**
|
|
279
|
-
* Called inside the lock, after the safety re-check passes but
|
|
280
|
-
* immediately before `writeExecutable`. Test-only seam: creates a
|
|
281
|
-
* file at the hook path to exercise the EEXIST-from-link path that
|
|
282
|
-
* guards the remaining TOCTOU window. Production callers never set this.
|
|
283
|
-
*/
|
|
284
|
-
onBeforeWrite?: (hookPath: string) => Promise<void> | void;
|
|
183
|
+
export declare function installPrePushFallback(options: InstallPrePushFallbackOptions): Promise<PrePushInstallResult>;
|
|
184
|
+
export interface PrePushCandidate {
|
|
185
|
+
path: string;
|
|
186
|
+
exists: boolean;
|
|
187
|
+
executable: boolean;
|
|
188
|
+
/** The marker classification of the file (if it exists). */
|
|
189
|
+
kind?: ClassifyExistingHook['kind'];
|
|
190
|
+
/** True when a rea-authored marker is present. */
|
|
191
|
+
reaManaged?: boolean;
|
|
192
|
+
/** True when the body contains `rea hook push-gate`. */
|
|
193
|
+
delegatesToGate?: boolean;
|
|
285
194
|
}
|
|
286
|
-
/**
|
|
287
|
-
* Install (or refresh, or skip) the fallback pre-push hook at `targetDir`.
|
|
288
|
-
* Idempotent: safe to call on every `rea init`, including re-runs over an
|
|
289
|
-
* existing install. Never overwrites a foreign hook.
|
|
290
|
-
*
|
|
291
|
-
* Requires `targetDir/.git` to exist. Non-git directories are skipped with
|
|
292
|
-
* a warning — same shape as `installCommitMsgHook`.
|
|
293
|
-
*/
|
|
294
|
-
export declare function installPrePushFallback(targetDir: string, options?: InstallPrePushOptions): Promise<PrePushInstallResult>;
|
|
295
|
-
/**
|
|
296
|
-
* Doctor check: at least one pre-push hook (Husky OR git fallback OR the
|
|
297
|
-
* configured hooksPath location) must exist AND be executable AND carry
|
|
298
|
-
* governance (rea marker or gate delegation). Returns a small record the
|
|
299
|
-
* doctor module can turn into a CheckResult.
|
|
300
|
-
*
|
|
301
|
-
* "Executable" is defined as having any of the user/group/other exec bits
|
|
302
|
-
* set, matching the existing `checkHooksInstalled` convention. A file that
|
|
303
|
-
* is executable but does not wire the Codex review gate is intentionally
|
|
304
|
-
* classified as non-governing: `ok=false` + `activeForeign=true`, which
|
|
305
|
-
* doctor turns into a `warn`, not a `pass`.
|
|
306
|
-
*/
|
|
307
195
|
export interface PrePushDoctorState {
|
|
308
|
-
/** Every candidate path we consulted, with its live status on disk. */
|
|
309
|
-
candidates: Array<{
|
|
310
|
-
path: string;
|
|
311
|
-
exists: boolean;
|
|
312
|
-
executable: boolean;
|
|
313
|
-
/** `true` when the file content carries our anchored rea prelude. */
|
|
314
|
-
reaManaged: boolean;
|
|
315
|
-
/** `true` when the body references the shared review gate. */
|
|
316
|
-
delegatesToGate: boolean;
|
|
317
|
-
}>;
|
|
318
|
-
/**
|
|
319
|
-
* The candidate path git would actually fire right now, given current
|
|
320
|
-
* `core.hooksPath`. May or may not exist.
|
|
321
|
-
*/
|
|
322
|
-
activePath: string;
|
|
323
|
-
/**
|
|
324
|
-
* True when the active candidate exists, is executable, AND carries
|
|
325
|
-
* governance (rea marker OR references the review gate).
|
|
326
|
-
*/
|
|
327
196
|
ok: boolean;
|
|
197
|
+
activePath: string | null;
|
|
328
198
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
199
|
+
* Foreign file detected at the active path — present + executable but
|
|
200
|
+
* neither rea-managed nor gate-delegating. Treated as a hard fail
|
|
201
|
+
* (silent-bypass risk).
|
|
332
202
|
*/
|
|
333
203
|
activeForeign: boolean;
|
|
204
|
+
candidates: PrePushCandidate[];
|
|
334
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Read-only probe used by `rea doctor`. Inspects every plausible hook
|
|
208
|
+
* location and reports whether governance is active.
|
|
209
|
+
*/
|
|
335
210
|
export declare function inspectPrePushState(targetDir: string): Promise<PrePushDoctorState>;
|