repo-nexus 0.5.2 → 0.5.4

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/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to Repo Nexus (`rnex`) will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - **Explicit `git_hooks` Workspace Configuration (`rnex.yaml` & `.local.rnex.yaml`)**:
12
+ - Top-level `git_hooks: true|false` setting establishes the workspace configuration as the single source of truth for automated Git hooks.
13
+ - Two-level configuration support allowing per-workstation overrides in `.local.rnex.yaml` (e.g. keeping hooks globally enabled in `rnex.yaml` while disabling locally via `rnex hooks uninstall --local`).
14
+ - `rnex hooks install [--local]` and `rnex hooks uninstall [--local]` record preference directly to `rnex.yaml` or `.local.rnex.yaml`.
15
+ - `rnex fix` (and `rnex sync`) automatically reconciles Git's `core.hooksPath` against the resolved `git_hooks` configuration.
16
+ - Interactive `rnex init` determines and records `git_hooks` preference upfront in new workspaces.
17
+ - Workspace schema migration in `rnex update` and `rnex fix -y` non-destructively inserts `git_hooks` into older configurations.
18
+ - `rnex status` and `rnex hooks status` display configuration state alongside runtime Git hooks status.
19
+
20
+ ### Fixed
21
+ - **Git Hooks Self-Rewrite & Wrapper Preservation**:
22
+ - Resolved self-rewrite bug where `rnex hooks run post-merge` invoked `cmd_fix`, which truncated and replaced `.rnex/hooks/post-merge` while the shell interpreter was actively executing it.
23
+ - Added in-hook execution guard (`_RNEX_INSIDE_HOOK=1`) preventing hook script regeneration during active hook dispatch.
24
+ - Preserved custom / repository-owned wrappers in `.rnex/hooks/` that are not auto-managed by `rnex`.
25
+ - Added `cmp -s` check before writing hook files to eliminate unnecessary file churn and in-place overwrites.
26
+ - Automatically chain to existing repository-owned `.githooks/` scripts if present.
27
+
8
28
  ## [0.5.1] - 2026-10-08
9
29
 
10
30
  ### Added
package/README.md CHANGED
@@ -245,6 +245,9 @@ The `karpathy-llm` plugin packages Andrej Karpathy's verified LLM agent design p
245
245
  # Directory for member repository clones
246
246
  repos_dir: ./repos
247
247
 
248
+ # Automated workspace Git hooks (default: true)
249
+ git_hooks: true
250
+
248
251
  # Workspace plugins
249
252
  plugins:
250
253
  karpathy-llm: {}
@@ -301,7 +304,7 @@ repos:
301
304
  | `rnex rnex-dir <enable\|disable> <name>` | Toggle `.rnex/` directory integration for a member repository |
302
305
  | `rnex fix [-y] [--quiet]` (or `sync`) | Reconcile workspace repositories, plugins, prompts, and member `.rnex/` directories |
303
306
  | `rnex update [-y]` (or `upgrade`) | Safely upgrade workspace configuration schema and routing instructions |
304
- | `rnex hooks <install\|uninstall\|status>` | Manage config-driven automated Git hooks (`.rnex/hooks`) |
307
+ | `rnex hooks <install\|uninstall\|status> [--local]` | Manage automated Git hooks (`.rnex/hooks` and `git_hooks: true\|false`) |
305
308
  | `rnex plugin <list\|info\|enable\|disable>` | Manage workspace plugins |
306
309
  | `rnex completion <bash\|zsh\|fish>` | Generate shell auto-completion script |
307
310
 
package/docs/CLI.md CHANGED
@@ -316,26 +316,35 @@ rnex rnex-dir <enable|disable> <name>
316
316
 
317
317
  ## Git Hooks Automation Commands
318
318
 
319
- Repo Nexus manages workspace-level Git hooks via `git config core.hooksPath .rnex/hooks`. The hooks are native shell scripts that dynamically evaluate configuration triggers in `rnex.yaml` and `.local.rnex.yaml`.
319
+ Repo Nexus manages workspace-level Git hooks via `git config core.hooksPath .rnex/hooks` and configuration in `rnex.yaml` (`git_hooks: true|false`). The hooks are native shell scripts that dynamically evaluate configuration triggers in `rnex.yaml` and `.local.rnex.yaml`.
320
320
 
321
321
  ```sh
322
- rnex hooks <install|uninstall|status>
322
+ rnex hooks <install|uninstall|status> [--local]
323
323
  ```
324
324
 
325
+ ### Configuration (`rnex.yaml` / `.local.rnex.yaml`)
326
+ ```yaml
327
+ # Enable or disable automated Git hooks
328
+ git_hooks: true # default: true (when in a Git repository)
329
+ ```
330
+ - When `git_hooks: true`, `rnex fix` ensures hooks are installed and up to date.
331
+ - When `git_hooks: false`, `rnex fix` disables and uninstalls hooks from Git.
332
+ - Overrides can be placed in `.local.rnex.yaml` to disable hooks locally without modifying team config.
333
+
325
334
  ### Subcommands
326
335
 
327
- #### `rnex hooks install`
328
- Installs native hook dispatchers into `.rnex/hooks/` and configures Git:
336
+ #### `rnex hooks install [--local]`
337
+ Installs native hook dispatchers into `.rnex/hooks/`, sets `core.hooksPath = .rnex/hooks`, and records `git_hooks: true` in `rnex.yaml` (or `.local.rnex.yaml` if `--local` is passed):
329
338
  - **`post-merge`**: Automatically runs `rnex fix -y --quiet` whenever you run `git pull` or merge upstream changes, ensuring new repositories or plugin additions sync instantly.
330
339
  - **`post-commit`**: Evaluates `lint_trigger: git-post-commit`. If `/raw/` or `/wiki/` files were committed, alerts the user to run maintenance workflows.
331
340
  - **`pre-commit`**: Evaluates `lint_trigger: git-pre-commit`. Validates staged files before committing.
332
341
  - **`pre-push`**: Evaluates `lint_trigger: git-pre-push`. Validates changes before pushing to remote.
333
342
 
334
343
  #### `rnex hooks status`
335
- Displays the active `core.hooksPath`, installed hook scripts, and plugin triggers detected in `rnex.yaml`.
344
+ Displays the configuration setting (`git_hooks: true/false`), active `core.hooksPath`, installed hook scripts, and plugin triggers detected in `rnex.yaml`.
336
345
 
337
- #### `rnex hooks uninstall`
338
- Unsets `core.hooksPath` in Git configuration and removes `.rnex/hooks/`.
346
+ #### `rnex hooks uninstall [--local]`
347
+ Unsets `core.hooksPath` in Git configuration, removes `.rnex/hooks/`, and records `git_hooks: false` in `rnex.yaml` (or `.local.rnex.yaml` if `--local` is passed).
339
348
 
340
349
  ---
341
350
 
@@ -59,6 +59,20 @@ ai_instructions: AGENTS.md
59
59
  #
60
60
  code_workspace: false
61
61
 
62
+ # ------------------------------------------------------------------------------
63
+ # git_hooks — Automated workspace Git hooks
64
+ # ------------------------------------------------------------------------------
65
+ # Enables automated Git hooks in `.rnex/hooks/` (post-merge auto-sync and
66
+ # optional plugin triggers like Karpathy wiki lint on commit/push).
67
+ #
68
+ # Default: true (when initialized in a Git repository)
69
+ #
70
+ # Examples:
71
+ # git_hooks: true # active (core.hooksPath = .rnex/hooks)
72
+ # git_hooks: false # disabled
73
+ #
74
+ git_hooks: true
75
+
62
76
  # ------------------------------------------------------------------------------
63
77
  # plugins — Workspace plugins and AI instruction packs
64
78
  # ------------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repo-nexus",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Lightweight, zero-dependency Virtual Meta-Repo companion for multi-repo workspaces and shared AI context (no git submodules)",
5
5
  "bin": {
6
6
  "repo-nexus": "rnex",
package/rnex CHANGED
@@ -146,6 +146,7 @@ require_workspace() {
146
146
  _load_repos_dir
147
147
  _load_ai_instructions_file
148
148
  _load_code_workspace
149
+ _load_git_hooks
149
150
  log_info "Using workspace at ${_BOLD}$NEXUS_DIR${_NC} ${_DIM}(via config: $WORKSPACE_YAML)${_NC}"
150
151
  return 0
151
152
  fi
@@ -175,6 +176,7 @@ require_workspace() {
175
176
  _load_repos_dir
176
177
  _load_ai_instructions_file
177
178
  _load_code_workspace
179
+ _load_git_hooks
178
180
 
179
181
  log_info "Using workspace at ${_BOLD}$NEXUS_DIR${_NC} ${_DIM}($(basename "$WORKSPACE_YAML") found)${_NC}"
180
182
  }
@@ -254,6 +256,18 @@ _load_code_workspace() {
254
256
  esac
255
257
  }
256
258
 
259
+ _load_git_hooks() {
260
+ _gh=""
261
+ if [ -f "$WORKSPACE_LOCAL_YAML" ]; then
262
+ _gh="$(awk '/^(git_hooks|hooks):/ { sub(/^[a-zA-Z0-9_]+:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_LOCAL_YAML")"
263
+ fi
264
+ if [ -z "$_gh" ] && [ -f "$WORKSPACE_YAML" ]; then
265
+ _gh="$(awk '/^(git_hooks|hooks):/ { sub(/^[a-zA-Z0-9_]+:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_YAML")"
266
+ fi
267
+ _gh="$(echo "$_gh" | tr -d " \t\r\n'\"")"
268
+ GIT_HOOKS_CONFIG="$_gh"
269
+ }
270
+
257
271
  # ---- YAML Parser / Manipulator (for rnex.yaml & .local.rnex.yaml) ----------
258
272
 
259
273
  _yaml_list_plugins_from_file() {
@@ -575,6 +589,60 @@ EOF
575
589
  _yaml_set_in_file "$WORKSPACE_LOCAL_YAML" "$_repo" "$_field" "$_value"
576
590
  }
577
591
 
592
+ _yaml_set_top_level_in_file() {
593
+ _file="$1"
594
+ _key="$2"
595
+ _val="$3"
596
+ [ -f "$_file" ] || return 0
597
+ _tmp="$(mktemp)"
598
+ if grep -q "^[ ]*${_key}:" "$_file" 2>/dev/null; then
599
+ awk -v key="$_key" -v val="$_val" '
600
+ $0 ~ ("^[ ]*" key ":") {
601
+ print key ": " val
602
+ next
603
+ }
604
+ { print }
605
+ ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
606
+ else
607
+ awk -v key="$_key" -v val="$_val" '
608
+ BEGIN { inserted=0 }
609
+ /^[ ]*(plugins|repos):/ && !inserted {
610
+ print key ": " val
611
+ print ""
612
+ inserted=1
613
+ }
614
+ { print }
615
+ END {
616
+ if (!inserted) {
617
+ print ""
618
+ print key ": " val
619
+ }
620
+ }
621
+ ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
622
+ fi
623
+ rm -f "$_tmp"
624
+ }
625
+
626
+ yaml_set_top_level() {
627
+ _key="$1"; _val="$2"
628
+ _yaml_set_top_level_in_file "$WORKSPACE_YAML" "$_key" "$_val"
629
+ }
630
+
631
+ yaml_set_top_level_local() {
632
+ _key="$1"; _val="$2"
633
+ if [ ! -f "$WORKSPACE_LOCAL_YAML" ]; then
634
+ cat <<'EOF' > "$WORKSPACE_LOCAL_YAML"
635
+ # ==============================================================================
636
+ # .local.rnex.yaml — Repo Nexus Local Configuration (Machine-Specific)
637
+ # ==============================================================================
638
+ # This file is gitignored. Use it to specify local repository paths, overrides,
639
+ # and enable/disable states for this workstation.
640
+
641
+ EOF
642
+ fi
643
+ _yaml_set_top_level_in_file "$WORKSPACE_LOCAL_YAML" "$_key" "$_val"
644
+ }
645
+
578
646
  # ---- Physical Repository Path Resolution ----------------------------------
579
647
 
580
648
  resolve_repo_path() {
@@ -1159,28 +1227,66 @@ upgrade_workspace_config() {
1159
1227
  ' "$_f" > "$_tmp" && mv "$_tmp" "$_f"
1160
1228
  fi
1161
1229
 
1230
+ # 4. Add git_hooks if missing
1231
+ if ! grep -q '^[ ]*\(git_hooks\|hooks\):' "$_f" 2>/dev/null; then
1232
+ _def_gh="false"
1233
+ _cur_hp="$(git -C "$NEXUS_DIR" config --get core.hooksPath 2>/dev/null || true)"
1234
+ if [ "$_cur_hp" = ".rnex/hooks" ] || [ "$_cur_hp" = "$NEXUS_DIR/.rnex/hooks" ]; then
1235
+ _def_gh="true"
1236
+ fi
1237
+ awk -v def_gh="$_def_gh" '
1238
+ BEGIN { inserted=0 }
1239
+ /^[ ]*(plugins|repos):/ && !inserted {
1240
+ print "# Automated workspace Git hooks (default: true; e.g. post-merge auto-sync, plugin triggers)"
1241
+ print "git_hooks: " def_gh
1242
+ print ""
1243
+ inserted=1
1244
+ }
1245
+ { print }
1246
+ END {
1247
+ if (!inserted) {
1248
+ print ""
1249
+ print "git_hooks: " def_gh
1250
+ }
1251
+ }
1252
+ ' "$_f" > "$_tmp" && mv "$_tmp" "$_f"
1253
+ fi
1254
+
1162
1255
  rm -f "$_tmp"
1163
1256
  }
1164
1257
 
1165
1258
  _create_hook_script() {
1166
1259
  _dst="$1"
1167
1260
  _hook_type="$2"
1168
- cat > "$_dst" <<'HOOK_EOF'
1261
+
1262
+ # Preserve custom / repository-owned wrappers that are not auto-managed by rnex
1263
+ if [ -f "$_dst" ] && ! grep -q "Managed automatically by rnex" "$_dst" 2>/dev/null; then
1264
+ return 0
1265
+ fi
1266
+
1267
+ _sub_tmp="$(mktemp)"
1268
+ cat > "$_sub_tmp" <<HOOK_EOF
1169
1269
  #!/bin/sh
1170
- # Repo Nexus Git Hook: __HOOK_TYPE__
1270
+ # Repo Nexus Git Hook: $_hook_type
1171
1271
  # Managed automatically by rnex. Do not edit manually.
1172
1272
 
1173
- _dir="$(cd "$(dirname "$0")/../.." 2>/dev/null && pwd -P)"
1273
+ _dir="\$(cd "\$(dirname "\$0")/../.." 2>/dev/null && pwd -P)"
1174
1274
  if command -v rnex >/dev/null 2>&1; then
1175
- rnex hooks run __HOOK_TYPE__ "$@"
1176
- elif [ -f "$_dir/rnex" ]; then
1177
- "$_dir/rnex" hooks run __HOOK_TYPE__ "$@"
1275
+ rnex hooks run $_hook_type "\$@"
1276
+ elif [ -f "\$_dir/rnex" ]; then
1277
+ "\$_dir/rnex" hooks run $_hook_type "\$@"
1178
1278
  fi
1179
1279
  HOOK_EOF
1180
- _sub_tmp="$(mktemp)"
1181
- sed "s/__HOOK_TYPE__/$_hook_type/g" "$_dst" > "$_sub_tmp" && mv "$_sub_tmp" "$_dst"
1182
- rm -f "$_sub_tmp"
1183
- chmod +x "$_dst"
1280
+
1281
+ chmod +x "$_sub_tmp"
1282
+
1283
+ # Avoid rewriting / modifying file if already identical (prevents inode churn & self-rewrite)
1284
+ if [ -f "$_dst" ] && cmp -s "$_sub_tmp" "$_dst"; then
1285
+ rm -f "$_sub_tmp"
1286
+ return 0
1287
+ fi
1288
+
1289
+ mv "$_sub_tmp" "$_dst"
1184
1290
  }
1185
1291
 
1186
1292
  cmd_hooks_install_dir() {
@@ -1212,11 +1318,43 @@ cmd_hooks() {
1212
1318
  case "${1:-status}" in
1213
1319
  install)
1214
1320
  require_workspace
1321
+ _local=0
1322
+ shift || true
1323
+ while [ $# -gt 0 ]; do
1324
+ case "$1" in
1325
+ --local) _local=1 ;;
1326
+ esac
1327
+ shift
1328
+ done
1215
1329
  cmd_hooks_install_dir "$NEXUS_DIR"
1330
+ if [ "$_local" -eq 1 ]; then
1331
+ yaml_set_top_level_local "git_hooks" "true"
1332
+ log_dim "Recorded git_hooks: true in $RNEX_LOCAL_CONFIG_FILE"
1333
+ else
1334
+ yaml_set_top_level "git_hooks" "true"
1335
+ log_dim "Recorded git_hooks: true in $RNEX_CONFIG_FILE"
1336
+ fi
1337
+ _load_git_hooks
1216
1338
  ;;
1217
1339
  uninstall)
1218
1340
  require_workspace
1341
+ _local=0
1342
+ shift || true
1343
+ while [ $# -gt 0 ]; do
1344
+ case "$1" in
1345
+ --local) _local=1 ;;
1346
+ esac
1347
+ shift
1348
+ done
1219
1349
  cmd_hooks_uninstall_dir "$NEXUS_DIR"
1350
+ if [ "$_local" -eq 1 ]; then
1351
+ yaml_set_top_level_local "git_hooks" "false"
1352
+ log_dim "Recorded git_hooks: false in $RNEX_LOCAL_CONFIG_FILE"
1353
+ else
1354
+ yaml_set_top_level "git_hooks" "false"
1355
+ log_dim "Recorded git_hooks: false in $RNEX_CONFIG_FILE"
1356
+ fi
1357
+ _load_git_hooks
1220
1358
  ;;
1221
1359
  status)
1222
1360
  require_workspace
@@ -1224,6 +1362,13 @@ cmd_hooks() {
1224
1362
  printf '=%.0s' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
1225
1363
  printf '\n\n'
1226
1364
 
1365
+ printf ' %b●%b Configuration: ' "$_DIM" "$_NC"
1366
+ case "$GIT_HOOKS_CONFIG" in
1367
+ true|yes|1) printf '%bgit_hooks: true%b %b(enabled in config)%b\n' "$_G" "$_NC" "$_DIM" "$_NC" ;;
1368
+ false|no|0) printf '%bgit_hooks: false%b %b(disabled in config)%b\n' "$_Y" "$_NC" "$_DIM" "$_NC" ;;
1369
+ *) printf '%bnot configured%b %b(defaults to active if git repo)%b\n' "$_DIM" "$_NC" "$_DIM" "$_NC" ;;
1370
+ esac
1371
+
1227
1372
  _hp="$(git -C "$NEXUS_DIR" config --get core.hooksPath 2>/dev/null || true)"
1228
1373
  if [ "$_hp" = ".rnex/hooks" ] || [ "$_hp" = "$NEXUS_DIR/.rnex/hooks" ]; then
1229
1374
  printf ' %b●%b core.hooksPath: %s %b(active)%b\n' "$_G" "$_NC" "$_hp" "$_DIM" "$_NC"
@@ -1258,6 +1403,7 @@ cmd_hooks() {
1258
1403
  shift
1259
1404
  _hook_name="$1"
1260
1405
  shift || true
1406
+ export _RNEX_INSIDE_HOOK=1
1261
1407
  require_workspace
1262
1408
  case "$_hook_name" in
1263
1409
  post-merge)
@@ -1271,6 +1417,9 @@ cmd_hooks() {
1271
1417
  fi
1272
1418
  fi
1273
1419
  fi
1420
+ if [ -x "$NEXUS_DIR/.githooks/post-merge" ]; then
1421
+ "$NEXUS_DIR/.githooks/post-merge" "$@" || true
1422
+ fi
1274
1423
  return 0
1275
1424
  ;;
1276
1425
  post-commit)
@@ -1284,6 +1433,9 @@ cmd_hooks() {
1284
1433
  fi
1285
1434
  fi
1286
1435
  fi
1436
+ if [ -x "$NEXUS_DIR/.githooks/post-commit" ]; then
1437
+ "$NEXUS_DIR/.githooks/post-commit" "$@" || true
1438
+ fi
1287
1439
  return 0
1288
1440
  ;;
1289
1441
  pre-commit)
@@ -1297,6 +1449,9 @@ cmd_hooks() {
1297
1449
  fi
1298
1450
  fi
1299
1451
  fi
1452
+ if [ -x "$NEXUS_DIR/.githooks/pre-commit" ]; then
1453
+ "$NEXUS_DIR/.githooks/pre-commit" "$@" || return $?
1454
+ fi
1300
1455
  return 0
1301
1456
  ;;
1302
1457
  pre-push)
@@ -1306,6 +1461,9 @@ cmd_hooks() {
1306
1461
  printf '%b[WIKI]%b Pre-push trigger: verifying wiki changes.\n' "$_C" "$_NC"
1307
1462
  fi
1308
1463
  fi
1464
+ if [ -x "$NEXUS_DIR/.githooks/pre-push" ]; then
1465
+ "$NEXUS_DIR/.githooks/pre-push" "$@" || return $?
1466
+ fi
1309
1467
  return 0
1310
1468
  ;;
1311
1469
  esac
@@ -1373,6 +1531,29 @@ cmd_init() {
1373
1531
  fi
1374
1532
  mkdir -p "$_target_dir"
1375
1533
 
1534
+ _is_git_ws=0
1535
+ if git -C "$_target_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
1536
+ _is_git_ws=1
1537
+ fi
1538
+
1539
+ _init_git_hooks="false"
1540
+ if [ "$_is_git_ws" -eq 1 ]; then
1541
+ if [ "$_cli_no_hooks" -eq 1 ] || [ "$_cli_hooks" = "false" ]; then
1542
+ _init_git_hooks="false"
1543
+ elif [ "$_cli_hooks" = "true" ] || [ "$_non_interactive" -eq 1 ]; then
1544
+ _init_git_hooks="true"
1545
+ elif [ -t 0 ]; then
1546
+ printf '%bConfigure automated Git hooks (post-merge auto-sync & wiki triggers)? [Y/n]: %b' "$_BOLD" "$_NC"
1547
+ read -r _h_ans
1548
+ case "$_h_ans" in
1549
+ n*|N*) _init_git_hooks="false" ;;
1550
+ *) _init_git_hooks="true" ;;
1551
+ esac
1552
+ else
1553
+ _init_git_hooks="true"
1554
+ fi
1555
+ fi
1556
+
1376
1557
  # ---- 1. Create config file if none exists ---------------------------------
1377
1558
  _config_created=0
1378
1559
  if [ -f "$_target_yaml" ]; then
@@ -1396,6 +1577,9 @@ ai_instructions: ${_cli_ai_instructions:-AGENTS.md}
1396
1577
  # VS Code / Cursor multi-root workspace file (default: false; e.g. true or workspace.code-workspace)
1397
1578
  code_workspace: ${_cli_code_workspace:-false}
1398
1579
 
1580
+ # Automated workspace Git hooks (default: true; e.g. post-merge auto-sync, plugin triggers)
1581
+ git_hooks: $_init_git_hooks
1582
+
1399
1583
  # Workspace plugins
1400
1584
  plugins:
1401
1585
 
@@ -1479,22 +1663,11 @@ YAML_EOF
1479
1663
  fi
1480
1664
 
1481
1665
  # ---- 7. Setup automated Git hooks if inside a Git repository ------------
1482
- if git -C "$_target_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
1483
- if [ "$_cli_no_hooks" -eq 1 ] || [ "$_cli_hooks" = "false" ]; then
1484
- log_dim "Skipping Git hooks setup (--no-hooks specified)"
1485
- elif [ "$_cli_hooks" = "true" ] || [ "$_non_interactive" -eq 1 ]; then
1666
+ if [ "$_is_git_ws" -eq 1 ]; then
1667
+ if [ "$_init_git_hooks" = "true" ]; then
1486
1668
  cmd_hooks_install_dir "$_target_dir"
1487
1669
  else
1488
- if [ -t 0 ]; then
1489
- printf '%bConfigure automated Git hooks (post-merge auto-sync & wiki triggers)? [Y/n]: %b' "$_BOLD" "$_NC"
1490
- read -r _h_ans
1491
- case "$_h_ans" in
1492
- n*|N*) log_dim "Skipping Git hooks setup (run 'rnex hooks install' later)" ;;
1493
- *) cmd_hooks_install_dir "$_target_dir" ;;
1494
- esac
1495
- else
1496
- cmd_hooks_install_dir "$_target_dir"
1497
- fi
1670
+ log_dim "Skipping Git hooks setup (run 'rnex hooks install' later to enable)"
1498
1671
  fi
1499
1672
  fi
1500
1673
 
@@ -1820,19 +1993,50 @@ cmd_status() {
1820
1993
  fi
1821
1994
 
1822
1995
  _hooks_path="$(git -C "$NEXUS_DIR" config --get core.hooksPath 2>/dev/null || true)"
1996
+ _gh_active=0
1823
1997
  if [ "$_hooks_path" = ".rnex/hooks" ] || [ "$_hooks_path" = "$NEXUS_DIR/.rnex/hooks" ]; then
1824
- _active_trigger=""
1825
- if yaml_has_plugin "karpathy-llm"; then
1826
- _active_trigger="$(yaml_plugin_get "karpathy-llm" "lint_trigger")"
1827
- fi
1828
- if [ -n "$_active_trigger" ]; then
1829
- printf ' %b✓%b Git Hooks: active (.rnex/hooks — post-merge auto-sync, %s)\n' "$_G" "$_NC" "$_active_trigger"
1830
- else
1831
- printf ' %b✓%b Git Hooks: active (.rnex/hooks — post-merge auto-sync)\n' "$_G" "$_NC"
1832
- fi
1833
- else
1834
- printf ' %b○%b Git Hooks: not configured (run "rnex hooks install" to enable)\n' "$_DIM" "$_NC"
1998
+ _gh_active=1
1835
1999
  fi
2000
+
2001
+ case "$GIT_HOOKS_CONFIG" in
2002
+ false|no|0)
2003
+ if [ "$_gh_active" -eq 1 ]; then
2004
+ printf ' %b!%b Git Hooks: active in Git, but disabled in config (git_hooks: false; run "rnex fix" to reconcile)\n' "$_Y" "$_NC"
2005
+ else
2006
+ printf ' %b○%b Git Hooks: disabled in configuration (git_hooks: false)\n' "$_DIM" "$_NC"
2007
+ fi
2008
+ ;;
2009
+ true|yes|1)
2010
+ if [ "$_gh_active" -eq 1 ]; then
2011
+ _active_trigger=""
2012
+ if yaml_has_plugin "karpathy-llm"; then
2013
+ _active_trigger="$(yaml_plugin_get "karpathy-llm" "lint_trigger")"
2014
+ fi
2015
+ if [ -n "$_active_trigger" ]; then
2016
+ printf ' %b✓%b Git Hooks: active (git_hooks: true, .rnex/hooks — %s)\n' "$_G" "$_NC" "$_active_trigger"
2017
+ else
2018
+ printf ' %b✓%b Git Hooks: active (git_hooks: true, .rnex/hooks — post-merge auto-sync)\n' "$_G" "$_NC"
2019
+ fi
2020
+ else
2021
+ printf ' %b!%b Git Hooks: enabled in config (git_hooks: true), but not active in Git (run "rnex fix")\n' "$_Y" "$_NC"
2022
+ fi
2023
+ ;;
2024
+ *)
2025
+ if [ "$_gh_active" -eq 1 ]; then
2026
+ _active_trigger=""
2027
+ if yaml_has_plugin "karpathy-llm"; then
2028
+ _active_trigger="$(yaml_plugin_get "karpathy-llm" "lint_trigger")"
2029
+ fi
2030
+ if [ -n "$_active_trigger" ]; then
2031
+ printf ' %b✓%b Git Hooks: active (.rnex/hooks — post-merge auto-sync, %s)\n' "$_G" "$_NC" "$_active_trigger"
2032
+ else
2033
+ printf ' %b✓%b Git Hooks: active (.rnex/hooks — post-merge auto-sync)\n' "$_G" "$_NC"
2034
+ fi
2035
+ else
2036
+ printf ' %b○%b Git Hooks: not configured (run "rnex hooks install" to enable)\n' "$_DIM" "$_NC"
2037
+ fi
2038
+ ;;
2039
+ esac
1836
2040
  printf '\n'
1837
2041
 
1838
2042
  printf '%bWorkspace AI Context (Routing):%b\n' "$_BOLD" "$_NC"
@@ -1969,6 +2173,7 @@ cmd_fix() {
1969
2173
  _load_repos_dir
1970
2174
  _load_ai_instructions_file
1971
2175
  _load_code_workspace
2176
+ _load_git_hooks
1972
2177
  [ "$_fix_quiet" -eq 0 ] && log_ok "Upgraded workspace configuration and routing instructions to v$RNEX_VERSION"
1973
2178
  fi
1974
2179
  fi
@@ -1985,15 +2190,41 @@ cmd_fix() {
1985
2190
  # 3. Sync standard prompts to .rnex/prompts/
1986
2191
  sync_prompts
1987
2192
 
1988
- # 4. If Git hooks are installed, ensure hook scripts are up to date
1989
- _cur_hp="$(git -C "$NEXUS_DIR" config --get core.hooksPath 2>/dev/null || true)"
1990
- if [ "$_cur_hp" = ".rnex/hooks" ] || [ "$_cur_hp" = "$NEXUS_DIR/.rnex/hooks" ]; then
1991
- _hooks_dir="$NEXUS_DIR/.rnex/hooks"
1992
- _create_hook_script "$_hooks_dir/post-merge" "post-merge"
1993
- _create_hook_script "$_hooks_dir/post-commit" "post-commit"
1994
- _create_hook_script "$_hooks_dir/pre-commit" "pre-commit"
1995
- _create_hook_script "$_hooks_dir/pre-push" "pre-push"
1996
- fi
2193
+ # 4. Reconcile Git hooks according to configuration
2194
+ case "$GIT_HOOKS_CONFIG" in
2195
+ false|no|0)
2196
+ _cur_hp="$(git -C "$NEXUS_DIR" config --get core.hooksPath 2>/dev/null || true)"
2197
+ if [ "$_cur_hp" = ".rnex/hooks" ] || [ "$_cur_hp" = "$NEXUS_DIR/.rnex/hooks" ]; then
2198
+ cmd_hooks_uninstall_dir "$NEXUS_DIR"
2199
+ fi
2200
+ ;;
2201
+ true|yes|1)
2202
+ if git -C "$NEXUS_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
2203
+ _cur_hp="$(git -C "$NEXUS_DIR" config --get core.hooksPath 2>/dev/null || true)"
2204
+ if [ "$_cur_hp" != ".rnex/hooks" ] && [ "$_cur_hp" != "$NEXUS_DIR/.rnex/hooks" ]; then
2205
+ cmd_hooks_install_dir "$NEXUS_DIR"
2206
+ elif [ "${_RNEX_INSIDE_HOOK:-0}" -ne 1 ]; then
2207
+ _hooks_dir="$NEXUS_DIR/.rnex/hooks"
2208
+ _create_hook_script "$_hooks_dir/post-merge" "post-merge"
2209
+ _create_hook_script "$_hooks_dir/post-commit" "post-commit"
2210
+ _create_hook_script "$_hooks_dir/pre-commit" "pre-commit"
2211
+ _create_hook_script "$_hooks_dir/pre-push" "pre-push"
2212
+ fi
2213
+ fi
2214
+ ;;
2215
+ *)
2216
+ _cur_hp="$(git -C "$NEXUS_DIR" config --get core.hooksPath 2>/dev/null || true)"
2217
+ if [ "$_cur_hp" = ".rnex/hooks" ] || [ "$_cur_hp" = "$NEXUS_DIR/.rnex/hooks" ]; then
2218
+ if [ "${_RNEX_INSIDE_HOOK:-0}" -ne 1 ]; then
2219
+ _hooks_dir="$NEXUS_DIR/.rnex/hooks"
2220
+ _create_hook_script "$_hooks_dir/post-merge" "post-merge"
2221
+ _create_hook_script "$_hooks_dir/post-commit" "post-commit"
2222
+ _create_hook_script "$_hooks_dir/pre-commit" "pre-commit"
2223
+ _create_hook_script "$_hooks_dir/pre-push" "pre-push"
2224
+ fi
2225
+ fi
2226
+ ;;
2227
+ esac
1997
2228
 
1998
2229
  # 5. Reconcile plugins
1999
2230
  sync_plugins
@@ -2404,8 +2635,8 @@ cmd_help() {
2404
2635
  install [dir] Install rnex & repo-nexus globally into ~/.local/bin (or custom dir)
2405
2636
  fix [-y] [--quiet] Reconcile workspace, plugins, prompts & member .rnex/ (alias: sync)
2406
2637
  update [-y] Upgrade workspace configuration and routing instructions (alias: upgrade)
2407
- hooks <install|uninstall|status>
2408
- Manage config-driven automated Git hooks (.rnex/hooks)
2638
+ hooks <install|uninstall|status> [--local]
2639
+ Manage automated Git hooks (--local writes to .local.rnex.yaml)
2409
2640
  completion <shell> Generate shell completion script (bash, zsh, fish)
2410
2641
 
2411
2642
  REPO MANAGEMENT