@bookedsolid/rea 0.41.0 → 0.43.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/MIGRATING.md +139 -0
- package/dist/cli/audit-summary.d.ts +15 -0
- package/dist/cli/audit-summary.js +94 -38
- package/dist/cli/doctor.d.ts +44 -4
- package/dist/cli/doctor.js +170 -43
- package/dist/cli/init.d.ts +102 -0
- package/dist/cli/init.js +417 -72
- package/dist/cli/upgrade-check.d.ts +38 -0
- package/dist/cli/upgrade-check.js +93 -7
- package/dist/cli/upgrade.js +42 -0
- package/package.json +1 -1
package/dist/cli/doctor.js
CHANGED
|
@@ -1264,18 +1264,74 @@ function defaultPython3PyYamlReachable(baseDir) {
|
|
|
1264
1264
|
return false;
|
|
1265
1265
|
}
|
|
1266
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* 0.42.0 codex round 5 P2 (2026-05-16) — execution probe for the
|
|
1269
|
+
* python3 list-walker. Mirrors `defaultPython3PyYamlReachable` exactly
|
|
1270
|
+
* but swaps the `import yaml` for `import json` (the actual stdlib
|
|
1271
|
+
* module the shim's list-walker branch imports). Spawning the
|
|
1272
|
+
* interpreter end-to-end catches the broken-symlink / unreachable-
|
|
1273
|
+
* shim case that a bare PATH check misses.
|
|
1274
|
+
*/
|
|
1275
|
+
function defaultPython3ListWalkerReachable(baseDir) {
|
|
1276
|
+
try {
|
|
1277
|
+
const probeEnv = { ...process.env, PYTHONSAFEPATH: '1' };
|
|
1278
|
+
delete probeEnv['PYTHONPATH'];
|
|
1279
|
+
delete probeEnv['PYTHONHOME'];
|
|
1280
|
+
delete probeEnv['PYTHONSTARTUP'];
|
|
1281
|
+
// Same sys.path scrub as the production loader, applied before
|
|
1282
|
+
// `import json`. `json` is stdlib so a malicious `./json.py`
|
|
1283
|
+
// attack would matter the same way `./yaml.py` does.
|
|
1284
|
+
const probeBody = [
|
|
1285
|
+
'import sys',
|
|
1286
|
+
'import os',
|
|
1287
|
+
'_cwd = os.getcwd()',
|
|
1288
|
+
'_cwd_real = os.path.realpath(_cwd)',
|
|
1289
|
+
'sys.path[:] = [p for p in sys.path if p not in ("", ".", _cwd, _cwd_real)]',
|
|
1290
|
+
'import json',
|
|
1291
|
+
'sys.stdout.write("ok")',
|
|
1292
|
+
].join('\n');
|
|
1293
|
+
const res = spawnSync('python3', ['-c', probeBody], {
|
|
1294
|
+
cwd: baseDir,
|
|
1295
|
+
env: probeEnv,
|
|
1296
|
+
timeout: 5_000,
|
|
1297
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1298
|
+
});
|
|
1299
|
+
if (res.status !== 0)
|
|
1300
|
+
return false;
|
|
1301
|
+
return (res.stdout?.toString().trim() ?? '') === 'ok';
|
|
1302
|
+
}
|
|
1303
|
+
catch {
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1267
1307
|
const DEFAULT_PROBES = {
|
|
1268
1308
|
cliDistExists: defaultCliDistExists,
|
|
1269
1309
|
cliInvokable: defaultCliInvokable,
|
|
1270
1310
|
python3OnPath: () => resolveBinaryOnPath('python3'),
|
|
1271
1311
|
python3PyYamlReachable: defaultPython3PyYamlReachable,
|
|
1312
|
+
python3ListWalkerReachable: defaultPython3ListWalkerReachable,
|
|
1272
1313
|
awkOnPath: () => resolveBinaryOnPath('awk'),
|
|
1273
1314
|
jqOnPath: () => resolveBinaryOnPath('jq'),
|
|
1274
1315
|
};
|
|
1275
1316
|
function resolveProbes(probes) {
|
|
1276
1317
|
if (probes === undefined)
|
|
1277
1318
|
return DEFAULT_PROBES;
|
|
1278
|
-
|
|
1319
|
+
// 0.42.0 codex round 5 P2 (2026-05-16): when a caller (typically
|
|
1320
|
+
// a unit test) stubs `python3OnPath` but does NOT stub the new
|
|
1321
|
+
// `python3ListWalkerReachable` execution probe, derive a faithful
|
|
1322
|
+
// fallback from `python3OnPath` instead of falling through to the
|
|
1323
|
+
// real `defaultPython3ListWalkerReachable` (which would spawn a
|
|
1324
|
+
// python3 subprocess and break test determinism). The convention
|
|
1325
|
+
// matches `python3PyYamlReachable` in the existing test suite:
|
|
1326
|
+
// stubs that say "python3 is present" want both downstream probes
|
|
1327
|
+
// to report reachable, and stubs that say "python3 is absent" want
|
|
1328
|
+
// both downstream probes to report unreachable.
|
|
1329
|
+
const overrides = { ...probes };
|
|
1330
|
+
if (overrides.python3ListWalkerReachable === undefined && overrides.python3OnPath !== undefined) {
|
|
1331
|
+
const stubbedPython3OnPath = overrides.python3OnPath;
|
|
1332
|
+
overrides.python3ListWalkerReachable = () => stubbedPython3OnPath() !== null;
|
|
1333
|
+
}
|
|
1334
|
+
return { ...DEFAULT_PROBES, ...overrides };
|
|
1279
1335
|
}
|
|
1280
1336
|
/**
|
|
1281
1337
|
* Tier 1 — `rea hook policy-get`. Reachable when the rea CLI is
|
|
@@ -1374,12 +1430,12 @@ export function checkPolicyReaderTier2(baseDir, probes) {
|
|
|
1374
1430
|
* Practically always present (POSIX requirement).
|
|
1375
1431
|
*
|
|
1376
1432
|
* 0.40.0 charter item 2 — conditional verdict, refined by codex
|
|
1377
|
-
* round 1 P2:
|
|
1433
|
+
* round 1 P2 (0.40.0) and round 2 P2 (0.42.0):
|
|
1378
1434
|
* - awk present → `pass`
|
|
1379
1435
|
* - awk absent AND Tier 2 reachable → `warn`
|
|
1380
1436
|
* (Tier 2 implies python3, which is a list-walker)
|
|
1381
1437
|
* - awk absent AND Tier 1 reachable AND a list walker
|
|
1382
|
-
* (jq OR
|
|
1438
|
+
* (jq OR full Tier-2 reachable) is usable → `warn`
|
|
1383
1439
|
* - awk absent AND Tier 1 reachable BUT no list walker → `fail`
|
|
1384
1440
|
* (codex round 1 P2 — list-valued policy reads silently
|
|
1385
1441
|
* fail-closed even though scalar reads work, so the
|
|
@@ -1398,9 +1454,29 @@ export function checkPolicyReaderTier2(baseDir, probes) {
|
|
|
1398
1454
|
* functional box that has python3 + jq + the rea CLI all wired but
|
|
1399
1455
|
* happens to lack awk.
|
|
1400
1456
|
*
|
|
1457
|
+
* List-iteration semantic (clarifying note for codex round 2 P2,
|
|
1458
|
+
* 2026-05-16): `policy_reader_get_list` in
|
|
1459
|
+
* `hooks/_lib/policy-reader.sh` walks the cached subtree JSON via
|
|
1460
|
+
* `jq` OR `python3` (stdlib-only — `json` module, no PyYAML import).
|
|
1461
|
+
* PyYAML is only needed for Tier 2 itself (YAML PARSING into JSON),
|
|
1462
|
+
* NOT for iterating the already-parsed JSON arrays at list-read time.
|
|
1463
|
+
*
|
|
1464
|
+
* Codex round 5 P2 (2026-05-16): the "list walker" predicate uses
|
|
1465
|
+
* `python3ListWalkerReachable` — an EXECUTION probe that actually
|
|
1466
|
+
* spawns `python3 -c "import json"` — instead of `python3OnPath`. A
|
|
1467
|
+
* PATH-only check passes for broken pyenv/asdf shims, dangling
|
|
1468
|
+
* symlinks, and sandboxed environments where the interpreter cannot
|
|
1469
|
+
* start; in those cases the shim's list-walker branch would actually
|
|
1470
|
+
* fail and `blocked_paths`/`protected_writes` enforcement would
|
|
1471
|
+
* silently break while doctor reported `warn`. The execution probe
|
|
1472
|
+
* mirrors `defaultPython3PyYamlReachable` exactly but swaps the
|
|
1473
|
+
* `import yaml` for `import json` so it's not gated on PyYAML
|
|
1474
|
+
* availability (which is irrelevant to list iteration).
|
|
1475
|
+
*
|
|
1401
1476
|
* Takes `baseDir` so it can evaluate Tier 1's two-stage check (dist
|
|
1402
|
-
* present + CLI invokable)
|
|
1403
|
-
* threaded through
|
|
1477
|
+
* present + CLI invokable), Tier 2's reachability, and the
|
|
1478
|
+
* list-walker execution probe. All probes are threaded through
|
|
1479
|
+
* identically.
|
|
1404
1480
|
*/
|
|
1405
1481
|
export function checkPolicyReaderTier3(baseDir, probes) {
|
|
1406
1482
|
const label = 'policy-reader Tier 3 (awk)';
|
|
@@ -1420,22 +1496,25 @@ export function checkPolicyReaderTier3(baseDir, probes) {
|
|
|
1420
1496
|
// ladder would actually do at runtime.
|
|
1421
1497
|
const tier1 = p.cliDistExists(baseDir) && p.cliInvokable(baseDir);
|
|
1422
1498
|
const tier2 = p.python3OnPath() !== null && p.python3PyYamlReachable(baseDir);
|
|
1423
|
-
// Codex round 1 P2 (
|
|
1424
|
-
//
|
|
1425
|
-
//
|
|
1426
|
-
//
|
|
1427
|
-
//
|
|
1428
|
-
//
|
|
1429
|
-
//
|
|
1430
|
-
//
|
|
1431
|
-
//
|
|
1432
|
-
//
|
|
1433
|
-
//
|
|
1434
|
-
//
|
|
1435
|
-
//
|
|
1436
|
-
//
|
|
1437
|
-
|
|
1438
|
-
|
|
1499
|
+
// Codex round 1 P2 (0.40.0) + round 2 P2 corrected (0.42.0,
|
|
1500
|
+
// 2026-05-16) + round 5 P2 (0.42.0, 2026-05-16): the
|
|
1501
|
+
// downgrade-to-warn branch needs a list walker too.
|
|
1502
|
+
// `policy_reader_get_list` iterates the parsed JSON array via jq
|
|
1503
|
+
// OR python3. The python3 branch uses `json` from stdlib only —
|
|
1504
|
+
// PyYAML is NOT required (it's only needed for Tier 2's YAML
|
|
1505
|
+
// parsing step, which has already run by the time list iteration
|
|
1506
|
+
// executes).
|
|
1507
|
+
//
|
|
1508
|
+
// Round 5 P2 hardening: the python3 leg of this predicate uses an
|
|
1509
|
+
// EXECUTION probe (`python3ListWalkerReachable`), not just a PATH
|
|
1510
|
+
// check. A `python3` symlink can resolve on PATH while the
|
|
1511
|
+
// interpreter itself fails to start (dangling pyenv/asdf shim,
|
|
1512
|
+
// sandboxed runner without dynamic libs, permission denied on the
|
|
1513
|
+
// resolved binary). PATH-only would let doctor declare `warn` on
|
|
1514
|
+
// a box where the shim's list walker would actually fail —
|
|
1515
|
+
// silently breaking `blocked_paths` / `protected_writes`
|
|
1516
|
+
// enforcement while doctor exits 0.
|
|
1517
|
+
const listWalker = p.jqOnPath() !== null || p.python3ListWalkerReachable(baseDir);
|
|
1439
1518
|
if (tier2 || (tier1 && listWalker)) {
|
|
1440
1519
|
const reachable = [];
|
|
1441
1520
|
if (tier1)
|
|
@@ -1451,21 +1530,43 @@ export function checkPolicyReaderTier3(baseDir, probes) {
|
|
|
1451
1530
|
'to restore the last-resort fallback.',
|
|
1452
1531
|
};
|
|
1453
1532
|
}
|
|
1454
|
-
// Codex round 1 P2: separate "no list
|
|
1455
|
-
// catastrophic "no tier at all" case.
|
|
1456
|
-
// AND no python3 AND no awk means
|
|
1457
|
-
// fail-closed silently — distinct from the
|
|
1458
|
-
// no-CLI-no-python-no-awk shape, and worth a precise
|
|
1533
|
+
// Codex round 1 P2 (0.40.0) + round 2 P2 (0.42.0): separate "no list
|
|
1534
|
+
// walker" diagnosis from the catastrophic "no tier at all" case.
|
|
1535
|
+
// Tier 1 reachable but no jq AND no python3 AND no awk means
|
|
1536
|
+
// list-valued policy reads fail-closed silently — distinct from the
|
|
1537
|
+
// truly-empty no-CLI-no-python-no-awk shape, and worth a precise
|
|
1538
|
+
// remediation. The python3-as-list-walker signal is plain
|
|
1539
|
+
// `python3OnPath` (the `json` module is stdlib — PyYAML is NOT
|
|
1540
|
+
// required for list iteration).
|
|
1459
1541
|
if (tier1) {
|
|
1542
|
+
// 0.42.0 codex round 6 P3 (2026-05-16): distinguish "python3 not
|
|
1543
|
+
// on PATH" from "python3 on PATH but execution fails". Pre-fix
|
|
1544
|
+
// this branch always reported "python3 is not on PATH" even when
|
|
1545
|
+
// a python3 binary was resolvable but a broken pyenv/asdf shim
|
|
1546
|
+
// or sandboxed interpreter failed the execution probe — that
|
|
1547
|
+
// sent operators toward the wrong remediation. Round 5 added
|
|
1548
|
+
// the execution probe specifically to surface this case; the
|
|
1549
|
+
// diagnostic needs to follow.
|
|
1550
|
+
const pythonOnPath = p.python3OnPath();
|
|
1551
|
+
const pythonState = pythonOnPath === null
|
|
1552
|
+
? 'python3 is not on PATH'
|
|
1553
|
+
: `python3 at ${pythonOnPath} cannot execute \`import json\` (broken pyenv/asdf shim, ` +
|
|
1554
|
+
'sandboxed interpreter, or permission-denied binary — fix the interpreter or ' +
|
|
1555
|
+
'remove the shim)';
|
|
1556
|
+
const remediation = pythonOnPath === null
|
|
1557
|
+
? 'Install awk OR jq OR python3 to restore list-iteration.'
|
|
1558
|
+
: `Install awk OR jq, or repair the python3 interpreter at ${pythonOnPath}, ` +
|
|
1559
|
+
'to restore list-iteration.';
|
|
1460
1560
|
return {
|
|
1461
1561
|
label,
|
|
1462
1562
|
status: 'fail',
|
|
1463
|
-
detail:
|
|
1464
|
-
'flow-form scalars, but `policy_reader_get_list`
|
|
1465
|
-
'(e.g. `blocked_paths: [.env, ...]`)
|
|
1466
|
-
'resulting JSON arrays.
|
|
1467
|
-
'`blocked-paths-
|
|
1468
|
-
|
|
1563
|
+
detail: `awk not on PATH AND jq is not on PATH AND ${pythonState} — ` +
|
|
1564
|
+
'Tier 1 (rea CLI) parses flow-form scalars, but `policy_reader_get_list` ' +
|
|
1565
|
+
'cannot iterate list-valued keys (e.g. `blocked_paths: [.env, ...]`) ' +
|
|
1566
|
+
'without jq OR python3 OR awk to walk the resulting JSON arrays. ' +
|
|
1567
|
+
'Affected hooks (`blocked-paths-bash-gate.sh`, ' +
|
|
1568
|
+
`\`blocked-paths-enforcer.sh\`, …) see an EMPTY list and silently stop ` +
|
|
1569
|
+
`enforcing. ${remediation}`,
|
|
1469
1570
|
};
|
|
1470
1571
|
}
|
|
1471
1572
|
return {
|
|
@@ -1542,11 +1643,14 @@ export function checkPolicyReaderTierSummary(baseDir, probes) {
|
|
|
1542
1643
|
const tier2 = py !== null && p.python3PyYamlReachable(baseDir);
|
|
1543
1644
|
const tier3 = p.awkOnPath() !== null;
|
|
1544
1645
|
const jq = p.jqOnPath();
|
|
1545
|
-
//
|
|
1546
|
-
//
|
|
1547
|
-
//
|
|
1548
|
-
// but
|
|
1549
|
-
|
|
1646
|
+
// 0.42.0 codex round 5 P2 (2026-05-16): list iteration after Tier
|
|
1647
|
+
// 1/2 needs jq OR a python3 that can ACTUALLY execute (not just
|
|
1648
|
+
// resolve on PATH). The execution probe catches the broken-shim
|
|
1649
|
+
// case where `python3` resolves but the interpreter cannot start —
|
|
1650
|
+
// PATH-only would falsely declare the list walker "usable" on a
|
|
1651
|
+
// box where the shim's python3 branch will fall through to Tier 3
|
|
1652
|
+
// and silently miss flow-form arrays.
|
|
1653
|
+
const listWalker = jq !== null || p.python3ListWalkerReachable(baseDir);
|
|
1550
1654
|
const reachable = [];
|
|
1551
1655
|
if (tier1)
|
|
1552
1656
|
reachable.push('Tier 1 (CLI)');
|
|
@@ -1556,17 +1660,40 @@ export function checkPolicyReaderTierSummary(baseDir, probes) {
|
|
|
1556
1660
|
reachable.push('Tier 3 (awk)');
|
|
1557
1661
|
if (tier1 || tier2) {
|
|
1558
1662
|
if (!listWalker) {
|
|
1559
|
-
// Tier 1 + no
|
|
1560
|
-
// arrays silently no-op via Tier 3 fallthrough.
|
|
1561
|
-
// is unreachable here because Tier 2 requires
|
|
1663
|
+
// Tier 1 + no working list walker. flow-form scalars work;
|
|
1664
|
+
// flow-form arrays silently no-op via Tier 3 fallthrough.
|
|
1665
|
+
// (Tier 2 path is unreachable here because Tier 2 requires
|
|
1666
|
+
// python3 itself reachable.)
|
|
1667
|
+
//
|
|
1668
|
+
// 0.43.0 round-7 P3 (2026-05-17): mirror the round-6 P3 fix
|
|
1669
|
+
// from `checkPolicyReaderTier3`. Pre-fix this branch always
|
|
1670
|
+
// said "neither jq nor python3 is on PATH" — but the
|
|
1671
|
+
// `listWalker` predicate is `jq OR python3ListWalkerReachable`,
|
|
1672
|
+
// so it also fires when python3 IS on PATH but the EXECUTION
|
|
1673
|
+
// probe fails (broken pyenv/asdf shim, dangling symlink,
|
|
1674
|
+
// sandboxed interpreter that fails to start). That
|
|
1675
|
+
// misdiagnosis sent operators chasing the wrong remediation
|
|
1676
|
+
// ("install python3" when python3 was already installed but
|
|
1677
|
+
// broken). Distinguish the two shapes so the operator sees
|
|
1678
|
+
// the actual problem, and surface the resolved path so they
|
|
1679
|
+
// can `ls -l` it on the filesystem.
|
|
1680
|
+
const pythonOnPath = py;
|
|
1681
|
+
const pythonState = pythonOnPath === null
|
|
1682
|
+
? 'neither jq nor python3 is on PATH'
|
|
1683
|
+
: `jq is not on PATH AND python3 at ${pythonOnPath} cannot execute \`import json\` ` +
|
|
1684
|
+
'(broken pyenv/asdf shim, sandboxed interpreter, or permission-denied binary — ' +
|
|
1685
|
+
'fix the interpreter or remove the shim)';
|
|
1686
|
+
const remediation = pythonOnPath === null
|
|
1687
|
+
? 'Install jq (`brew install jq` / `apt-get install jq`) or python3 to close the gap.'
|
|
1688
|
+
: `Install jq (\`brew install jq\` / \`apt-get install jq\`) or repair the python3 ` +
|
|
1689
|
+
`interpreter at ${pythonOnPath} to close the gap.`;
|
|
1562
1690
|
return {
|
|
1563
1691
|
label,
|
|
1564
1692
|
status: 'warn',
|
|
1565
1693
|
detail: `${reachable.join(', ')} reachable — flow-form scalars parse via Tier 1 CLI, ` +
|
|
1566
|
-
|
|
1694
|
+
`BUT ${pythonState} so \`policy_reader_get_list\` cannot iterate ` +
|
|
1567
1695
|
'the resulting JSON arrays. Flow-form list policy (e.g. `blocked_paths: [.env, ...]`) ' +
|
|
1568
|
-
|
|
1569
|
-
'(`brew install jq` / `apt-get install jq`) or python3 to close the gap.',
|
|
1696
|
+
`silently falls through to Tier 3 awk and misses inline arrays. ${remediation}`,
|
|
1570
1697
|
};
|
|
1571
1698
|
}
|
|
1572
1699
|
return {
|
package/dist/cli/init.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AutonomyLevel } from '../policy/types.js';
|
|
1
2
|
export interface InitOptions {
|
|
2
3
|
yes?: boolean | undefined;
|
|
3
4
|
fromReagent?: boolean | undefined;
|
|
@@ -16,4 +17,105 @@ export interface InitOptions {
|
|
|
16
17
|
*/
|
|
17
18
|
codex?: boolean | undefined;
|
|
18
19
|
}
|
|
20
|
+
type ProfileName = 'client-engagement' | 'bst-internal' | 'bst-internal-no-codex' | 'lit-wc' | 'open-source' | 'open-source-no-codex' | 'minimal';
|
|
21
|
+
export interface ResolvedConfig {
|
|
22
|
+
profile: ProfileName;
|
|
23
|
+
autonomyLevel: AutonomyLevel;
|
|
24
|
+
maxAutonomyLevel: AutonomyLevel;
|
|
25
|
+
blockAiAttribution: boolean;
|
|
26
|
+
blockedPaths: string[];
|
|
27
|
+
notificationChannel: string;
|
|
28
|
+
/**
|
|
29
|
+
* G11.4: written to `.rea/policy.yaml` as `review.codex_required`. We
|
|
30
|
+
* always emit the field explicitly — no implicit defaults — so an
|
|
31
|
+
* operator reading the file sees the choice that was made at init time.
|
|
32
|
+
*/
|
|
33
|
+
codexRequired: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Round-27 F6: preserved 0.26.0 local-review + commit-hygiene knobs.
|
|
36
|
+
* Each is `undefined` when the operator never set it, in which case
|
|
37
|
+
* the policy writer omits the corresponding line from the YAML output
|
|
38
|
+
* (consumers fall through to the documented 0.26.0 defaults).
|
|
39
|
+
*/
|
|
40
|
+
localReviewMode?: 'enforced' | 'off';
|
|
41
|
+
localReviewRefuseAt?: 'push' | 'commit' | 'both';
|
|
42
|
+
localReviewBypassEnvVar?: string;
|
|
43
|
+
localReviewMaxAgeSeconds?: number;
|
|
44
|
+
commitHygieneWarnAtCommits?: number;
|
|
45
|
+
commitHygieneRefuseAtCommits?: number;
|
|
46
|
+
/**
|
|
47
|
+
* 0.30.0 attribution augmenter. Preserved across re-init from a prior
|
|
48
|
+
* on-disk policy and seeded from the chosen profile on first install.
|
|
49
|
+
* Every shipped profile pins `enabled: false`, so the default for new
|
|
50
|
+
* installs is "block ready, opt in by editing the policy".
|
|
51
|
+
*/
|
|
52
|
+
attributionCoAuthor?: {
|
|
53
|
+
enabled?: boolean;
|
|
54
|
+
name?: string;
|
|
55
|
+
email?: string;
|
|
56
|
+
skipMerge?: boolean;
|
|
57
|
+
};
|
|
58
|
+
fromReagent: boolean;
|
|
59
|
+
reagentPolicyPath: string | null;
|
|
60
|
+
reagentNotices: string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 0.43.0 UX polish: build the human-readable install summary shown
|
|
64
|
+
* BEFORE any files are written. Lists, in order: the policy file
|
|
65
|
+
* being written, the chosen profile + autonomy, hook + agent counts
|
|
66
|
+
* planned, the git/husky hooks planned (paths reflect what the
|
|
67
|
+
* installer will ACTUALLY do given the target tree's shape), and
|
|
68
|
+
* whether re-run preservation is active.
|
|
69
|
+
*
|
|
70
|
+
* Rendered via clack's `note` primitive so it sits in a bordered block
|
|
71
|
+
* adjacent to the final `confirm` gate. The string is also returned
|
|
72
|
+
* verbatim so the test suite can assert content without mocking clack.
|
|
73
|
+
*
|
|
74
|
+
* `targetState` is computed by {@link detectTargetState} — kept as a
|
|
75
|
+
* separate argument so tests can drive both shapes (husky-present and
|
|
76
|
+
* husky-absent) without touching the filesystem.
|
|
77
|
+
*/
|
|
78
|
+
export declare function buildInstallSummary(targetDir: string, config: ResolvedConfig, reRunMode: boolean, targetState: TargetState): string;
|
|
79
|
+
/**
|
|
80
|
+
* 0.43.0 codex round-1 P3: shape of the target tree the installer
|
|
81
|
+
* will see. `buildInstallSummary` and the post-install verifier both
|
|
82
|
+
* need to know whether `.git/` and `.husky/` are present so the
|
|
83
|
+
* summary doesn't lie about which hook files will be written.
|
|
84
|
+
*/
|
|
85
|
+
export interface TargetState {
|
|
86
|
+
gitRepoPresent: boolean;
|
|
87
|
+
huskyDirPresent: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 0.43.0 codex round-1 P3: detect which hook surfaces the installer
|
|
91
|
+
* will actually touch. Returns a snapshot so the install-summary
|
|
92
|
+
* confirm screen can show the right paths.
|
|
93
|
+
*
|
|
94
|
+
* Intentionally simple — the installers themselves (commit-msg,
|
|
95
|
+
* prepare-commit-msg, pre-push) each re-check at write time, so this
|
|
96
|
+
* detection is purely presentational. If something races between the
|
|
97
|
+
* snapshot and the writes (a `pnpm install` adding `.husky/` in the
|
|
98
|
+
* window between confirm and spinner), the installer's own checks win
|
|
99
|
+
* and the summary was only slightly stale.
|
|
100
|
+
*/
|
|
101
|
+
export declare function detectTargetState(targetDir: string): TargetState;
|
|
102
|
+
/**
|
|
103
|
+
* 0.43.0 UX polish: post-install sanity check. Runs synchronously
|
|
104
|
+
* after the file-write phase to catch installs that completed
|
|
105
|
+
* "successfully" but are missing a critical artifact (write
|
|
106
|
+
* permissions issue, partial copy, etc.).
|
|
107
|
+
*
|
|
108
|
+
* Strictly read-only — no probes that touch python3 / jq / codex.
|
|
109
|
+
* Pattern modelled on the synthetic round-trip checks established by
|
|
110
|
+
* `checkDelegationRoundTrip` in 0.29.0/0.31.0: cheap, in-process,
|
|
111
|
+
* sufficient to catch the "looks-installed-but-isn't" failure shape
|
|
112
|
+
* that bites first-time consumers hardest. For deep diagnostics
|
|
113
|
+
* point the operator at `rea doctor`.
|
|
114
|
+
*
|
|
115
|
+
* Returns the list of issues found (empty = healthy). The caller
|
|
116
|
+
* surfaces them via clack's `log.warn` and points the operator at
|
|
117
|
+
* `rea doctor` for follow-up.
|
|
118
|
+
*/
|
|
119
|
+
export declare function postInstallVerify(targetDir: string): string[];
|
|
19
120
|
export declare function runInit(options: InitOptions): Promise<void>;
|
|
121
|
+
export {};
|