repo-nexus 0.7.28 → 0.7.30

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.
@@ -105,9 +105,6 @@ plugins:
105
105
  # Notify developer when changes are committed to intake sources or .rnex/wiki/
106
106
  notify_on_changes: true
107
107
 
108
- # Ingest member repository READMEs into atomic wiki pages
109
- ingest_member_readmes: true
110
-
111
108
  # Enable or disable
112
109
  enabled: true
113
110
  ```
@@ -116,10 +113,8 @@ plugins:
116
113
  |:---|:---|:---|:---|
117
114
  | `wiki_dir` | string | `.rnex/wiki` | Directory containing curated atomic markdown pages. |
118
115
  | `sources` | list | `[docs, "repos/**/README.md"]` | Intake directories and/or glob patterns relative to workspace root. Overridable in `.local.rnex.yaml`. |
119
- | `raw_dir` | string | *(deprecated)* | Deprecated legacy intake directory. Transparently treated as a single `sources` entry with a migration warning. |
120
116
  | `lint_trigger` | string | `manual` | Git hook event that triggers wiki change detection (`git-post-merge`, `git-post-commit`, `git-pre-commit`, `git-pre-push`, `manual`). |
121
117
  | `notify_on_changes` | boolean | `true` | Show notification banner when changes are committed to intake sources or `.rnex/wiki/`. |
122
- | `ingest_member_readmes` | boolean | `true` | Automatically ingest member repository READMEs into atomic wiki pages. |
123
118
  | `enabled` | boolean | `true` | Enable or disable the plugin. Overridable in `.local.rnex.yaml`. |
124
119
 
125
120
  ---
@@ -133,7 +128,7 @@ The `sources` setting defines where the Karpathy LLM Wiki discovers raw intake d
133
128
  - **Single-Level Globs (e.g. `repos/*/README.md` or `docs/*.md`)**: Matches files at a fixed directory level.
134
129
  - **Active Repository Filtering**: Any resolved path located within `repos/<name>/` is checked against member repository status. If a repository is disabled (`enabled: false`) in `rnex.yaml` or `.local.rnex.yaml`, its files are automatically excluded.
135
130
  - **Two-Level Configuration Precedence**: If `sources` is declared in `.local.rnex.yaml`, it completely overrides the `sources` list defined in `rnex.yaml`.
136
- - **Legacy Fallback & Migration**: Existing configurations with `raw_dir: ./path` continue to work without disruption. Repo Nexus reads `raw_dir` as a single-element sources list and emits a non-fatal deprecation notice advising you to migrate to `sources`.
131
+ - **Legacy Migration**: Existing configurations with `raw_dir: <dir>` are automatically rewritten to a `sources` list during `rnex sync`.
137
132
  - **Directory Provisioning**: When running `rnex sync`, explicit directory entries (such as `docs`) are created automatically if missing. Glob patterns (containing `*`, `?`, or `[`) and file targets are never converted into directories.
138
133
 
139
134
  ---
@@ -105,11 +105,9 @@ plugins:
105
105
  # - docs # directory: all files (hidden dirs and node_modules pruned)
106
106
  # - "repos/**/README.md" # recursive glob (quote it); disabled repos are excluded
107
107
  # # A sources list in .local.rnex.yaml replaces this list entirely.
108
- # # Legacy 'raw_dir' is still read as a single source (deprecated).
109
108
  # wiki_dir: ".rnex/wiki" # Directory for curated atomic markdown pages (default: .rnex/wiki)
110
109
  # lint_trigger: "manual" # Runs manually via 'rnex sync' or validation checklists (git-post-merge, git-post-commit, git-pre-commit, git-pre-push, manual)
111
110
  # notify_on_changes: true # Show notification when changes are committed to ./docs or .rnex/wiki
112
- # ingest_member_readmes: true # Automatically ingest member repository READMEs into atomic wiki pages (default: true)
113
111
 
114
112
 
115
113
  # ------------------------------------------------------------------------------
@@ -228,7 +228,7 @@ cmd_status() {
228
228
  printf ' %b↳ Options: %s%b\n' "$_DIM" "$_opts" "$_NC"
229
229
  fi
230
230
  if [ "$_pname" = "karpathy-llm" ] && [ -n "$(plugin_legacy_raw_dir "$_pname" 2>/dev/null)" ]; then
231
- printf ' %b↳ [!] Deprecated: raw_dir — replace it with a sources list and run rnex sync%b\n' "$_Y" "$_NC"
231
+ printf ' %b↳ [!] Deprecated: raw_dir — run "rnex sync" to rewrite to sources%b\n' "$_Y" "$_NC"
232
232
  fi
233
233
  fi
234
234
  fi
@@ -71,6 +71,10 @@ cmd_sync() {
71
71
  [ "$_sync_quiet" -eq 0 ] && log_ok "Upgraded workspace configuration and routing instructions to v$RNEX_VERSION"
72
72
  fi
73
73
 
74
+ # Reconcile legacy plugin settings (e.g. karpathy-llm raw_dir -> sources, strip ingest_member_readmes)
75
+ migrate_karpathy_llm_config "$WORKSPACE_YAML"
76
+ [ -f "$WORKSPACE_LOCAL_YAML" ] && migrate_karpathy_llm_config "$WORKSPACE_LOCAL_YAML"
77
+
74
78
  # --------------------------------------------------------------------------
75
79
  # Step 1: Sync hardcoded files & invoke commands that do not depend on config
76
80
  # --------------------------------------------------------------------------
package/lib/core.sh CHANGED
@@ -52,6 +52,29 @@ resolve_path() {
52
52
  esac
53
53
  }
54
54
 
55
+ # Return 0 unless value is explicitly falsy (false, no, 0, disabled, off, none)
56
+ is_enabled_val() {
57
+ _iev="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
58
+ case "$_iev" in
59
+ false|no|0|disabled|off|none) return 1 ;;
60
+ *) return 0 ;;
61
+ esac
62
+ }
63
+
64
+ # Return 0 if value is explicitly truthy (true, yes, 1, on, enabled)
65
+ is_truthy() {
66
+ _it="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
67
+ case "$_it" in
68
+ true|yes|1|on|enabled) return 0 ;;
69
+ *) return 1 ;;
70
+ esac
71
+ }
72
+
73
+ # Strip surrounding whitespace, quotes, leading ./ or /, and trailing /
74
+ normalize_rel_path() {
75
+ printf '%s' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^\./##' -e 's#^/##' -e 's#/$##'
76
+ }
77
+
55
78
 
56
79
  # ---- Version Resolution (package.json is single source of truth) ----------
57
80
 
package/lib/plugins.sh CHANGED
@@ -180,11 +180,6 @@ plugin_instantiate_template() {
180
180
 
181
181
  # Sync plugins into scoped directories: .rnex/plugins/<plugin-name>/
182
182
  sync_plugins() {
183
- # Emit the legacy raw_dir deprecation notice once per sync (stdout discarded)
184
- if yaml_plugin_enabled "karpathy-llm" 2>/dev/null; then
185
- get_plugin_config_list "karpathy-llm" "sources" >/dev/null
186
- fi
187
-
188
183
  # Clean up any bogus placeholder directories accidentally created in workspace root
189
184
  for _bogus in \
190
185
  "$NEXUS_DIR/\"{wiki_dir}" \
package/lib/workspace.sh CHANGED
@@ -144,26 +144,11 @@ _init_local_yaml() {
144
144
  }
145
145
 
146
146
  _load_workspace_version() {
147
- _wv=""
148
- if [ -f "$WORKSPACE_LOCAL_YAML" ]; then
149
- _wv="$(awk '/^version:/ { sub(/^version:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_LOCAL_YAML")"
150
- fi
151
- if [ -z "$_wv" ] && [ -f "$WORKSPACE_YAML" ]; then
152
- _wv="$(awk '/^version:/ { sub(/^version:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_YAML")"
153
- fi
154
- _wv="$(echo "$_wv" | tr -d " \t\r\n'\"")"
155
- WORKSPACE_CONFIG_VERSION="$_wv"
147
+ WORKSPACE_CONFIG_VERSION="$(yaml_get_top_level "version")"
156
148
  }
157
149
 
158
150
  _load_repos_dir() {
159
- _rd=""
160
- if [ -f "$WORKSPACE_LOCAL_YAML" ]; then
161
- _rd="$(awk '/^repos_dir:/ { sub(/^repos_dir:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_LOCAL_YAML")"
162
- fi
163
- if [ -z "$_rd" ] && [ -f "$WORKSPACE_YAML" ]; then
164
- _rd="$(awk '/^repos_dir:/ { sub(/^repos_dir:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_YAML")"
165
- fi
166
- _rd="${_rd:-./repos}"
151
+ _rd="$(yaml_get_top_level "repos_dir" "./repos")"
167
152
  case "$_rd" in
168
153
  /*) REPOS_DIR="$_rd" ;;
169
154
  *) REPOS_DIR="$NEXUS_DIR/$_rd" ;;
@@ -171,14 +156,7 @@ _load_repos_dir() {
171
156
  }
172
157
 
173
158
  _load_code_workspace() {
174
- _cw=""
175
- if [ -f "$WORKSPACE_LOCAL_YAML" ]; then
176
- _cw="$(awk '/^(code_workspace|vscode_workspace):/ { sub(/^[a-zA-Z0-9_]+:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_LOCAL_YAML")"
177
- fi
178
- if [ -z "$_cw" ] && [ -f "$WORKSPACE_YAML" ]; then
179
- _cw="$(awk '/^(code_workspace|vscode_workspace):/ { sub(/^[a-zA-Z0-9_]+:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_YAML")"
180
- fi
181
- _cw="$(echo "$_cw" | tr -d " \t\r\n'\"")"
159
+ _cw="$(yaml_get_top_level "code_workspace|vscode_workspace")"
182
160
  case "$_cw" in
183
161
  true|yes|1)
184
162
  _ws_basename="$(basename "$NEXUS_DIR")"
@@ -197,15 +175,98 @@ _load_code_workspace() {
197
175
  }
198
176
 
199
177
  _load_git_hooks() {
200
- _gh=""
201
- if [ -f "$WORKSPACE_LOCAL_YAML" ]; then
202
- _gh="$(awk '/^(git_hooks|hooks):/ { sub(/^[a-zA-Z0-9_]+:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_LOCAL_YAML")"
178
+ GIT_HOOKS_CONFIG="$(yaml_get_top_level "git_hooks|hooks")"
179
+ }
180
+
181
+ migrate_karpathy_llm_config() {
182
+ _mkc_f="$1"
183
+ [ -f "$_mkc_f" ] || return 0
184
+
185
+ _mkc_has_sources=0
186
+ if command -v _yaml_plugin_list_from_file >/dev/null 2>&1; then
187
+ [ -n "$(_yaml_plugin_list_from_file "$_mkc_f" "karpathy-llm" "sources")" ] && _mkc_has_sources=1
188
+ elif grep -q '^[ \t]*sources:[ \t]*' "$_mkc_f" 2>/dev/null; then
189
+ _mkc_has_sources=1
203
190
  fi
204
- if [ -z "$_gh" ] && [ -f "$WORKSPACE_YAML" ]; then
205
- _gh="$(awk '/^(git_hooks|hooks):/ { sub(/^[a-zA-Z0-9_]+:[ ]*/, ""); sub(/[ ]*#.*/, ""); val=$0 } END { if (val) print val }' "$WORKSPACE_YAML")"
191
+
192
+ _mkc_tmp="$(mktemp)"
193
+ awk -v has_sources="$_mkc_has_sources" '
194
+ /^[ \t]*plugins:[ \t]*(#.*)?$/ { in_plugins=1; in_target=0; plugin_indent=-1; print; next }
195
+ in_plugins && /^[^ \t#]/ { in_plugins=0; in_target=0 }
196
+ {
197
+ match($0, /^[ \t]*/)
198
+ indent = RLENGTH
199
+ trimmed = substr($0, indent + 1)
200
+ }
201
+ trimmed ~ /^ingest_member_readmes:[ \t]*/ {
202
+ next
203
+ }
204
+ !in_plugins || trimmed ~ /^([#]|$)/ {
205
+ print
206
+ next
207
+ }
208
+
209
+ in_target && (indent <= plugin_indent) {
210
+ in_target=0
211
+ }
212
+
213
+ !in_target {
214
+ k = trimmed
215
+ sub(/:.*/, "", k)
216
+ gsub(/^["\047]|["\047]$/, "", k)
217
+ gsub(/[[:space:]]/, "", k)
218
+ if (k == "karpathy-llm" && trimmed ~ /:[ \t]*(#.*)?$/) {
219
+ in_target=1
220
+ plugin_indent=indent
221
+ }
222
+ print
223
+ next
224
+ }
225
+
226
+ in_target && (indent > plugin_indent) {
227
+ k = trimmed
228
+ sub(/:.*/, "", k)
229
+ gsub(/^["\047]|["\047]$/, "", k)
230
+ gsub(/[[:space:]]/, "", k)
231
+ if (k == "raw_dir" && trimmed ~ /:[ \t]*/) {
232
+ if (has_sources == 1) {
233
+ next
234
+ }
235
+ val = trimmed
236
+ sub(/^[^:]*:[ \t]*/, "", val)
237
+ if (val ~ /^"[^"]*"/) {
238
+ match(val, /^"[^"]*"/)
239
+ val = substr(val, 2, RLENGTH - 2)
240
+ } else if (val ~ /^\047[^\047]*\047/) {
241
+ match(val, /^\047[^\047]*\047/)
242
+ val = substr(val, 2, RLENGTH - 2)
243
+ } else {
244
+ sub(/[ \t]*#.*$/, "", val)
245
+ sub(/[ \t]+$/, "", val)
246
+ }
247
+ while (sub(/^\.\//, "", val)) {}
248
+ if (val != "." && val != "/") {
249
+ sub(/\/+$/, "", val)
250
+ }
251
+ if (length(val) == 0) val = "docs"
252
+ ind = substr($0, 1, indent)
253
+ if (val ~ /[*?\[]|[ \t]/) {
254
+ printf "%ssources:\n%s - \"%s\"\n", ind, ind, val
255
+ } else {
256
+ printf "%ssources:\n%s - %s\n", ind, ind, val
257
+ }
258
+ has_sources=1
259
+ next
260
+ }
261
+ }
262
+ { print }
263
+ ' "$_mkc_f" > "$_mkc_tmp"
264
+
265
+ if ! cmp -s "$_mkc_f" "$_mkc_tmp"; then
266
+ mv "$_mkc_tmp" "$_mkc_f"
267
+ else
268
+ rm -f "$_mkc_tmp"
206
269
  fi
207
- _gh="$(echo "$_gh" | tr -d " \t\r\n'\"")"
208
- GIT_HOOKS_CONFIG="$_gh"
209
270
  }
210
271
 
211
272
  upgrade_workspace_config() {
@@ -303,5 +364,8 @@ upgrade_workspace_config() {
303
364
  ' "$_f" > "$_tmp" && mv "$_tmp" "$_f"
304
365
  fi
305
366
 
367
+ # 5. Migrate legacy karpathy-llm raw_dir to sources and strip obsolete ingest_member_readmes
368
+ migrate_karpathy_llm_config "$_f"
369
+
306
370
  rm -f "$_tmp"
307
371
  }
package/lib/yaml.sh CHANGED
@@ -4,13 +4,68 @@
4
4
  # lib/yaml.sh — YAML Parser / Manipulator (for rnex.yaml & .local.rnex.yaml)
5
5
  # ============================================================================
6
6
 
7
- _yaml_list_plugins_from_file() {
8
- _f="$1"
9
- [ -f "$_f" ] || return 0
10
- awk '
11
- /^[ \t]*plugins:[ \t]*(#.*)?$/ { in_plugins=1; plugin_indent=-1; next }
12
- in_plugins && /^[^ \t#]/ { in_plugins=0 }
13
- !in_plugins { next }
7
+ # ---- Config Path & File Initialization Helpers -----------------------------
8
+
9
+ _yaml_resolve_files() {
10
+ _wy="${WORKSPACE_YAML:-}"
11
+ [ -n "$_wy" ] || _wy="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_CONFIG_FILE:-rnex.yaml}"
12
+ _wly="${WORKSPACE_LOCAL_YAML:-}"
13
+ [ -n "$_wly" ] || _wly="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
14
+ }
15
+
16
+ _ensure_local_yaml_file() {
17
+ _ely_sec="${1:-}"
18
+ if [ ! -f "$WORKSPACE_LOCAL_YAML" ]; then
19
+ cat <<EOF > "$WORKSPACE_LOCAL_YAML"
20
+ # ==============================================================================
21
+ # .local.rnex.yaml — Repo Nexus Local Configuration (Machine-Specific)
22
+ # ==============================================================================
23
+ # This file is gitignored. Use it to specify local repository paths, overrides,
24
+ # and enable/disable states for this workstation.
25
+ ${_ely_sec:+
26
+ ${_ely_sec}:}
27
+ EOF
28
+ fi
29
+ }
30
+
31
+ # ---- Generic Top-Level & Section YAML Primitives ---------------------------
32
+
33
+ _yaml_top_level_from_file() {
34
+ _ytf_f="$1"; _ytf_pat="$2"
35
+ [ -f "$_ytf_f" ] || return 0
36
+ awk -v pat="^(${_ytf_pat})[ \t]*:" '
37
+ $0 ~ pat {
38
+ line = $0
39
+ sub(/^[a-zA-Z0-9_.-]+[ \t]*:[ \t]*/, "", line)
40
+ sub(/[ \t]*#.*/, "", line)
41
+ sub(/^[ \t]+/, "", line)
42
+ sub(/[ \t\r]+$/, "", line)
43
+ gsub(/^["\047]|["\047]$/, "", line)
44
+ val = line
45
+ }
46
+ END { if (length(val) > 0) print val }
47
+ ' "$_ytf_f"
48
+ }
49
+
50
+ yaml_get_top_level() {
51
+ _ygt_pat="$1"; _ygt_def="${2:-}"
52
+ _yaml_resolve_files
53
+ _ygt_val="$(_yaml_top_level_from_file "$_wly" "$_ygt_pat")"
54
+ [ -n "$_ygt_val" ] || _ygt_val="$(_yaml_top_level_from_file "$_wy" "$_ygt_pat")"
55
+ if [ -n "$_ygt_val" ]; then
56
+ printf '%s' "$_ygt_val"
57
+ elif [ -n "$_ygt_def" ]; then
58
+ printf '%s' "$_ygt_def"
59
+ fi
60
+ }
61
+
62
+ _yaml_section_keys() {
63
+ _ysk_f="$1"; _ysk_sec="$2"
64
+ [ -f "$_ysk_f" ] || return 0
65
+ awk -v sec="$_ysk_sec" '
66
+ $0 ~ ("^[ \t]*" sec ":[ \t]*(#.*)?$") { in_sec=1; sec_indent=-1; next }
67
+ in_sec && /^[^ \t#]/ { in_sec=0 }
68
+ !in_sec { next }
14
69
  {
15
70
  match($0, /^[ \t]*/)
16
71
  indent = RLENGTH
@@ -18,18 +73,18 @@ _yaml_list_plugins_from_file() {
18
73
  }
19
74
  trimmed ~ /^([#]|$)/ { next }
20
75
 
21
- in_plugins && (plugin_indent == -1) && (indent > 0) {
22
- plugin_indent = indent
76
+ in_sec && (sec_indent == -1) && (indent > 0) {
77
+ sec_indent = indent
23
78
  }
24
79
 
25
- in_plugins && (indent == plugin_indent) {
80
+ in_sec && (indent == sec_indent) {
26
81
  if (trimmed ~ /^["\047]?[a-zA-Z0-9_.-]+["\047]?:/) {
27
82
  line = trimmed
28
83
  sub(/:.*/, "", line)
29
84
  gsub(/^["\047]|["\047]$/, "", line)
30
85
  gsub(/[[:space:]]/, "", line)
31
86
  if (length(line) > 0) print line
32
- } else if (trimmed ~ /^-[ \t]+["\047]?[a-zA-Z0-9_.-]+/) {
87
+ } else if (sec == "plugins" && trimmed ~ /^-[ \t]+["\047]?[a-zA-Z0-9_.-]+/) {
33
88
  line = trimmed
34
89
  sub(/^-[ \t]+/, "", line)
35
90
  sub(/[ \t]*#.*/, "", line)
@@ -39,27 +94,16 @@ _yaml_list_plugins_from_file() {
39
94
  if (length(line) > 0) print line
40
95
  }
41
96
  }
42
- ' "$_f"
43
- }
44
-
45
- yaml_list_plugins() {
46
- _wy="${WORKSPACE_YAML:-}"
47
- [ -z "$_wy" ] && _wy="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_CONFIG_FILE:-rnex.yaml}"
48
- _wly="${WORKSPACE_LOCAL_YAML:-}"
49
- [ -z "$_wly" ] && _wly="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
50
- {
51
- _yaml_list_plugins_from_file "$_wy"
52
- _yaml_list_plugins_from_file "$_wly"
53
- } | awk '!seen[$0]++'
97
+ ' "$_ysk_f"
54
98
  }
55
99
 
56
- _yaml_plugin_get_from_file() {
57
- _f="$1"; _pname="$2"; _key="$3"
58
- [ -f "$_f" ] || return 0
59
- awk -v plugin="$_pname" -v key="$_key" '
60
- /^[ \t]*plugins:[ \t]*(#.*)?$/ { in_plugins=1; in_target=0; plugin_indent=-1; next }
61
- in_plugins && /^[^ \t#]/ { in_plugins=0; in_target=0 }
62
- !in_plugins { next }
100
+ _yaml_section_get() {
101
+ _ysg_f="$1"; _ysg_sec="$2"; _ysg_item="$3"; _ysg_key="$4"
102
+ [ -f "$_ysg_f" ] || return 0
103
+ awk -v sec="$_ysg_sec" -v item="$_ysg_item" -v key="$_ysg_key" '
104
+ $0 ~ ("^[ \t]*" sec ":[ \t]*(#.*)?$") { in_sec=1; in_target=0; item_indent=-1; next }
105
+ in_sec && /^[^ \t#]/ { in_sec=0; in_target=0 }
106
+ !in_sec { next }
63
107
  {
64
108
  match($0, /^[ \t]*/)
65
109
  indent = RLENGTH
@@ -67,7 +111,7 @@ _yaml_plugin_get_from_file() {
67
111
  }
68
112
  trimmed ~ /^([#]|$)/ { next }
69
113
 
70
- in_target && (indent <= plugin_indent) {
114
+ in_target && (indent <= item_indent) {
71
115
  in_target=0
72
116
  }
73
117
 
@@ -76,14 +120,14 @@ _yaml_plugin_get_from_file() {
76
120
  sub(/:.*/, "", k)
77
121
  gsub(/^["\047]|["\047]$/, "", k)
78
122
  gsub(/[[:space:]]/, "", k)
79
- if (k == plugin && trimmed ~ /:[ \t]*(#.*)?$/) {
123
+ if (k == item && trimmed ~ /:[ \t]*(\{\})?[ \t]*(#.*)?$/) {
80
124
  in_target=1
81
- plugin_indent=indent
125
+ item_indent=indent
82
126
  next
83
127
  }
84
128
  }
85
129
 
86
- in_target && (indent > plugin_indent) {
130
+ in_target && (indent > item_indent) {
87
131
  k = trimmed
88
132
  sub(/:.*/, "", k)
89
133
  gsub(/^["\047]|["\047]$/, "", k)
@@ -105,15 +149,122 @@ _yaml_plugin_get_from_file() {
105
149
  exit
106
150
  }
107
151
  }
108
- ' "$_f"
152
+ ' "$_ysg_f"
153
+ }
154
+
155
+ _yaml_section_set() {
156
+ _yss_file="$1"; _yss_sec="$2"; _yss_item="$3"; _yss_field="$4"; _yss_value="$5"
157
+ if [ ! -f "$_yss_file" ]; then
158
+ mkdir -p "$(dirname "$_yss_file")"
159
+ printf '%s:\n' "$_yss_sec" > "$_yss_file"
160
+ elif ! grep -q "^${_yss_sec}:" "$_yss_file" 2>/dev/null; then
161
+ printf '\n%s:\n' "$_yss_sec" >> "$_yss_file"
162
+ fi
163
+
164
+ if ! awk -v sec="$_yss_sec" -v item="$_yss_item" '
165
+ $0 ~ ("^" sec ":[ ]*$") || $0 ~ ("^" sec ":[ ]*#") { in_s=1; next }
166
+ in_s && /^[^ #]/ { in_s=0 }
167
+ in_s && match($0, "^ " item ":[ ]*(#.*)?$") { found=1; exit }
168
+ END { exit !found }
169
+ ' "$_yss_file" 2>/dev/null; then
170
+ if grep -qE "^ $_yss_item:[ ]*(\{\}|\[\])?$" "$_yss_file" 2>/dev/null; then
171
+ _tmp="$(mktemp)"
172
+ awk -v item="$_yss_item" -v field="$_yss_field" -v value="$_yss_value" '
173
+ match($0, "^ " item ":[ ]*(\\{\\}|\\[\\])?$") {
174
+ printf " %s:\n %s: %s\n", item, field, value
175
+ next
176
+ }
177
+ { print }
178
+ ' "$_yss_file" > "$_tmp" && mv "$_tmp" "$_yss_file"
179
+ return 0
180
+ fi
181
+ _tmp="$(mktemp)"
182
+ awk -v sec="$_yss_sec" -v item="$_yss_item" -v field="$_yss_field" -v value="$_yss_value" '
183
+ $0 ~ ("^" sec ":[ ]*$") || $0 ~ ("^" sec ":[ ]*#") {
184
+ print
185
+ printf " %s:\n %s: %s\n", item, field, value
186
+ added=1
187
+ next
188
+ }
189
+ { print }
190
+ END {
191
+ if (!added) {
192
+ printf "%s:\n %s:\n %s: %s\n", sec, item, field, value
193
+ }
194
+ }
195
+ ' "$_yss_file" > "$_tmp" && mv "$_tmp" "$_yss_file"
196
+ return 0
197
+ fi
198
+
199
+ _tmp="$(mktemp)"
200
+ awk -v sec="$_yss_sec" -v item="$_yss_item" -v field="$_yss_field" -v value="$_yss_value" '
201
+ $0 ~ ("^" sec ":") { in_sec=1; print; next }
202
+ in_sec && /^[^ #]/ {
203
+ if (in_target && !field_set) {
204
+ printf " %s: %s\n", field, value
205
+ field_set=1
206
+ }
207
+ in_sec=0; in_target=0
208
+ }
209
+ in_sec && match($0, "^ " item ":[ ]*(#.*)?$") {
210
+ in_target=1; print; next
211
+ }
212
+ in_target && /^ [^ #]/ {
213
+ if (!field_set) {
214
+ printf " %s: %s\n", field, value
215
+ field_set=1
216
+ }
217
+ in_target=0
218
+ }
219
+ in_target && match($0, "^ " field ":") {
220
+ printf " %s: %s\n", field, value
221
+ field_set=1
222
+ next
223
+ }
224
+ { print }
225
+ END {
226
+ if (in_target && !field_set) {
227
+ printf " %s: %s\n", field, value
228
+ }
229
+ }
230
+ ' "$_yss_file" > "$_tmp" && mv "$_tmp" "$_yss_file"
231
+ }
232
+
233
+ _yaml_section_remove() {
234
+ _ysr_file="$1"; _ysr_sec="$2"; _ysr_name="$3"
235
+ [ -f "$_ysr_file" ] || return 0
236
+ _tmp="$(mktemp)"
237
+ awk -v sec="$_ysr_sec" -v name="$_ysr_name" '
238
+ $0 ~ ("^" sec ":") { in_sec=1; print; next }
239
+ in_sec && /^[^ #]/ { in_sec=0; skip=0 }
240
+ in_sec && match($0, "^ " name ":[ ]*(\\{\\})?[ ]*(#.*)?$") { skip=1; next }
241
+ in_sec && skip && /^ [^ #]/ { skip=0 }
242
+ skip && /^ / { next }
243
+ { print }
244
+ ' "$_ysr_file" > "$_tmp" && mv "$_tmp" "$_ysr_file"
245
+ }
246
+
247
+ # ---- Plugin Configuration Operations ---------------------------------------
248
+
249
+ _yaml_list_plugins_from_file() {
250
+ _yaml_section_keys "$1" "plugins"
251
+ }
252
+
253
+ yaml_list_plugins() {
254
+ _yaml_resolve_files
255
+ {
256
+ _yaml_list_plugins_from_file "$_wy"
257
+ _yaml_list_plugins_from_file "$_wly"
258
+ } | awk '!seen[$0]++'
259
+ }
260
+
261
+ _yaml_plugin_get_from_file() {
262
+ _yaml_section_get "$1" "plugins" "$2" "$3"
109
263
  }
110
264
 
111
265
  yaml_plugin_get() {
112
266
  _pname="$1"; _key="$2"
113
- _wy="${WORKSPACE_YAML:-}"
114
- [ -z "$_wy" ] && _wy="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_CONFIG_FILE:-rnex.yaml}"
115
- _wly="${WORKSPACE_LOCAL_YAML:-}"
116
- [ -z "$_wly" ] && _wly="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
267
+ _yaml_resolve_files
117
268
  _val="$(_yaml_plugin_get_from_file "$_wly" "$_pname" "$_key")"
118
269
  if [ -n "$_val" ]; then
119
270
  printf '%s' "$_val"
@@ -217,13 +368,10 @@ _yaml_plugin_list_from_file() {
217
368
  # deprecation warning on stderr), then to the defaults "docs" and "repos/**/README.md".
218
369
  get_plugin_config_list() {
219
370
  _gpl_plugin="$1"; _gpl_key="$2"
220
- _gpl_wy="${WORKSPACE_YAML:-}"
221
- [ -z "$_gpl_wy" ] && _gpl_wy="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_CONFIG_FILE:-rnex.yaml}"
222
- _gpl_wly="${WORKSPACE_LOCAL_YAML:-}"
223
- [ -z "$_gpl_wly" ] && _gpl_wly="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
371
+ _yaml_resolve_files
224
372
 
225
- _gpl_items="$(_yaml_plugin_list_from_file "$_gpl_wly" "$_gpl_plugin" "$_gpl_key")"
226
- [ -z "$_gpl_items" ] && _gpl_items="$(_yaml_plugin_list_from_file "$_gpl_wy" "$_gpl_plugin" "$_gpl_key")"
373
+ _gpl_items="$(_yaml_plugin_list_from_file "$_wly" "$_gpl_plugin" "$_gpl_key")"
374
+ [ -n "$_gpl_items" ] || _gpl_items="$(_yaml_plugin_list_from_file "$_wy" "$_gpl_plugin" "$_gpl_key")"
227
375
  if [ -n "$_gpl_items" ]; then
228
376
  printf '%s\n' "$_gpl_items"
229
377
  return 0
@@ -233,7 +381,7 @@ get_plugin_config_list() {
233
381
  karpathy-llm:sources)
234
382
  _gpl_raw="$(plugin_legacy_raw_dir "$_gpl_plugin")"
235
383
  if [ -n "$_gpl_raw" ]; then
236
- log_warn "plugins.$_gpl_plugin.raw_dir is deprecated; replace it with a 'sources' list in ${RNEX_CONFIG_FILE:-rnex.yaml} (or ${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}) and run 'rnex sync'." >&2
384
+ log_warn "plugins.$_gpl_plugin.raw_dir is deprecated; run 'rnex sync' to rewrite it to 'sources' in ${RNEX_CONFIG_FILE:-rnex.yaml}." >&2
237
385
  printf '%s\n' "$_gpl_raw"
238
386
  else
239
387
  printf '%s\n' "docs" "repos/**/README.md"
@@ -246,12 +394,9 @@ get_plugin_config_list() {
246
394
  # 'sources' list is configured in either config file. Prints nothing otherwise.
247
395
  plugin_legacy_raw_dir() {
248
396
  _plr_plugin="$1"
249
- _plr_wy="${WORKSPACE_YAML:-}"
250
- [ -z "$_plr_wy" ] && _plr_wy="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_CONFIG_FILE:-rnex.yaml}"
251
- _plr_wly="${WORKSPACE_LOCAL_YAML:-}"
252
- [ -z "$_plr_wly" ] && _plr_wly="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
253
- [ -n "$(_yaml_plugin_list_from_file "$_plr_wly" "$_plr_plugin" "sources")" ] && return 0
254
- [ -n "$(_yaml_plugin_list_from_file "$_plr_wy" "$_plr_plugin" "sources")" ] && return 0
397
+ _yaml_resolve_files
398
+ [ -n "$(_yaml_plugin_list_from_file "$_wly" "$_plr_plugin" "sources")" ] && return 0
399
+ [ -n "$(_yaml_plugin_list_from_file "$_wy" "$_plr_plugin" "sources")" ] && return 0
255
400
  yaml_plugin_get "$_plr_plugin" "raw_dir" 2>/dev/null || true
256
401
  }
257
402
 
@@ -284,45 +429,26 @@ yaml_has_plugin() {
284
429
  yaml_plugin_enabled() {
285
430
  _plugin="$1"
286
431
  yaml_has_plugin "$_plugin" || return 1
287
- _val="$(yaml_plugin_get "$_plugin" "enabled")"
288
- _val="$(printf '%s' "$_val" | tr '[:upper:]' '[:lower:]')"
289
- case "$_val" in
290
- false|no|0|disabled|off) return 1 ;;
291
- *) return 0 ;;
292
- esac
432
+ is_enabled_val "$(yaml_plugin_get "$_plugin" "enabled")"
293
433
  }
294
434
 
295
435
  provider_plugin_enabled() {
296
- _p="$1"
297
- if yaml_plugin_enabled "$_p"; then
298
- return 0
299
- fi
300
- return 1
436
+ yaml_plugin_enabled "$1"
301
437
  }
302
438
 
303
439
  provider_option_enabled() {
304
- _p="$1"
305
- _opt="$2"
440
+ _p="$1"; _opt="$2"
306
441
  provider_plugin_enabled "$_p" || return 1
307
- _val="$(yaml_plugin_get "$_p" "$_opt")"
308
- _val="$(printf '%s' "$_val" | tr '[:upper:]' '[:lower:]')"
309
- case "$_val" in
310
- false|no|0|disabled|off) return 1 ;;
311
- *) return 0 ;;
312
- esac
442
+ is_enabled_val "$(yaml_plugin_get "$_p" "$_opt")"
313
443
  }
314
444
 
315
445
  provider_auto_sync_enabled() {
316
446
  _p="$1"
317
447
  provider_plugin_enabled "$_p" || return 1
318
448
  _val="$(yaml_plugin_get "$_p" "auto_sync_on")"
319
- [ -z "$_val" ] && _val="$(yaml_plugin_get "$_p" "autoSyncOn")"
320
- [ -z "$_val" ] && _val="SessionStart"
321
- _val="$(printf '%s' "$_val" | tr '[:upper:]' '[:lower:]')"
322
- case "$_val" in
323
- false|no|0|disabled|off|none) return 1 ;;
324
- *) return 0 ;;
325
- esac
449
+ [ -n "$_val" ] || _val="$(yaml_plugin_get "$_p" "autoSyncOn")"
450
+ [ -n "$_val" ] || _val="SessionStart"
451
+ is_enabled_val "$_val"
326
452
  }
327
453
 
328
454
  copilot_plugin_enabled() { provider_plugin_enabled "copilot"; }
@@ -348,7 +474,6 @@ windsurf_instructions_enabled() { provider_plugin_enabled "windsurf"; }
348
474
  windsurf_skills_enabled() { provider_option_enabled "windsurf" "skills"; }
349
475
 
350
476
  has_active_provider_instructions() {
351
- _dir="${1:-${NEXUS_DIR:-$PWD}}"
352
477
  _saved_ws_yaml="${WORKSPACE_YAML:-}"
353
478
  _saved_local_yaml="${WORKSPACE_LOCAL_YAML:-}"
354
479
  if [ -n "$1" ] && [ -d "$1" ]; then
@@ -371,102 +496,16 @@ has_active_provider_instructions() {
371
496
  }
372
497
 
373
498
  _yaml_plugin_set_in_file() {
374
- _file="$1"; _plugin="$2"; _field="$3"; _value="$4"
375
- if [ ! -f "$_file" ]; then
376
- mkdir -p "$(dirname "$_file")"
377
- printf 'plugins:\n' > "$_file"
378
- elif ! grep -q '^plugins:' "$_file" 2>/dev/null; then
379
- printf '\nplugins:\n' >> "$_file"
380
- fi
381
-
382
- if ! awk -v plugin="$_plugin" '
383
- /^plugins:[ ]*$/ || /^plugins:[ ]*#/ { in_p=1; next }
384
- in_p && /^[^ #]/ { in_p=0 }
385
- in_p && match($0, "^ " plugin ":[ ]*(#.*)?$") { found=1; exit }
386
- END { exit !found }
387
- ' "$_file" 2>/dev/null; then
388
- if grep -qE "^ $_plugin:[ ]*(\{\}|\[\])?$" "$_file" 2>/dev/null; then
389
- _tmp="$(mktemp)"
390
- awk -v plugin="$_plugin" -v field="$_field" -v value="$_value" '
391
- match($0, "^ " plugin ":[ ]*(\\{\\}|\\[\\])?$") {
392
- printf " %s:\n %s: %s\n", plugin, field, value
393
- next
394
- }
395
- { print }
396
- ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
397
- return 0
398
- fi
399
- _tmp="$(mktemp)"
400
- awk -v plugin="$_plugin" -v field="$_field" -v value="$_value" '
401
- /^plugins:[ ]*$/ || /^plugins:[ ]*#/ {
402
- print
403
- printf " %s:\n %s: %s\n", plugin, field, value
404
- added=1
405
- next
406
- }
407
- { print }
408
- END {
409
- if (!added) {
410
- printf "plugins:\n %s:\n %s: %s\n", plugin, field, value
411
- }
412
- }
413
- ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
414
- return 0
415
- fi
416
-
417
- _tmp="$(mktemp)"
418
- awk -v plugin="$_plugin" -v field="$_field" -v value="$_value" '
419
- /^plugins:/ { in_plugins=1; print; next }
420
- in_plugins && /^[^ #]/ {
421
- if (in_target && !field_set) {
422
- printf " %s: %s\n", field, value
423
- field_set=1
424
- }
425
- in_plugins=0; in_target=0
426
- }
427
- in_plugins && match($0, "^ " plugin ":[ ]*(#.*)?$") {
428
- in_target=1; print; next
429
- }
430
- in_target && /^ [^ #]/ {
431
- if (!field_set) {
432
- printf " %s: %s\n", field, value
433
- field_set=1
434
- }
435
- in_target=0
436
- }
437
- in_target && match($0, "^ " field ":") {
438
- printf " %s: %s\n", field, value
439
- field_set=1
440
- next
441
- }
442
- { print }
443
- END {
444
- if (in_target && !field_set) {
445
- printf " %s: %s\n", field, value
446
- }
447
- }
448
- ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
499
+ _yaml_section_set "$1" "plugins" "$2" "$3" "$4"
449
500
  }
450
501
 
451
502
  yaml_plugin_set() {
452
- _plugin="$1"; _field="$2"; _value="$3"
453
- _yaml_plugin_set_in_file "$WORKSPACE_YAML" "$_plugin" "$_field" "$_value"
503
+ _yaml_plugin_set_in_file "$WORKSPACE_YAML" "$1" "$2" "$3"
454
504
  }
455
505
 
456
506
  yaml_plugin_set_local() {
457
- _plugin="$1"; _field="$2"; _value="$3"
458
- if [ ! -f "$WORKSPACE_LOCAL_YAML" ]; then
459
- cat <<'EOF' > "$WORKSPACE_LOCAL_YAML"
460
- # ==============================================================================
461
- # .local.rnex.yaml — Repo Nexus Local Configuration (Machine-Specific)
462
- # ==============================================================================
463
- # This file is gitignored. Use it to specify local repository paths, overrides,
464
- # and enable/disable states for this workstation.
465
-
466
- plugins:
467
- EOF
468
- fi
469
- _yaml_plugin_set_in_file "$WORKSPACE_LOCAL_YAML" "$_plugin" "$_field" "$_value"
507
+ _ensure_local_yaml_file "plugins"
508
+ _yaml_plugin_set_in_file "$WORKSPACE_LOCAL_YAML" "$1" "$2" "$3"
470
509
  }
471
510
 
472
511
  yaml_enable_plugin() {
@@ -532,17 +571,7 @@ yaml_enable_plugin() {
532
571
  }
533
572
 
534
573
  _yaml_disable_plugin_in_file() {
535
- _file="$1"; _name="$2"
536
- [ -f "$_file" ] || return 0
537
- _tmp="$(mktemp)"
538
- awk -v name="$_name" '
539
- /^plugins:/ { in_plugins=1; print; next }
540
- in_plugins && /^[^ #]/ { in_plugins=0 }
541
- in_plugins && match($0, "^ " name ":") { skip=1; next }
542
- in_plugins && skip && /^ [^ #]/ { skip=0 }
543
- skip && /^ / { next }
544
- { print }
545
- ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
574
+ _yaml_section_remove "$1" "plugins" "$2"
546
575
  }
547
576
 
548
577
  yaml_disable_plugin() {
@@ -551,68 +580,37 @@ yaml_disable_plugin() {
551
580
  _yaml_disable_plugin_in_file "$WORKSPACE_LOCAL_YAML" "$_name"
552
581
  }
553
582
 
583
+ # ---- Repository Configuration Operations -----------------------------------
584
+
554
585
  # Parse list of repos from a single config file
555
586
  _yaml_list_repos_from_file() {
556
- _f="$1"
557
- [ -f "$_f" ] || return 0
558
- awk '
559
- /^repos:[ ]*$/ || /^repos:[ ]*#/ { r=1; next }
560
- r && /^[^ #]/ { r=0 }
561
- r && /^ [a-zA-Z0-9_.-]+:[ ]*(\{\})?[ ]*(#.*)?$/ {
562
- line = $0
563
- sub(/^[ ]*/, "", line)
564
- sub(/:.*/, "", line)
565
- gsub(/[[:space:]]/, "", line)
566
- if (length(line) > 0) print line
567
- }
568
- ' "$_f"
587
+ _yaml_section_keys "$1" "repos"
569
588
  }
570
589
 
571
590
  # Parse list of repos merged from repo and local configs (order preserved, deduplicated)
572
591
  yaml_list_repos() {
573
- _w_y="${WORKSPACE_YAML:-}"
574
- [ -z "$_w_y" ] && _w_y="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_CONFIG_FILE:-rnex.yaml}"
575
- _w_ly="${WORKSPACE_LOCAL_YAML:-}"
576
- [ -z "$_w_ly" ] && _w_ly="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
592
+ _yaml_resolve_files
577
593
  {
578
- _yaml_list_repos_from_file "$_w_y"
579
- _yaml_list_repos_from_file "$_w_ly"
594
+ _yaml_list_repos_from_file "$_wy"
595
+ _yaml_list_repos_from_file "$_wly"
580
596
  } | awk '!seen[$0]++'
581
597
  }
582
598
 
583
599
  # Get a field value for a repo from a specific config file
584
600
  _yaml_get_from_file() {
585
- _f="$1"; _repo="$2"; _field="$3"
586
- [ -f "$_f" ] || return 0
587
- awk -v repo="$_repo" -v field="$_field" '
588
- /^repos:[ ]*$/ || /^repos:[ ]*#/ { r=1; next }
589
- r && /^[^ #]/ { r=0 }
590
- r && match($0, "^ " repo ":[ ]*(\\{\\})?[ ]*(#.*)?$") { in_target=1; next }
591
- in_target && /^ [^ #]/ { in_target=0 }
592
- in_target && match($0, "^ " field ":[ ]*") {
593
- line = $0
594
- sub("^ " field ":[ ]*", "", line)
595
- sub(/[ ]*#.*$/, "", line)
596
- gsub(/^["\047]|["\047]$/, "", line)
597
- print line
598
- exit
599
- }
600
- ' "$_f"
601
+ _yaml_section_get "$1" "repos" "$2" "$3"
601
602
  }
602
603
 
603
604
  # Get field value (local config overrides repo config)
604
605
  yaml_get() {
605
606
  _repo="$1"; _field="$2"
606
- _w_y="${WORKSPACE_YAML:-}"
607
- [ -z "$_w_y" ] && _w_y="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_CONFIG_FILE:-rnex.yaml}"
608
- _w_ly="${WORKSPACE_LOCAL_YAML:-}"
609
- [ -z "$_w_ly" ] && _w_ly="${NEXUS_DIR:-${RNEX_ROOT:-.}}/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
610
- _val="$(_yaml_get_from_file "$_w_ly" "$_repo" "$_field")"
607
+ _yaml_resolve_files
608
+ _val="$(_yaml_get_from_file "$_wly" "$_repo" "$_field")"
611
609
  if [ -n "$_val" ]; then
612
610
  printf '%s' "$_val"
613
611
  return 0
614
612
  fi
615
- _yaml_get_from_file "$_w_y" "$_repo" "$_field"
613
+ _yaml_get_from_file "$_wy" "$_repo" "$_field"
616
614
  }
617
615
 
618
616
  yaml_has_repo() {
@@ -621,13 +619,7 @@ yaml_has_repo() {
621
619
 
622
620
  # Check if a repo is enabled: default is true unless explicitly set to false/no/0/disabled
623
621
  yaml_repo_enabled() {
624
- _repo="$1"
625
- _val="$(yaml_get "$_repo" "enabled")"
626
- _val="$(printf '%s' "$_val" | tr '[:upper:]' '[:lower:]')"
627
- case "$_val" in
628
- false|no|0|disabled|off) return 1 ;;
629
- *) return 0 ;;
630
- esac
622
+ is_enabled_val "$(yaml_get "$1" "enabled")"
631
623
  }
632
624
 
633
625
  yaml_add_repo() {
@@ -650,17 +642,7 @@ yaml_add_repo() {
650
642
  }
651
643
 
652
644
  _yaml_remove_repo_from_file() {
653
- _file="$1"; _name="$2"
654
- [ -f "$_file" ] || return 0
655
- _tmp="$(mktemp)"
656
- awk -v name="$_name" '
657
- /^repos:/ { r=1; print; next }
658
- r && /^[^ #]/ { r=0 }
659
- r && match($0, "^ " name ":[ ]*(#.*)?$") { skip=1; next }
660
- r && skip && /^ [^ #]/ { skip=0 }
661
- skip && /^ / { next }
662
- { print }
663
- ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
645
+ _yaml_section_remove "$1" "repos" "$2"
664
646
  }
665
647
 
666
648
  yaml_remove_repo() {
@@ -670,97 +652,22 @@ yaml_remove_repo() {
670
652
  }
671
653
 
672
654
  _yaml_set_in_file() {
673
- _file="$1"; _repo="$2"; _field="$3"; _value="$4"
674
- if [ ! -f "$_file" ]; then
675
- mkdir -p "$(dirname "$_file")"
676
- printf 'repos:\n' > "$_file"
677
- elif ! grep -q '^repos:' "$_file" 2>/dev/null; then
678
- printf '\nrepos:\n' >> "$_file"
679
- fi
680
-
681
- if ! awk -v repo="$_repo" '
682
- /^repos:[ ]*$/ || /^repos:[ ]*#/ { in_r=1; next }
683
- in_r && /^[^ #]/ { in_r=0 }
684
- in_r && match($0, "^ " repo ":[ ]*(#.*)?$") { found=1; exit }
685
- END { exit !found }
686
- ' "$_file" 2>/dev/null; then
687
- _tmp="$(mktemp)"
688
- awk -v repo="$_repo" -v field="$_field" -v value="$_value" '
689
- /^repos:[ ]*$/ || /^repos:[ ]*#/ {
690
- print
691
- printf " %s:\n %s: %s\n", repo, field, value
692
- added=1
693
- next
694
- }
695
- { print }
696
- END {
697
- if (!added) {
698
- printf "repos:\n %s:\n %s: %s\n", repo, field, value
699
- }
700
- }
701
- ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
702
- return 0
703
- fi
704
-
705
- _tmp="$(mktemp)"
706
- awk -v repo="$_repo" -v field="$_field" -v value="$_value" '
707
- /^repos:/ { in_repos=1; print; next }
708
- in_repos && /^[^ #]/ {
709
- if (in_target && !field_set) {
710
- printf " %s: %s\n", field, value
711
- field_set=1
712
- }
713
- in_repos=0; in_target=0
714
- }
715
- in_repos && match($0, "^ " repo ":[ ]*(#.*)?$") {
716
- in_target=1; print; next
717
- }
718
- in_target && /^ [^ #]/ {
719
- if (!field_set) {
720
- printf " %s: %s\n", field, value
721
- field_set=1
722
- }
723
- in_target=0
724
- }
725
- in_target && match($0, "^ " field ":") {
726
- printf " %s: %s\n", field, value
727
- field_set=1
728
- next
729
- }
730
- { print }
731
- END {
732
- if (in_target && !field_set) {
733
- printf " %s: %s\n", field, value
734
- }
735
- }
736
- ' "$_file" > "$_tmp" && mv "$_tmp" "$_file"
655
+ _yaml_section_set "$1" "repos" "$2" "$3" "$4"
737
656
  }
738
657
 
739
658
  yaml_set() {
740
- _repo="$1"; _field="$2"; _value="$3"
741
- _yaml_set_in_file "$WORKSPACE_YAML" "$_repo" "$_field" "$_value"
659
+ _yaml_set_in_file "$WORKSPACE_YAML" "$1" "$2" "$3"
742
660
  }
743
661
 
744
662
  yaml_set_local() {
745
- _repo="$1"; _field="$2"; _value="$3"
746
- if [ ! -f "$WORKSPACE_LOCAL_YAML" ]; then
747
- cat <<'EOF' > "$WORKSPACE_LOCAL_YAML"
748
- # ==============================================================================
749
- # .local.rnex.yaml — Repo Nexus Local Configuration (Machine-Specific)
750
- # ==============================================================================
751
- # This file is gitignored. Use it to specify local repository paths, overrides,
752
- # and enable/disable states for this workstation.
753
-
754
- repos:
755
- EOF
756
- fi
757
- _yaml_set_in_file "$WORKSPACE_LOCAL_YAML" "$_repo" "$_field" "$_value"
663
+ _ensure_local_yaml_file "repos"
664
+ _yaml_set_in_file "$WORKSPACE_LOCAL_YAML" "$1" "$2" "$3"
758
665
  }
759
666
 
667
+ # ---- Top-Level Key Setters -------------------------------------------------
668
+
760
669
  _yaml_set_top_level_in_file() {
761
- _file="$1"
762
- _key="$2"
763
- _val="$3"
670
+ _file="$1"; _key="$2"; _val="$3"
764
671
  [ -f "$_file" ] || return 0
765
672
  _tmp="$(mktemp)"
766
673
  if grep -q "^[ ]*${_key}:" "$_file" 2>/dev/null; then
@@ -792,21 +699,10 @@ _yaml_set_top_level_in_file() {
792
699
  }
793
700
 
794
701
  yaml_set_top_level() {
795
- _key="$1"; _val="$2"
796
- _yaml_set_top_level_in_file "$WORKSPACE_YAML" "$_key" "$_val"
702
+ _yaml_set_top_level_in_file "$WORKSPACE_YAML" "$1" "$2"
797
703
  }
798
704
 
799
705
  yaml_set_top_level_local() {
800
- _key="$1"; _val="$2"
801
- if [ ! -f "$WORKSPACE_LOCAL_YAML" ]; then
802
- cat <<'EOF' > "$WORKSPACE_LOCAL_YAML"
803
- # ==============================================================================
804
- # .local.rnex.yaml — Repo Nexus Local Configuration (Machine-Specific)
805
- # ==============================================================================
806
- # This file is gitignored. Use it to specify local repository paths, overrides,
807
- # and enable/disable states for this workstation.
808
-
809
- EOF
810
- fi
811
- _yaml_set_top_level_in_file "$WORKSPACE_LOCAL_YAML" "$_key" "$_val"
706
+ _ensure_local_yaml_file ""
707
+ _yaml_set_top_level_in_file "$WORKSPACE_LOCAL_YAML" "$1" "$2"
812
708
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repo-nexus",
3
- "version": "0.7.28",
3
+ "version": "0.7.30",
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",
@@ -105,11 +105,9 @@ plugins:
105
105
  # - docs # directory: all files (hidden dirs and node_modules pruned)
106
106
  # - "repos/**/README.md" # recursive glob (quote it); disabled repos are excluded
107
107
  # # A sources list in .local.rnex.yaml replaces this list entirely.
108
- # # Legacy 'raw_dir' is still read as a single source (deprecated).
109
108
  # wiki_dir: ".rnex/wiki" # Directory for curated atomic markdown pages (default: .rnex/wiki)
110
109
  # lint_trigger: "manual" # Runs manually via 'rnex sync' or validation checklists (git-post-merge, git-post-commit, git-pre-commit, git-pre-push, manual)
111
110
  # notify_on_changes: true # Show notification when changes are committed to ./docs or .rnex/wiki
112
- # ingest_member_readmes: true # Automatically ingest member repository READMEs into atomic wiki pages (default: true)
113
111
 
114
112
 
115
113
  # ------------------------------------------------------------------------------
@@ -131,10 +131,6 @@ plugins:
131
131
 
132
132
  # Directory paths relative to workspace root (defaults shown)
133
133
  wiki_dir: .rnex/wiki # Directory for curated atomic markdown pages
134
-
135
- # Member repository README auto-ingestion (default: true)
136
- # Automatically registers member repos into wiki pages and hot.md during rnex sync
137
- ingest_member_readmes: true
138
134
  ```
139
135
 
140
136
  ### 2. Local Machine Configuration (`.local.rnex.yaml`)
@@ -158,11 +154,6 @@ plugins:
158
154
 
159
155
  ### Detailed Option Descriptions
160
156
 
161
- #### `ingest_member_readmes` (Repo Config)
162
- * **What it does:** Automatically ingests member repository READMEs into atomic wiki pages (`repo-<name>.md`) and registers them in `hot.md`.
163
- * **How it works:** During `rnex sync`, the plugin discovers configured repositories in `rnex.yaml`, extracts top-level README summaries, creates initial `repo-<name>.md` pages non-destructively, logs ingestion in `_log.md`, and updates the catalog in `hot.md`.
164
- * **Why you need it:** Provides newly onboarded AI assistants immediate context and navigation for all member repositories in the multi-repo workspace. If set to `false`, member repositories are not automatically scanned or ingested during sync.
165
-
166
157
  #### `lint_trigger` (Local Config)
167
158
  * **What it does:** Optional git hook trigger to automate wiki graph audits and maintenance workflows (`wiki-lint` and `wiki-ingest`).
168
159
  * **Default:** Unset / omitted. Automated triggers are **disabled by default**; wiki maintenance is purely manual unless you explicitly opt into a Git hook here.
@@ -183,10 +174,8 @@ plugins:
183
174
 
184
175
  | Field | Scope / File | Type | Default | Values / Behavior |
185
176
  |:---|:---|:---|:---|:---|
186
- | `ingest_member_readmes` | Repo (`rnex.yaml`) | `boolean` | `true` | Automatically ingests member repo READMEs into `repo-<name>.md` and `hot.md` during `rnex sync`. |
187
177
  | `sources` | Repo (`rnex.yaml`) | `list` | `[docs, "repos/**/README.md"]` | Directory paths and/or glob patterns for intake documents. |
188
178
  | `wiki_dir` | Repo (`rnex.yaml`) | `string` | `.rnex/wiki` | Directory containing atomic domain pages. |
189
- | `raw_dir` | Repo (`rnex.yaml`) | `string` | `./docs` | *(Deprecated)* Append-only directory for unmodified source materials. Use `sources` instead. |
190
179
  | `lint_trigger` | Local (`.local.rnex.yaml`) | `string` | *(unset)* | Optional opt-in Git hook: `git-post-commit`, `git-pre-push`, `git-post-merge`. Unset = purely manual. |
191
180
  | `notify_on_changes` | Local (`.local.rnex.yaml`) | `boolean` | `true` | Terminal alerts printed right after git commit/push when `./docs/` or `.rnex/wiki/` needs attention. |
192
181
 
@@ -72,7 +72,6 @@ get_cfg() {
72
72
  _wiki="$(get_cfg "wiki_dir" ".rnex/wiki")"
73
73
  _wiki="$(printf '%s' "$_wiki" | sed -e 's#^/##' -e 's#/$##')"
74
74
  _wiki_path="$_root/$_wiki"
75
- _ingest_readmes="$(get_cfg "ingest_member_readmes" "true")"
76
75
 
77
76
  mkdir -p "$_wiki_path"
78
77
 
@@ -420,12 +419,7 @@ LOG_EOF
420
419
  fi
421
420
  }
422
421
 
423
- # 2. Automatically ingest README.md from all registered/enabled member repositories
424
- case "$(printf '%s' "$_ingest_readmes" | tr '[:upper:]' '[:lower:]')" in
425
- false|no|0|disabled|off) _skip_all_readmes=1 ;;
426
- *) _skip_all_readmes=0 ;;
427
- esac
428
-
422
+ # 2. Automatically ingest README.md from registered/enabled member repositories when included in sources
429
423
  _rd=""
430
424
  for _cfg in "$_root/.local.rnex.yaml" "$_root/rnex.yaml"; do
431
425
  [ -f "$_cfg" ] || continue
@@ -448,6 +442,24 @@ done
448
442
  REPOS_DIR="${_rd:-./repos}"
449
443
  _clean_repos="$(printf '%s' "$REPOS_DIR" | sed -e 's#^[[:space:]]*##' -e 's#[[:space:]]*$##' -e 's#^\./##' -e 's#^/##' -e 's#/$##')"
450
444
 
445
+ _skip_all_readmes=0
446
+ if command -v get_plugin_config_list >/dev/null 2>&1; then
447
+ _src_list="$(NEXUS_DIR="$_root" WORKSPACE_YAML="$_root/rnex.yaml" WORKSPACE_LOCAL_YAML="$_root/.local.rnex.yaml" get_plugin_config_list "karpathy-llm" "sources" 2>/dev/null || true)"
448
+ if [ -n "$_src_list" ]; then
449
+ _skip_all_readmes=1
450
+ while IFS= read -r _s_item || [ -n "$_s_item" ]; do
451
+ case "$_s_item" in
452
+ *README*|*readme*|repos/*|"./repos/"*|"$_clean_repos"/*|*"**"*)
453
+ _skip_all_readmes=0
454
+ break
455
+ ;;
456
+ esac
457
+ done <<EOF_SRC
458
+ $_src_list
459
+ EOF_SRC
460
+ fi
461
+ fi
462
+
451
463
  list_configured_repos() {
452
464
  for _cfg_file in "$_root/.local.rnex.yaml" "$_root/rnex.yaml"; do
453
465
  [ -f "$_cfg_file" ] || continue
@@ -475,7 +487,6 @@ if [ "$_skip_all_readmes" -ne 1 ]; then
475
487
 
476
488
  # Check if this repository is explicitly disabled in .local.rnex.yaml or rnex.yaml (block-scoped)
477
489
  _is_disabled=""
478
- _repo_ingest=""
479
490
  for _cfg_file in "$RNEX_ROOT/.local.rnex.yaml" "$RNEX_ROOT/rnex.yaml"; do
480
491
  [ -f "$_cfg_file" ] || continue
481
492
  _en="$(awk -v repo="$repo_name" '
@@ -499,27 +510,6 @@ if [ "$_skip_all_readmes" -ne 1 ]; then
499
510
  esac
500
511
  fi
501
512
 
502
- _ingest_val="$(awk -v repo="$repo_name" '
503
- /^repos:[ ]*$/ || /^repos:[ ]*#/ { r=1; next }
504
- r && /^[^ #]/ { r=0 }
505
- r && match($0, "^ " repo ":[ ]*(\\{\\})?[ ]*(#.*)?$") { in_target=1; next }
506
- in_target && /^ [^ #]/ { in_target=0 }
507
- in_target && match($0, "^ ingest_member_readmes:[ ]*") {
508
- line = $0
509
- sub("^ ingest_member_readmes:[ ]*", "", line)
510
- sub(/[ ]*#.*$/, "", line)
511
- gsub(/^["\047]|["\047]$/, "", line)
512
- print line
513
- exit
514
- }
515
- ' "$_cfg_file")"
516
- if [ -n "$_ingest_val" ] && [ -z "$_repo_ingest" ]; then
517
- case "$(printf '%s' "$_ingest_val" | tr '[:upper:]' '[:lower:]')" in
518
- false|no|0|disabled|off) _repo_ingest=0 ;;
519
- *) _repo_ingest=1 ;;
520
- esac
521
- fi
522
-
523
513
  if [ -z "$_custom_path" ]; then
524
514
  _cp="$(awk -v repo="$repo_name" '
525
515
  /^repos:[ ]*$/ || /^repos:[ ]*#/ { r=1; next }
@@ -539,7 +529,6 @@ if [ "$_skip_all_readmes" -ne 1 ]; then
539
529
  fi
540
530
  done
541
531
  [ "$_is_disabled" = "1" ] && continue
542
- [ "$_repo_ingest" = "0" ] && continue
543
532
 
544
533
  # Resolve physical repository path and relative location
545
534
  _repo_rel=""
@@ -11,15 +11,12 @@ config_defaults:
11
11
  wiki_dir: ".rnex/wiki"
12
12
  lint_trigger: "manual" # Default changed to manual to avoid unsolicited background trigger tasks
13
13
  notify_on_changes: true
14
- ingest_member_readmes: true
15
14
 
16
15
  config_descriptions:
17
16
  sources: "<list> (intake directories and/or glob patterns; .local.rnex.yaml list replaces rnex.yaml list)"
18
17
  wiki_dir: "<dir> (directory for curated markdown pages)"
19
- raw_dir: "<dir> (deprecated: single intake directory, used only when sources is unset)"
20
18
  lint_trigger: "git-post-merge|git-post-commit|git-pre-commit|git-pre-push|manual"
21
19
  notify_on_changes: "true|false (show notification when raw/wiki changes are committed)"
22
- ingest_member_readmes: "true|false (automatically track member repository READMEs in wiki)"
23
20
 
24
21
  rules:
25
22
  - rules/behavioral.md: .rnex/plugins/karpathy-llm/rules/behavioral.md