@bookedsolid/rea 0.43.0 → 0.44.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/dist/cli/init.d.ts +71 -1
- package/dist/cli/init.js +206 -6
- package/package.json +1 -1
package/dist/cli/init.d.ts
CHANGED
|
@@ -59,6 +59,29 @@ export interface ResolvedConfig {
|
|
|
59
59
|
reagentPolicyPath: string | null;
|
|
60
60
|
reagentNotices: string[];
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 0.44.0 charter item 1: derive the canonical hook filename set the
|
|
64
|
+
* installer will lay down. Union of:
|
|
65
|
+
*
|
|
66
|
+
* - `EXPECTED_HOOKS` (the doctor's required-on-disk list — source of
|
|
67
|
+
* truth for "what `.claude/hooks/` must contain after install").
|
|
68
|
+
* - The `command` paths of every entry in `defaultDesiredHooks()`
|
|
69
|
+
* (the source of truth for "what `.claude/settings.json` registers
|
|
70
|
+
* with Claude Code"). Each command path ends in
|
|
71
|
+
* `.claude/hooks/<name>.sh`; we extract `<name>.sh` so the result
|
|
72
|
+
* joins cleanly with `EXPECTED_HOOKS`.
|
|
73
|
+
*
|
|
74
|
+
* Pre-0.44.0 `buildInstallSummary` hard-coded a hook count / list. If
|
|
75
|
+
* a new hook was added to `EXPECTED_HOOKS` (e.g. `delegation-advisory`
|
|
76
|
+
* was promoted in 0.36.0) or registered in `defaultDesiredHooks()`
|
|
77
|
+
* without anyone touching the summary, the operator's confirm screen
|
|
78
|
+
* silently lied about what was about to be installed. This helper
|
|
79
|
+
* means the summary now tracks the real installer surface — adding
|
|
80
|
+
* a hook to either canonical source automatically updates the screen.
|
|
81
|
+
*
|
|
82
|
+
* Sorted + deduped so the screen is stable across orderings.
|
|
83
|
+
*/
|
|
84
|
+
export declare function canonicalInstalledHooks(): string[];
|
|
62
85
|
/**
|
|
63
86
|
* 0.43.0 UX polish: build the human-readable install summary shown
|
|
64
87
|
* BEFORE any files are written. Lists, in order: the policy file
|
|
@@ -67,6 +90,11 @@ export interface ResolvedConfig {
|
|
|
67
90
|
* installer will ACTUALLY do given the target tree's shape), and
|
|
68
91
|
* whether re-run preservation is active.
|
|
69
92
|
*
|
|
93
|
+
* 0.44.0 charter item 1: hook count + listing is derived from the
|
|
94
|
+
* canonical hook resolvers via {@link canonicalInstalledHooks}, NOT
|
|
95
|
+
* hard-coded. Adding a hook to `EXPECTED_HOOKS` or
|
|
96
|
+
* `defaultDesiredHooks()` automatically reflects in this screen.
|
|
97
|
+
*
|
|
70
98
|
* Rendered via clack's `note` primitive so it sits in a bordered block
|
|
71
99
|
* adjacent to the final `confirm` gate. The string is also returned
|
|
72
100
|
* verbatim so the test suite can assert content without mocking clack.
|
|
@@ -99,12 +127,52 @@ export interface TargetState {
|
|
|
99
127
|
* and the summary was only slightly stale.
|
|
100
128
|
*/
|
|
101
129
|
export declare function detectTargetState(targetDir: string): TargetState;
|
|
130
|
+
/**
|
|
131
|
+
* 0.44.0 charter item 2: detect filesystems where Unix mode bits are
|
|
132
|
+
* unreliable (Windows-class FSes, WSL/native crossings, some network
|
|
133
|
+
* mounts). On these, `stat.mode` for a freshly-installed `.sh` either
|
|
134
|
+
* reads back without the `0o111` exec bit set, or is zeroed entirely.
|
|
135
|
+
*
|
|
136
|
+
* Pre-fix `postInstallVerify` hard-failed the install when zero `.sh`
|
|
137
|
+
* files had the exec bit — every Windows install thus produced a
|
|
138
|
+
* false-positive "0 executable .sh files" warning even on a perfectly
|
|
139
|
+
* healthy install. We now treat exec-bit checks as advisory on these
|
|
140
|
+
* filesystems and still verify the more meaningful invariant: the
|
|
141
|
+
* files exist and have non-empty bytes.
|
|
142
|
+
*
|
|
143
|
+
* Detection strategy — two layers, either sufficient:
|
|
144
|
+
*
|
|
145
|
+
* 1. Platform — `process.platform === 'win32'` always skips the
|
|
146
|
+
* exec-bit check (native Windows has no POSIX mode bit; node's
|
|
147
|
+
* `stat.mode` is a translation that may or may not preserve the
|
|
148
|
+
* 0o111 bit depending on the source).
|
|
149
|
+
* 2. Sample — even on Linux/macOS, when crossing into a Windows-
|
|
150
|
+
* backed filesystem (WSL bind-mount onto `/mnt/c/`, an SMB
|
|
151
|
+
* share, etc.), `stat.mode` returns a value whose `0o777`
|
|
152
|
+
* portion is zero. We detect this by sampling the FIRST `.sh`
|
|
153
|
+
* file in the hooks directory and checking whether ANY of the
|
|
154
|
+
* `0o777` bits are set; if none are, treat as mode-less.
|
|
155
|
+
*
|
|
156
|
+
* Returns true when the exec-bit check should be SKIPPED.
|
|
157
|
+
*
|
|
158
|
+
* Exported for testability — callers can stub the filesystem and
|
|
159
|
+
* exercise both shapes (mode-aware vs mode-less) without spinning
|
|
160
|
+
* up an actual Windows VM.
|
|
161
|
+
*/
|
|
162
|
+
export declare function isModeLessFilesystem(hooksDir: string): boolean;
|
|
102
163
|
/**
|
|
103
164
|
* 0.43.0 UX polish: post-install sanity check. Runs synchronously
|
|
104
165
|
* after the file-write phase to catch installs that completed
|
|
105
166
|
* "successfully" but are missing a critical artifact (write
|
|
106
167
|
* permissions issue, partial copy, etc.).
|
|
107
168
|
*
|
|
169
|
+
* 0.44.0 charter item 2: exec-bit check is skipped on mode-less
|
|
170
|
+
* filesystems (Windows / WSL-crossing / SMB mounts). When skipped, we
|
|
171
|
+
* still verify the files exist + are non-empty — that's the invariant
|
|
172
|
+
* a partial-copy or zero-byte write would actually violate. The skip
|
|
173
|
+
* is annotated in the returned advisory so the operator knows why a
|
|
174
|
+
* check they expected to run didn't.
|
|
175
|
+
*
|
|
108
176
|
* Strictly read-only — no probes that touch python3 / jq / codex.
|
|
109
177
|
* Pattern modelled on the synthetic round-trip checks established by
|
|
110
178
|
* `checkDelegationRoundTrip` in 0.29.0/0.31.0: cheap, in-process,
|
|
@@ -112,7 +180,9 @@ export declare function detectTargetState(targetDir: string): TargetState;
|
|
|
112
180
|
* that bites first-time consumers hardest. For deep diagnostics
|
|
113
181
|
* point the operator at `rea doctor`.
|
|
114
182
|
*
|
|
115
|
-
* Returns the list of issues found (empty = healthy).
|
|
183
|
+
* Returns the list of issues found (empty = healthy). Advisory
|
|
184
|
+
* (skipped-check) lines are prefixed with `advisory:` so the caller
|
|
185
|
+
* can distinguish them from real issues if desired. The caller
|
|
116
186
|
* surfaces them via clack's `log.warn` and points the operator at
|
|
117
187
|
* `rea doctor` for follow-up.
|
|
118
188
|
*/
|
package/dist/cli/init.js
CHANGED
|
@@ -8,6 +8,7 @@ import { HARD_DEFAULTS, loadProfile, mergeProfiles } from '../policy/profiles.js
|
|
|
8
8
|
import { copyArtifacts } from './install/copy.js';
|
|
9
9
|
import { ensureReaGitignore } from './install/gitignore.js';
|
|
10
10
|
import { canonicalSettingsSubsetHash, defaultDesiredHooks, mergeSettings, readSettings, writeSettingsAtomic, } from './install/settings-merge.js';
|
|
11
|
+
import { EXPECTED_HOOKS } from './doctor.js';
|
|
11
12
|
import { installCommitMsgHook } from './install/commit-msg.js';
|
|
12
13
|
import { installPrepareCommitMsgHook } from './install/prepare-commit-msg.js';
|
|
13
14
|
import { installPrePushFallback } from './install/pre-push.js';
|
|
@@ -813,6 +814,44 @@ function readExistingManifestInstalledAt(manifestPath) {
|
|
|
813
814
|
}
|
|
814
815
|
return undefined;
|
|
815
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* 0.44.0 charter item 1: derive the canonical hook filename set the
|
|
819
|
+
* installer will lay down. Union of:
|
|
820
|
+
*
|
|
821
|
+
* - `EXPECTED_HOOKS` (the doctor's required-on-disk list — source of
|
|
822
|
+
* truth for "what `.claude/hooks/` must contain after install").
|
|
823
|
+
* - The `command` paths of every entry in `defaultDesiredHooks()`
|
|
824
|
+
* (the source of truth for "what `.claude/settings.json` registers
|
|
825
|
+
* with Claude Code"). Each command path ends in
|
|
826
|
+
* `.claude/hooks/<name>.sh`; we extract `<name>.sh` so the result
|
|
827
|
+
* joins cleanly with `EXPECTED_HOOKS`.
|
|
828
|
+
*
|
|
829
|
+
* Pre-0.44.0 `buildInstallSummary` hard-coded a hook count / list. If
|
|
830
|
+
* a new hook was added to `EXPECTED_HOOKS` (e.g. `delegation-advisory`
|
|
831
|
+
* was promoted in 0.36.0) or registered in `defaultDesiredHooks()`
|
|
832
|
+
* without anyone touching the summary, the operator's confirm screen
|
|
833
|
+
* silently lied about what was about to be installed. This helper
|
|
834
|
+
* means the summary now tracks the real installer surface — adding
|
|
835
|
+
* a hook to either canonical source automatically updates the screen.
|
|
836
|
+
*
|
|
837
|
+
* Sorted + deduped so the screen is stable across orderings.
|
|
838
|
+
*/
|
|
839
|
+
export function canonicalInstalledHooks() {
|
|
840
|
+
const fromExpected = new Set(EXPECTED_HOOKS);
|
|
841
|
+
for (const group of defaultDesiredHooks()) {
|
|
842
|
+
for (const h of group.hooks) {
|
|
843
|
+
const cmd = h.command;
|
|
844
|
+
// Commands have shape `"$CLAUDE_PROJECT_DIR"/.claude/hooks/<name>.sh`.
|
|
845
|
+
// Take the basename (everything after the last `/`). Robust against
|
|
846
|
+
// future path changes — only the filename matters here.
|
|
847
|
+
const slashIdx = cmd.lastIndexOf('/');
|
|
848
|
+
const basename = slashIdx >= 0 ? cmd.slice(slashIdx + 1) : cmd;
|
|
849
|
+
if (basename.endsWith('.sh'))
|
|
850
|
+
fromExpected.add(basename);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return Array.from(fromExpected).sort();
|
|
854
|
+
}
|
|
816
855
|
/**
|
|
817
856
|
* 0.43.0 UX polish: build the human-readable install summary shown
|
|
818
857
|
* BEFORE any files are written. Lists, in order: the policy file
|
|
@@ -821,6 +860,11 @@ function readExistingManifestInstalledAt(manifestPath) {
|
|
|
821
860
|
* installer will ACTUALLY do given the target tree's shape), and
|
|
822
861
|
* whether re-run preservation is active.
|
|
823
862
|
*
|
|
863
|
+
* 0.44.0 charter item 1: hook count + listing is derived from the
|
|
864
|
+
* canonical hook resolvers via {@link canonicalInstalledHooks}, NOT
|
|
865
|
+
* hard-coded. Adding a hook to `EXPECTED_HOOKS` or
|
|
866
|
+
* `defaultDesiredHooks()` automatically reflects in this screen.
|
|
867
|
+
*
|
|
824
868
|
* Rendered via clack's `note` primitive so it sits in a bordered block
|
|
825
869
|
* adjacent to the final `confirm` gate. The string is also returned
|
|
826
870
|
* verbatim so the test suite can assert content without mocking clack.
|
|
@@ -843,7 +887,16 @@ export function buildInstallSummary(targetDir, config, reRunMode, targetState) {
|
|
|
843
887
|
lines.push(` .rea/registry.yaml — empty MCP-server registry`);
|
|
844
888
|
lines.push(` .rea/install-manifest.json — hash record for drift detection`);
|
|
845
889
|
lines.push(` .claude/agents/ — curated specialist agents`);
|
|
846
|
-
|
|
890
|
+
// 0.44.0 charter item 1: hook count derived from the canonical
|
|
891
|
+
// resolvers (EXPECTED_HOOKS + defaultDesiredHooks). Pre-fix this
|
|
892
|
+
// line read `.claude/hooks/ — hook scripts (executable)`
|
|
893
|
+
// with no count, so adding a new hook silently changed the install
|
|
894
|
+
// surface without surfacing in the operator's confirm screen.
|
|
895
|
+
const hookNames = canonicalInstalledHooks();
|
|
896
|
+
lines.push(` .claude/hooks/ — ${hookNames.length} hook scripts (executable):`);
|
|
897
|
+
for (const name of hookNames) {
|
|
898
|
+
lines.push(` ${name}`);
|
|
899
|
+
}
|
|
847
900
|
lines.push(` .claude/commands/ — slash commands`);
|
|
848
901
|
lines.push(` .claude/settings.json — hook registration entries`);
|
|
849
902
|
// 0.43.0 codex round-1 P3: the installer writes to `.git/hooks/*`
|
|
@@ -908,12 +961,81 @@ export function detectTargetState(targetDir) {
|
|
|
908
961
|
huskyDirPresent: fs.existsSync(path.join(targetDir, '.husky')),
|
|
909
962
|
};
|
|
910
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* 0.44.0 charter item 2: detect filesystems where Unix mode bits are
|
|
966
|
+
* unreliable (Windows-class FSes, WSL/native crossings, some network
|
|
967
|
+
* mounts). On these, `stat.mode` for a freshly-installed `.sh` either
|
|
968
|
+
* reads back without the `0o111` exec bit set, or is zeroed entirely.
|
|
969
|
+
*
|
|
970
|
+
* Pre-fix `postInstallVerify` hard-failed the install when zero `.sh`
|
|
971
|
+
* files had the exec bit — every Windows install thus produced a
|
|
972
|
+
* false-positive "0 executable .sh files" warning even on a perfectly
|
|
973
|
+
* healthy install. We now treat exec-bit checks as advisory on these
|
|
974
|
+
* filesystems and still verify the more meaningful invariant: the
|
|
975
|
+
* files exist and have non-empty bytes.
|
|
976
|
+
*
|
|
977
|
+
* Detection strategy — two layers, either sufficient:
|
|
978
|
+
*
|
|
979
|
+
* 1. Platform — `process.platform === 'win32'` always skips the
|
|
980
|
+
* exec-bit check (native Windows has no POSIX mode bit; node's
|
|
981
|
+
* `stat.mode` is a translation that may or may not preserve the
|
|
982
|
+
* 0o111 bit depending on the source).
|
|
983
|
+
* 2. Sample — even on Linux/macOS, when crossing into a Windows-
|
|
984
|
+
* backed filesystem (WSL bind-mount onto `/mnt/c/`, an SMB
|
|
985
|
+
* share, etc.), `stat.mode` returns a value whose `0o777`
|
|
986
|
+
* portion is zero. We detect this by sampling the FIRST `.sh`
|
|
987
|
+
* file in the hooks directory and checking whether ANY of the
|
|
988
|
+
* `0o777` bits are set; if none are, treat as mode-less.
|
|
989
|
+
*
|
|
990
|
+
* Returns true when the exec-bit check should be SKIPPED.
|
|
991
|
+
*
|
|
992
|
+
* Exported for testability — callers can stub the filesystem and
|
|
993
|
+
* exercise both shapes (mode-aware vs mode-less) without spinning
|
|
994
|
+
* up an actual Windows VM.
|
|
995
|
+
*/
|
|
996
|
+
export function isModeLessFilesystem(hooksDir) {
|
|
997
|
+
if (process.platform === 'win32')
|
|
998
|
+
return true;
|
|
999
|
+
// Sample any single .sh file to probe whether the FS preserves
|
|
1000
|
+
// exec bits at all. We don't need every file — just one signal.
|
|
1001
|
+
try {
|
|
1002
|
+
const entries = fs.readdirSync(hooksDir);
|
|
1003
|
+
const firstSh = entries.find((e) => e.endsWith('.sh'));
|
|
1004
|
+
if (firstSh === undefined) {
|
|
1005
|
+
// No .sh files at all — let the caller's existence check fire.
|
|
1006
|
+
// Treat as mode-aware (skip = false) so we don't hide the
|
|
1007
|
+
// genuinely-missing-files case behind the WSL advisory.
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
const stat = fs.statSync(path.join(hooksDir, firstSh));
|
|
1011
|
+
// If ALL 0o777 bits are clear, the FS is not preserving Unix
|
|
1012
|
+
// mode bits. Genuine Unix installs always have at least the
|
|
1013
|
+
// owner-read bit (0o400) set, so an entirely-zero perms triple
|
|
1014
|
+
// means we're on a mode-less mount.
|
|
1015
|
+
if ((stat.mode & 0o777) === 0)
|
|
1016
|
+
return true;
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
catch {
|
|
1020
|
+
// Stat failed — let the caller's enumeration handle the error.
|
|
1021
|
+
// Returning false here means "don't skip" so a genuine ENOENT
|
|
1022
|
+
// surfaces through the normal exec-bit branch.
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
911
1026
|
/**
|
|
912
1027
|
* 0.43.0 UX polish: post-install sanity check. Runs synchronously
|
|
913
1028
|
* after the file-write phase to catch installs that completed
|
|
914
1029
|
* "successfully" but are missing a critical artifact (write
|
|
915
1030
|
* permissions issue, partial copy, etc.).
|
|
916
1031
|
*
|
|
1032
|
+
* 0.44.0 charter item 2: exec-bit check is skipped on mode-less
|
|
1033
|
+
* filesystems (Windows / WSL-crossing / SMB mounts). When skipped, we
|
|
1034
|
+
* still verify the files exist + are non-empty — that's the invariant
|
|
1035
|
+
* a partial-copy or zero-byte write would actually violate. The skip
|
|
1036
|
+
* is annotated in the returned advisory so the operator knows why a
|
|
1037
|
+
* check they expected to run didn't.
|
|
1038
|
+
*
|
|
917
1039
|
* Strictly read-only — no probes that touch python3 / jq / codex.
|
|
918
1040
|
* Pattern modelled on the synthetic round-trip checks established by
|
|
919
1041
|
* `checkDelegationRoundTrip` in 0.29.0/0.31.0: cheap, in-process,
|
|
@@ -921,7 +1043,9 @@ export function detectTargetState(targetDir) {
|
|
|
921
1043
|
* that bites first-time consumers hardest. For deep diagnostics
|
|
922
1044
|
* point the operator at `rea doctor`.
|
|
923
1045
|
*
|
|
924
|
-
* Returns the list of issues found (empty = healthy).
|
|
1046
|
+
* Returns the list of issues found (empty = healthy). Advisory
|
|
1047
|
+
* (skipped-check) lines are prefixed with `advisory:` so the caller
|
|
1048
|
+
* can distinguish them from real issues if desired. The caller
|
|
925
1049
|
* surfaces them via clack's `log.warn` and points the operator at
|
|
926
1050
|
* `rea doctor` for follow-up.
|
|
927
1051
|
*/
|
|
@@ -945,17 +1069,21 @@ export function postInstallVerify(targetDir) {
|
|
|
945
1069
|
'run `rea doctor` for details');
|
|
946
1070
|
}
|
|
947
1071
|
}
|
|
948
|
-
// 2. .claude/hooks directory present with
|
|
1072
|
+
// 2. .claude/hooks directory present with non-empty scripts (and,
|
|
1073
|
+
// on mode-aware filesystems, executable).
|
|
949
1074
|
const hooksDir = path.join(targetDir, '.claude', 'hooks');
|
|
950
1075
|
if (!fs.existsSync(hooksDir)) {
|
|
951
1076
|
issues.push(`.claude/hooks/ directory missing after install (expected at ${hooksDir})`);
|
|
952
1077
|
}
|
|
953
1078
|
else {
|
|
1079
|
+
const modeLess = isModeLessFilesystem(hooksDir);
|
|
954
1080
|
let executableCount = 0;
|
|
1081
|
+
let shCount = 0;
|
|
955
1082
|
try {
|
|
956
1083
|
for (const entry of fs.readdirSync(hooksDir)) {
|
|
957
1084
|
if (!entry.endsWith('.sh'))
|
|
958
1085
|
continue;
|
|
1086
|
+
shCount += 1;
|
|
959
1087
|
const stat = fs.statSync(path.join(hooksDir, entry));
|
|
960
1088
|
if ((stat.mode & 0o111) !== 0)
|
|
961
1089
|
executableCount += 1;
|
|
@@ -964,7 +1092,58 @@ export function postInstallVerify(targetDir) {
|
|
|
964
1092
|
catch (e) {
|
|
965
1093
|
issues.push(`failed to enumerate .claude/hooks/: ${e instanceof Error ? e.message : String(e)}`);
|
|
966
1094
|
}
|
|
967
|
-
if (
|
|
1095
|
+
if (modeLess) {
|
|
1096
|
+
// 0.44.0 charter item 2: emit a one-liner advisory so the
|
|
1097
|
+
// operator understands why the exec-bit check didn't run. Still
|
|
1098
|
+
// verify the files exist + have content — that's the partial-
|
|
1099
|
+
// copy failure shape we genuinely want to catch on these FSes.
|
|
1100
|
+
//
|
|
1101
|
+
// 0.44.0 codex round-1 P2 fix: validate the FULL canonical hook
|
|
1102
|
+
// set, not just `shCount > 0 && nonEmptyCount > 0`. Pre-fix a
|
|
1103
|
+
// partial copy that left ONE non-empty .sh and dropped the rest
|
|
1104
|
+
// would still report "install looks healthy" because the
|
|
1105
|
+
// substitute invariant only required at least one survivor.
|
|
1106
|
+
// Now we per-file check every entry in canonicalInstalledHooks()
|
|
1107
|
+
// for existence + non-empty bytes — equivalent rigor to the
|
|
1108
|
+
// mode-aware path's per-file exec-bit check.
|
|
1109
|
+
issues.push('advisory: skipping exec-bit check on this filesystem ' +
|
|
1110
|
+
'(Windows/WSL/SMB-class; mode bits not reliable). ' +
|
|
1111
|
+
'Verifying per-file presence and non-empty content instead.');
|
|
1112
|
+
const expected = canonicalInstalledHooks();
|
|
1113
|
+
const missing = [];
|
|
1114
|
+
const empty = [];
|
|
1115
|
+
for (const name of expected) {
|
|
1116
|
+
const hookPath = path.join(hooksDir, name);
|
|
1117
|
+
if (!fs.existsSync(hookPath)) {
|
|
1118
|
+
missing.push(name);
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
try {
|
|
1122
|
+
const stat = fs.statSync(hookPath);
|
|
1123
|
+
if (stat.size === 0)
|
|
1124
|
+
empty.push(name);
|
|
1125
|
+
}
|
|
1126
|
+
catch {
|
|
1127
|
+
// Treat unstattable as missing — the partial-copy failure
|
|
1128
|
+
// shape we are trying to detect.
|
|
1129
|
+
missing.push(name);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (missing.length > 0) {
|
|
1133
|
+
issues.push(`.claude/hooks/ is missing ${missing.length} expected hook file(s): ${missing.join(', ')}`);
|
|
1134
|
+
}
|
|
1135
|
+
if (empty.length > 0) {
|
|
1136
|
+
issues.push(`.claude/hooks/ has ${empty.length} empty hook file(s): ${empty.join(', ')}`);
|
|
1137
|
+
}
|
|
1138
|
+
// Fallback for the no-canonical-list-known case (defensive — the
|
|
1139
|
+
// helper always returns >=1 in practice, but if a future
|
|
1140
|
+
// refactor empties the resolvers we still want to catch a
|
|
1141
|
+
// completely-empty hooks dir).
|
|
1142
|
+
if (expected.length === 0 && shCount === 0) {
|
|
1143
|
+
issues.push('.claude/hooks/ contains zero .sh files — run `rea doctor`');
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
else if (executableCount === 0) {
|
|
968
1147
|
issues.push('.claude/hooks/ contains zero executable .sh files — run `rea doctor`');
|
|
969
1148
|
}
|
|
970
1149
|
}
|
|
@@ -1281,13 +1460,34 @@ export async function runInit(options) {
|
|
|
1281
1460
|
// operator at `rea doctor` for the deep dive. Modelled on the
|
|
1282
1461
|
// 0.29.0/0.31.0 synthetic round-trip pattern.
|
|
1283
1462
|
const verifyIssues = postInstallVerify(targetDir);
|
|
1284
|
-
|
|
1463
|
+
// 0.44.0 charter item 2: split advisory (`advisory:`-prefixed) from
|
|
1464
|
+
// real issues. Advisories explain skipped checks (Windows/WSL exec-
|
|
1465
|
+
// bit skip) and don't merit the loud "verification flagged" header
|
|
1466
|
+
// when no real issue is present.
|
|
1467
|
+
const realIssues = verifyIssues.filter((i) => !i.startsWith('advisory:'));
|
|
1468
|
+
const advisories = verifyIssues.filter((i) => i.startsWith('advisory:'));
|
|
1469
|
+
if (realIssues.length > 0) {
|
|
1285
1470
|
console.log('');
|
|
1286
1471
|
warn('post-install verification flagged the following:');
|
|
1287
|
-
for (const issue of
|
|
1472
|
+
for (const issue of realIssues)
|
|
1288
1473
|
warn(` • ${issue}`);
|
|
1474
|
+
for (const adv of advisories)
|
|
1475
|
+
warn(` • ${adv}`);
|
|
1289
1476
|
warn('Run `rea doctor` for a full diagnostic.');
|
|
1290
1477
|
}
|
|
1478
|
+
else if (advisories.length > 0) {
|
|
1479
|
+
if (interactive) {
|
|
1480
|
+
p.log.success('Post-install check: install looks healthy.');
|
|
1481
|
+
for (const adv of advisories)
|
|
1482
|
+
p.log.info(adv);
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
console.log('');
|
|
1486
|
+
console.log('Post-install check: install looks healthy.');
|
|
1487
|
+
for (const adv of advisories)
|
|
1488
|
+
console.log(` ${adv}`);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1291
1491
|
else if (interactive) {
|
|
1292
1492
|
// Quiet success — confirm we checked, but don't shout about it.
|
|
1293
1493
|
p.log.success('Post-install check: install looks healthy.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bookedsolid/rea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and Codex-integrated adversarial review for AI-assisted projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Booked Solid Technology <oss@bookedsolid.tech> (https://bookedsolid.tech)",
|