repo-nexus 0.7.26 → 0.7.28
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 +21 -0
- package/docs/CLI.md +1 -1
- package/docs/plugins/karpathy-llm.md +27 -5
- package/docs/rnex.example.yaml +5 -1
- package/lib/commands/init.sh +1 -9
- package/lib/commands/plugin.sh +4 -0
- package/lib/commands/status.sh +3 -0
- package/lib/hooks.sh +29 -8
- package/lib/plugins.sh +268 -15
- package/lib/repos.sh +3 -1
- package/lib/yaml.sh +172 -17
- package/package.json +1 -1
- package/templates/rnex.example.yaml +5 -1
- package/toolkit/plugins/copilot/hooks/after_sync.sh +4 -4
- package/toolkit/plugins/copilot/hooks/before_plugin_disable.sh +4 -4
- package/toolkit/plugins/karpathy-llm/README.md +8 -3
- package/toolkit/plugins/karpathy-llm/agents/rnex-wiki-curator.agent.md +1 -1
- package/toolkit/plugins/karpathy-llm/hooks/after_sync.sh +63 -8
- package/toolkit/plugins/karpathy-llm/hooks/git_post-commit.sh +20 -0
- package/toolkit/plugins/karpathy-llm/hooks/git_post-merge.sh +20 -0
- package/toolkit/plugins/karpathy-llm/hooks/git_pre-commit.sh +20 -0
- package/toolkit/plugins/karpathy-llm/hooks/git_pre-push.sh +20 -0
- package/toolkit/plugins/karpathy-llm/instructions/rnex-scaffolding-meta-prompt.md +5 -5
- package/toolkit/plugins/karpathy-llm/instructions/rnex-wiki-architecture.md +6 -6
- package/toolkit/plugins/karpathy-llm/plugin.yaml +5 -2
- package/toolkit/plugins/karpathy-llm/rules/behavioral.md +1 -1
- package/toolkit/plugins/karpathy-llm/skills/wiki-ingest.md +7 -6
- package/toolkit/plugins/karpathy-llm/skills/wiki-lint.md +7 -6
- package/toolkit/plugins/karpathy-llm/templates/rnex-wiki-page.sample.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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
|
+
## [0.7.26] - 2026-10-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Multi-Source Intake Configuration for Karpathy LLM Wiki (`sources`)**:
|
|
12
|
+
- Replaced single `raw_dir` scalar configuration with multi-value `sources` list under `plugins.karpathy-llm.sources`.
|
|
13
|
+
- Added pure POSIX `/bin/sh` glob and directory resolver (`resolve_plugin_sources`) supporting both explicit directory paths (e.g. `docs`) and recursive/single-level glob patterns (e.g. `"repos/**/README.md"`, `repos/*/README.md`).
|
|
14
|
+
- Implemented automatic exclusion of member repositories disabled locally or globally (`enabled: false`) from resolved intake paths.
|
|
15
|
+
- Enforced two-level configuration precedence: `.local.rnex.yaml` takes full precedence over `rnex.yaml` when defining `sources`.
|
|
16
|
+
- Added automatic directory pruning for `.git`, `.rnex`, and `node_modules` during recursive file traversal.
|
|
17
|
+
- Added safe directory provisioning in `rnex sync` that creates explicit directory sources (such as `docs`) while skipping wildcard patterns and file targets.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Default Intake Sources**:
|
|
21
|
+
- Default sources for `karpathy-llm` plugin are now `docs` and `"repos/**/README.md"`.
|
|
22
|
+
- Updated `rnex init` templates, `rnex status`, and plugin scaffolding to render and configure multi-line `sources`.
|
|
23
|
+
- Updated plugin lifecycle hooks (`after_sync.sh`, `git_*.sh`) and agentic skills (`wiki-ingest.md`, `wiki-lint.md`) to resolve documentation from `sources`.
|
|
24
|
+
|
|
25
|
+
### Deprecated
|
|
26
|
+
- **Legacy `raw_dir` Configuration**:
|
|
27
|
+
- `plugins.karpathy-llm.raw_dir` is deprecated in favor of `sources`. Workspaces configuring `raw_dir` are automatically read via transparent fallback with a non-fatal deprecation notice advising migration to `sources`.
|
|
28
|
+
|
|
8
29
|
## [0.7.7] - 2026-10-15
|
|
9
30
|
|
|
10
31
|
### Added
|
package/docs/CLI.md
CHANGED
|
@@ -93,7 +93,7 @@ Hooks have access to POSIX helper functions for managing User Skill pointers:
|
|
|
93
93
|
### Karpathy LLM Wiki Plugin
|
|
94
94
|
The `karpathy-llm` plugin uses lifecycle hooks to manage its domain directories:
|
|
95
95
|
- **Default curated knowledge base (`wiki_dir`)**: `.rnex/wiki`
|
|
96
|
-
- **Default
|
|
96
|
+
- **Default intake sources (`sources`)**: `docs` directory and `"repos/**/README.md"` glob pattern (legacy scalar `raw_dir` supported with deprecation notice)
|
|
97
97
|
- **Master navigation index**: `.rnex/wiki/index.md`
|
|
98
98
|
- **Rolling orientation cache**: `.rnex/wiki/hot.md`
|
|
99
99
|
- **Operation log**: `.rnex/wiki/_log.md`
|
|
@@ -93,15 +93,21 @@ plugins:
|
|
|
93
93
|
karpathy-llm:
|
|
94
94
|
# Directory paths relative to workspace root (defaults shown)
|
|
95
95
|
wiki_dir: .rnex/wiki # Directory for curated atomic markdown pages
|
|
96
|
-
|
|
96
|
+
sources: # Intake documents: directories and/or glob patterns
|
|
97
|
+
- docs # directory: all files (hidden dirs and node_modules pruned)
|
|
98
|
+
- "repos/**/README.md" # recursive glob (quote it); disabled repos are excluded
|
|
99
|
+
# A sources list in .local.rnex.yaml replaces this list entirely.
|
|
97
100
|
|
|
98
101
|
# Automated Git hook trigger for wiki verification
|
|
99
102
|
# Options: git-post-merge, git-post-commit, git-pre-commit, git-pre-push, manual
|
|
100
103
|
lint_trigger: manual
|
|
101
104
|
|
|
102
|
-
# Notify developer when changes are committed to
|
|
105
|
+
# Notify developer when changes are committed to intake sources or .rnex/wiki/
|
|
103
106
|
notify_on_changes: true
|
|
104
107
|
|
|
108
|
+
# Ingest member repository READMEs into atomic wiki pages
|
|
109
|
+
ingest_member_readmes: true
|
|
110
|
+
|
|
105
111
|
# Enable or disable
|
|
106
112
|
enabled: true
|
|
107
113
|
```
|
|
@@ -109,17 +115,33 @@ plugins:
|
|
|
109
115
|
| Option | Type | Default | Description |
|
|
110
116
|
|:---|:---|:---|:---|
|
|
111
117
|
| `wiki_dir` | string | `.rnex/wiki` | Directory containing curated atomic markdown pages. |
|
|
112
|
-
| `
|
|
118
|
+
| `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. |
|
|
113
120
|
| `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`). |
|
|
114
|
-
| `notify_on_changes` | boolean | `true` | Show notification banner when changes are committed to
|
|
121
|
+
| `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. |
|
|
115
123
|
| `enabled` | boolean | `true` | Enable or disable the plugin. Overridable in `.local.rnex.yaml`. |
|
|
116
124
|
|
|
117
125
|
---
|
|
118
126
|
|
|
127
|
+
### Intake Sources (`sources`)
|
|
128
|
+
|
|
129
|
+
The `sources` setting defines where the Karpathy LLM Wiki discovers raw intake documents for `wiki-ingest` and change verification:
|
|
130
|
+
|
|
131
|
+
- **Directory Paths (e.g. `docs`, `notes`)**: Repo Nexus recursively scans the directory for all files. Hidden directories (starting with `.`) and `node_modules` are automatically pruned.
|
|
132
|
+
- **Recursive Globs (e.g. `"repos/**/README.md"`)**: Pure POSIX `/bin/sh` pattern resolution matches files at arbitrary directory depths. Always quote globs containing `*` in YAML. Directories named `.git`, `.rnex`, and `node_modules` are pruned during traversal.
|
|
133
|
+
- **Single-Level Globs (e.g. `repos/*/README.md` or `docs/*.md`)**: Matches files at a fixed directory level.
|
|
134
|
+
- **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
|
+
- **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`.
|
|
137
|
+
- **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
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
119
141
|
## Automated Git Hooks Integration
|
|
120
142
|
|
|
121
143
|
When workspace Git hooks are installed (`rnex hooks install`), the `karpathy-llm` plugin links directly into Git events:
|
|
122
|
-
- **`post-merge`**: Detects if incoming commits touched
|
|
144
|
+
- **`post-merge`**: Detects if incoming commits touched intake sources or `.rnex/wiki/`, warning the developer to review or re-lint.
|
|
123
145
|
- **`post-commit`**: Reminds the developer to run `wiki-lint` or `wiki-ingest` if wiki files were committed.
|
|
124
146
|
- **`pre-commit` & `pre-push`**: Emits trigger notices if unvalidated wiki files are staged or about to be pushed.
|
|
125
147
|
|
package/docs/rnex.example.yaml
CHANGED
|
@@ -101,7 +101,11 @@ plugins:
|
|
|
101
101
|
# windsurf:
|
|
102
102
|
# skills: true # generate user skills in .windsurf/rules/ (default: true)
|
|
103
103
|
# karpathy-llm:
|
|
104
|
-
#
|
|
104
|
+
# sources: # Intake documents: directories and/or glob patterns (default below)
|
|
105
|
+
# - docs # directory: all files (hidden dirs and node_modules pruned)
|
|
106
|
+
# - "repos/**/README.md" # recursive glob (quote it); disabled repos are excluded
|
|
107
|
+
# # A sources list in .local.rnex.yaml replaces this list entirely.
|
|
108
|
+
# # Legacy 'raw_dir' is still read as a single source (deprecated).
|
|
105
109
|
# wiki_dir: ".rnex/wiki" # Directory for curated atomic markdown pages (default: .rnex/wiki)
|
|
106
110
|
# lint_trigger: "manual" # Runs manually via 'rnex sync' or validation checklists (git-post-merge, git-post-commit, git-pre-commit, git-pre-push, manual)
|
|
107
111
|
# notify_on_changes: true # Show notification when changes are committed to ./docs or .rnex/wiki
|
package/lib/commands/init.sh
CHANGED
|
@@ -107,15 +107,7 @@ cmd_init() {
|
|
|
107
107
|
_pdir="$(find_plugin_dir "$_p" 2>/dev/null || true)"
|
|
108
108
|
_cfg_lines=""
|
|
109
109
|
if [ -n "$_pdir" ] && [ -f "$_pdir/plugin.yaml" ]; then
|
|
110
|
-
_cfg_lines="$(
|
|
111
|
-
/^config_defaults:[ ]*$/ || /^config_defaults:[ ]*#/ { in_c=1; next }
|
|
112
|
-
in_c && /^[^ #]/ { exit }
|
|
113
|
-
in_c && /^[ ]*[a-zA-Z0-9_.-]+:[ ]*/ {
|
|
114
|
-
line = $0
|
|
115
|
-
sub(/^[ ]*/, " ", line)
|
|
116
|
-
print line
|
|
117
|
-
}
|
|
118
|
-
' "$_pdir/plugin.yaml")"
|
|
110
|
+
_cfg_lines="$(plugin_config_default_lines "$_pdir/plugin.yaml")"
|
|
119
111
|
fi
|
|
120
112
|
if [ -n "$_cfg_lines" ]; then
|
|
121
113
|
_init_plugins="${_init_plugins} ${_p}:\n${_cfg_lines}\n"
|
package/lib/commands/plugin.sh
CHANGED
|
@@ -99,6 +99,10 @@ cmd_plugin_info() {
|
|
|
99
99
|
[ -n "$_skill_target" ] && _desc="$(printf '%s' "$_desc" | sed -e "s|{skill_dir}|$_skill_target|g")"
|
|
100
100
|
|
|
101
101
|
_cur="$(yaml_plugin_get "$_name" "$_opt" 2>/dev/null || true)"
|
|
102
|
+
if [ -z "$_cur" ]; then
|
|
103
|
+
# List-valued options (e.g. sources): show the effective list, comma-separated
|
|
104
|
+
_cur="$(get_plugin_config_list "$_name" "$_opt" 2>/dev/null | awk '{ out = (NR == 1) ? $0 : out ", " $0 } END { if (NR) print out }')"
|
|
105
|
+
fi
|
|
102
106
|
if [ -z "$_cur" ]; then
|
|
103
107
|
_cur="$(awk -v k="$_opt" '
|
|
104
108
|
/^config_defaults:[ ]*$/ || /^config_defaults:[ ]*#/ { in_c=1; next }
|
package/lib/commands/status.sh
CHANGED
|
@@ -227,6 +227,9 @@ cmd_status() {
|
|
|
227
227
|
if [ -n "$_opts" ]; then
|
|
228
228
|
printf ' %b↳ Options: %s%b\n' "$_DIM" "$_opts" "$_NC"
|
|
229
229
|
fi
|
|
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"
|
|
232
|
+
fi
|
|
230
233
|
fi
|
|
231
234
|
fi
|
|
232
235
|
else
|
package/lib/hooks.sh
CHANGED
|
@@ -146,14 +146,35 @@ run_plugin_git_hooks() {
|
|
|
146
146
|
|
|
147
147
|
if [ -n "$_h_cand" ] && [ -f "$_h_cand" ]; then
|
|
148
148
|
chmod +x "$_h_cand" 2>/dev/null || true
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"$
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
if [ -n "$_stdin_data" ]; then
|
|
150
|
+
printf '%s\n' "$_stdin_data" | env \
|
|
151
|
+
NEXUS_DIR="$RNEX_ROOT" \
|
|
152
|
+
WORKSPACE_YAML="$RNEX_ROOT/${RNEX_CONFIG_FILE:-rnex.yaml}" \
|
|
153
|
+
WORKSPACE_LOCAL_YAML="$RNEX_ROOT/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}" \
|
|
154
|
+
RNEX_ROOT="$RNEX_ROOT" \
|
|
155
|
+
RNEX_CONFIG_MERGED="$RNEX_CONFIG_MERGED" \
|
|
156
|
+
RNEX_PLUGIN_DIR="$RNEX_PLUGIN_DIR" \
|
|
157
|
+
RNEX_CMD="$RNEX_CMD" \
|
|
158
|
+
RNEX_ARGS="$RNEX_ARGS" \
|
|
159
|
+
RNEX_QUIET="$RNEX_QUIET" \
|
|
160
|
+
RNEX_CONFIG_FILE="${RNEX_CONFIG_FILE:-rnex.yaml}" \
|
|
161
|
+
RNEX_LOCAL_CONFIG_FILE="${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}" \
|
|
162
|
+
"$_h_cand" "$@" || true
|
|
163
|
+
else
|
|
164
|
+
env \
|
|
165
|
+
NEXUS_DIR="$RNEX_ROOT" \
|
|
166
|
+
WORKSPACE_YAML="$RNEX_ROOT/${RNEX_CONFIG_FILE:-rnex.yaml}" \
|
|
167
|
+
WORKSPACE_LOCAL_YAML="$RNEX_ROOT/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}" \
|
|
168
|
+
RNEX_ROOT="$RNEX_ROOT" \
|
|
169
|
+
RNEX_CONFIG_MERGED="$RNEX_CONFIG_MERGED" \
|
|
170
|
+
RNEX_PLUGIN_DIR="$RNEX_PLUGIN_DIR" \
|
|
171
|
+
RNEX_CMD="$RNEX_CMD" \
|
|
172
|
+
RNEX_ARGS="$RNEX_ARGS" \
|
|
173
|
+
RNEX_QUIET="$RNEX_QUIET" \
|
|
174
|
+
RNEX_CONFIG_FILE="${RNEX_CONFIG_FILE:-rnex.yaml}" \
|
|
175
|
+
RNEX_LOCAL_CONFIG_FILE="${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}" \
|
|
176
|
+
"$_h_cand" "$@" || true
|
|
177
|
+
fi
|
|
157
178
|
fi
|
|
158
179
|
done
|
|
159
180
|
}
|
package/lib/plugins.sh
CHANGED
|
@@ -143,15 +143,14 @@ plugin_resolve_placeholders() {
|
|
|
143
143
|
[ -z "$_w_val" ] && _w_val=".rnex/wiki"
|
|
144
144
|
_w_val="$(printf '%s' "$_w_val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^\./##' -e 's#^/##' -e 's#/$##')"
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
_r_val="$(
|
|
146
|
+
# {raw_dir} (legacy alias) resolves to the first explicit directory source; when no
|
|
147
|
+
# directory source exists it stays unresolved so callers can skip the destination.
|
|
148
|
+
_r_val="$(plugin_primary_source_dir "$_p" 2>/dev/null || true)"
|
|
149
149
|
|
|
150
|
-
_res="$(printf '%s' "$_res" | sed \
|
|
151
|
-
|
|
152
|
-
-e "s|{raw_dir}|${_r_val}|g" \
|
|
153
|
-
|
|
154
|
-
-e "s|^\.\{0,1\}\/\{0,1\}raw/|${_r_val}/|")"
|
|
150
|
+
_res="$(printf '%s' "$_res" | sed -e "s|{wiki_dir}|${_w_val}|g" -e "s|^\.\{0,1\}\/\{0,1\}wiki/|${_w_val}/|")"
|
|
151
|
+
if [ -n "$_r_val" ]; then
|
|
152
|
+
_res="$(printf '%s' "$_res" | sed -e "s|{raw_dir}|${_r_val}|g" -e "s|^\.\{0,1\}\/\{0,1\}raw/|${_r_val}/|")"
|
|
153
|
+
fi
|
|
155
154
|
|
|
156
155
|
_res="$(printf '%s' "$_res" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^\./##')"
|
|
157
156
|
|
|
@@ -165,17 +164,27 @@ plugin_instantiate_template() {
|
|
|
165
164
|
[ -z "$_w_val" ] && _w_val=".rnex/wiki"
|
|
166
165
|
_w_val="$(printf '%s' "$_w_val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^/##' -e 's#/$##')"
|
|
167
166
|
|
|
168
|
-
_r_val="$(
|
|
167
|
+
_r_val="$(plugin_primary_source_dir "$_p" 2>/dev/null || true)"
|
|
169
168
|
[ -z "$_r_val" ] && _r_val="docs"
|
|
170
|
-
|
|
169
|
+
|
|
170
|
+
# {sources}: human-readable list of configured intake sources (sed-escaped)
|
|
171
|
+
_s_val="$(plugin_sources_display "$_p" 2>/dev/null || true)"
|
|
172
|
+
[ -z "$_s_val" ] && _s_val="$(printf '\140%s/\140' "$_r_val")"
|
|
173
|
+
_s_val="$(printf '%s' "$_s_val" | sed -e 's/[\\&|]/\\&/g')"
|
|
171
174
|
|
|
172
175
|
sed -e "s|{wiki_dir}|$_w_val|g" \
|
|
173
176
|
-e "s|{raw_dir}|$_r_val|g" \
|
|
177
|
+
-e "s|{sources}|$_s_val|g" \
|
|
174
178
|
"$_src" > "$_dest"
|
|
175
179
|
}
|
|
176
180
|
|
|
177
181
|
# Sync plugins into scoped directories: .rnex/plugins/<plugin-name>/
|
|
178
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
|
+
|
|
179
188
|
# Clean up any bogus placeholder directories accidentally created in workspace root
|
|
180
189
|
for _bogus in \
|
|
181
190
|
"$NEXUS_DIR/\"{wiki_dir}" \
|
|
@@ -189,15 +198,15 @@ sync_plugins() {
|
|
|
189
198
|
done
|
|
190
199
|
|
|
191
200
|
# Clean up obsolete leaked root ./raw and ./wiki directories if non-default paths are configured
|
|
192
|
-
_raw_cfg="$(
|
|
201
|
+
_raw_cfg="$(plugin_primary_source_dir "karpathy-llm" 2>/dev/null || true)"
|
|
193
202
|
[ -z "$_raw_cfg" ] && _raw_cfg="docs"
|
|
194
|
-
_raw_cfg="$(printf '%s' "$_raw_cfg" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^\./##' -e 's#^/##' -e 's#/$##')"
|
|
195
203
|
|
|
196
204
|
_wiki_cfg="$(yaml_plugin_get "karpathy-llm" "wiki_dir" 2>/dev/null || true)"
|
|
197
205
|
[ -z "$_wiki_cfg" ] && _wiki_cfg=".rnex/wiki"
|
|
198
206
|
_wiki_cfg="$(printf '%s' "$_wiki_cfg" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^\./##' -e 's#^/##' -e 's#/$##')"
|
|
199
207
|
|
|
200
|
-
if [ "$_raw_cfg" != "raw" ] && [ -d "$NEXUS_DIR/raw" ]
|
|
208
|
+
if [ "$_raw_cfg" != "raw" ] && [ -d "$NEXUS_DIR/raw" ] && \
|
|
209
|
+
! plugin_source_dirs "karpathy-llm" 2>/dev/null | grep -qx 'raw'; then
|
|
201
210
|
if ! rmdir "$NEXUS_DIR/raw" 2>/dev/null; then
|
|
202
211
|
_can_clean=1
|
|
203
212
|
for _f in "$NEXUS_DIR/raw"/* "$NEXUS_DIR/raw"/.[!.]* "$NEXUS_DIR/raw"/..?*; do
|
|
@@ -293,7 +302,9 @@ sync_plugins() {
|
|
|
293
302
|
rm -rf "${_scoped_dest:?}/$_sub"
|
|
294
303
|
mkdir -p "$_scoped_dest/$_sub"
|
|
295
304
|
cp -R "$_pdir/$_sub"/. "$_scoped_dest/$_sub"/ 2>/dev/null || true
|
|
296
|
-
[ "$_sub" = "hooks" ]
|
|
305
|
+
if [ "$_sub" = "hooks" ]; then
|
|
306
|
+
chmod +x "$_scoped_dest/hooks"/* 2>/dev/null || true
|
|
307
|
+
fi
|
|
297
308
|
else
|
|
298
309
|
rm -rf "${_scoped_dest:?}/$_sub"
|
|
299
310
|
fi
|
|
@@ -311,6 +322,17 @@ sync_plugins() {
|
|
|
311
322
|
fi
|
|
312
323
|
fi
|
|
313
324
|
|
|
325
|
+
# Provision explicit directory sources declared under plugins.<name>.sources (e.g. ./docs)
|
|
326
|
+
for _sdir in $(plugin_source_dirs "$_pname" 2>/dev/null); do
|
|
327
|
+
[ -n "$_sdir" ] || continue
|
|
328
|
+
_full_sdir="$NEXUS_DIR/$_sdir"
|
|
329
|
+
if [ ! -d "$_full_sdir" ]; then
|
|
330
|
+
mkdir -p "$_full_sdir"
|
|
331
|
+
[ -f "$_full_sdir/.gitkeep" ] || touch "$_full_sdir/.gitkeep"
|
|
332
|
+
log_ok "Created intake source directory: $_sdir"
|
|
333
|
+
fi
|
|
334
|
+
done
|
|
335
|
+
|
|
314
336
|
# Initialize templates declared in plugin (functional external dirs like raw/, wiki/)
|
|
315
337
|
plugin_list_templates "$_pdir" | while read -r _item; do
|
|
316
338
|
[ -n "$_item" ] || continue
|
|
@@ -321,6 +343,11 @@ sync_plugins() {
|
|
|
321
343
|
|
|
322
344
|
_d="$(plugin_resolve_placeholders "$_pname" "$_pdir" "$_d")"
|
|
323
345
|
|
|
346
|
+
# No explicit directory source configured (e.g. glob-only sources): nothing to provision
|
|
347
|
+
case "$_d" in
|
|
348
|
+
*"{raw_dir}"*) continue ;;
|
|
349
|
+
esac
|
|
350
|
+
|
|
324
351
|
# Defensive guard: never create directories or files if placeholder expansion failed or quotes leaked
|
|
325
352
|
case "$_d" in
|
|
326
353
|
*"{"*"}"*|*\"*|*\'*)
|
|
@@ -335,9 +362,235 @@ sync_plugins() {
|
|
|
335
362
|
if [ -f "$_src_path" ] && [ ! -f "$_dest_path" ]; then
|
|
336
363
|
mkdir -p "$(dirname "$_dest_path")"
|
|
337
364
|
plugin_instantiate_template "$_pname" "$_pdir" "$_src_path" "$_dest_path"
|
|
338
|
-
[ -x "$_src_path" ]
|
|
365
|
+
if [ -x "$_src_path" ]; then
|
|
366
|
+
chmod +x "$_dest_path" 2>/dev/null || true
|
|
367
|
+
fi
|
|
339
368
|
log_ok "Initialized template: $_d"
|
|
340
369
|
fi
|
|
341
370
|
done
|
|
342
371
|
done
|
|
343
372
|
}
|
|
373
|
+
|
|
374
|
+
# ----------------------------------------------------------------------------
|
|
375
|
+
# Plugin source resolution (directories + POSIX glob patterns)
|
|
376
|
+
# ----------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
# True if the argument contains a glob metacharacter (*, ?, [)
|
|
379
|
+
_src_has_glob() {
|
|
380
|
+
case "$1" in
|
|
381
|
+
*'*'*|*'?'*|*'['*) return 0 ;;
|
|
382
|
+
esac
|
|
383
|
+
return 1
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
# Workspace-relative member repositories directory (default: repos)
|
|
387
|
+
_plugin_repos_rel_dir() {
|
|
388
|
+
_prd="${REPOS_DIR:-}"
|
|
389
|
+
if [ -n "$_prd" ]; then
|
|
390
|
+
_prd="${_prd#"${NEXUS_DIR:-.}"/}"
|
|
391
|
+
else
|
|
392
|
+
_prd="repos"
|
|
393
|
+
fi
|
|
394
|
+
while [ "${_prd#./}" != "$_prd" ]; do _prd="${_prd#./}"; done
|
|
395
|
+
printf '%s' "${_prd%/}"
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
# Normalize a source entry: trim whitespace/CR, strip leading ./ and trailing /
|
|
399
|
+
_src_normalize_entry() {
|
|
400
|
+
_sne="$(printf '%s' "$1" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
401
|
+
while [ "${_sne#./}" != "$_sne" ]; do _sne="${_sne#./}"; done
|
|
402
|
+
[ "$_sne" = "." ] || _sne="${_sne%/}"
|
|
403
|
+
printf '%s' "$_sne"
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
# True if a normalized entry names a file rather than a directory
|
|
407
|
+
# (glob pattern, .md/.txt suffix, or an existing regular file)
|
|
408
|
+
_src_is_file_like() {
|
|
409
|
+
_src_has_glob "$1" && return 0
|
|
410
|
+
case "$1" in
|
|
411
|
+
*.md|*.txt) return 0 ;;
|
|
412
|
+
esac
|
|
413
|
+
[ -f "${NEXUS_DIR:-.}/$1" ] && return 0
|
|
414
|
+
return 1
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# List explicit directory sources of a plugin that rnex may provision (mkdir).
|
|
418
|
+
# Skips glob patterns, file entries, absolute paths, '..' traversal, the workspace
|
|
419
|
+
# root, and paths inside the member repositories directory.
|
|
420
|
+
plugin_source_dirs() {
|
|
421
|
+
_psd_rdir="$(_plugin_repos_rel_dir)"
|
|
422
|
+
get_plugin_config_list "$1" "sources" 2>/dev/null | while IFS= read -r _psd_e || [ -n "$_psd_e" ]; do
|
|
423
|
+
_psd_e="$(_src_normalize_entry "$_psd_e")"
|
|
424
|
+
case "$_psd_e" in
|
|
425
|
+
''|.|/*|..|../*|*/..|*/../*) continue ;;
|
|
426
|
+
esac
|
|
427
|
+
_src_is_file_like "$_psd_e" && continue
|
|
428
|
+
case "$_psd_e" in
|
|
429
|
+
"$_psd_rdir"|"$_psd_rdir"/*) continue ;;
|
|
430
|
+
esac
|
|
431
|
+
printf '%s\n' "$_psd_e"
|
|
432
|
+
done
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# First provisionable directory source (legacy {raw_dir} placeholder); may be empty
|
|
436
|
+
plugin_primary_source_dir() {
|
|
437
|
+
plugin_source_dirs "$1" | sed -n '1p'
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
# Human-readable source list for templates, e.g.: `docs/`, `repos/**/README.md`
|
|
441
|
+
plugin_sources_display() {
|
|
442
|
+
get_plugin_config_list "$1" "sources" 2>/dev/null | while IFS= read -r _psy_e || [ -n "$_psy_e" ]; do
|
|
443
|
+
_psy_e="$(_src_normalize_entry "$_psy_e")"
|
|
444
|
+
[ -n "$_psy_e" ] || continue
|
|
445
|
+
if _src_is_file_like "$_psy_e"; then
|
|
446
|
+
printf '\140%s\140\n' "$_psy_e"
|
|
447
|
+
else
|
|
448
|
+
printf '\140%s/\140\n' "$_psy_e"
|
|
449
|
+
fi
|
|
450
|
+
done | awk '{ out = (NR == 1) ? $0 : out ", " $0 } END { if (NR) print out }'
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
# Split a pattern into its leading wildcard-free directory prefix and the rest.
|
|
454
|
+
# The last component always stays in the rest. Sets _sp_base (default ".") and _sp_rest.
|
|
455
|
+
_src_static_prefix() {
|
|
456
|
+
_sp_base=""
|
|
457
|
+
_sp_rest="$1"
|
|
458
|
+
while :; do
|
|
459
|
+
_sp_comp="${_sp_rest%%/*}"
|
|
460
|
+
[ "$_sp_comp" = "$_sp_rest" ] && break
|
|
461
|
+
_src_has_glob "$_sp_comp" && break
|
|
462
|
+
_sp_base="${_sp_base:+$_sp_base/}$_sp_comp"
|
|
463
|
+
_sp_rest="${_sp_rest#*/}"
|
|
464
|
+
done
|
|
465
|
+
[ -z "$_sp_base" ] && _sp_base="."
|
|
466
|
+
return 0
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
# List all files under a directory, pruning hidden directories/files and node_modules
|
|
470
|
+
_src_find_dir() {
|
|
471
|
+
find "$1" ! -path "$1" -type d \( -name '.*' -o -name node_modules \) -prune -o -type f ! -name '.*' -print 2>/dev/null
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
# Resolve a recursive glob: <base>/**/<rest> (base "." for a leading **/)
|
|
475
|
+
_src_find_recursive() {
|
|
476
|
+
_fr_base="$1"; _fr_rest="$2"
|
|
477
|
+
if [ "${_fr_rest#*/}" = "$_fr_rest" ] && ! _src_has_glob "$_fr_base"; then
|
|
478
|
+
# Single trailing component: exact semantics via -name at any depth
|
|
479
|
+
[ -d "$_fr_base" ] || return 0
|
|
480
|
+
find "$_fr_base" ! -path "$_fr_base" -type d \( -name .git -o -name .rnex -o -name node_modules \) -prune \
|
|
481
|
+
-o -type f -name "$_fr_rest" -print 2>/dev/null
|
|
482
|
+
return 0
|
|
483
|
+
fi
|
|
484
|
+
_fr_start="$_fr_base"
|
|
485
|
+
if _src_has_glob "$_fr_base"; then
|
|
486
|
+
_src_static_prefix "$_fr_base/x"
|
|
487
|
+
_fr_start="$_sp_base"
|
|
488
|
+
fi
|
|
489
|
+
[ -d "$_fr_start" ] || return 0
|
|
490
|
+
find "$_fr_start" ! -path "$_fr_start" -type d \( -name .git -o -name .rnex -o -name node_modules \) -prune \
|
|
491
|
+
-o -type f \( -path "$_fr_base/$_fr_rest" -o -path "$_fr_base/*/$_fr_rest" \) -print 2>/dev/null
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# Resolve a single-level glob (e.g. repos/*/README.md, docs/*.md) with bounded depth
|
|
495
|
+
_src_find_glob() {
|
|
496
|
+
_src_static_prefix "$1"
|
|
497
|
+
[ -d "$_sp_base" ] || return 0
|
|
498
|
+
# Prune directories at (or below) the pattern depth so find never descends further
|
|
499
|
+
_fg_depth="$_sp_base"
|
|
500
|
+
_fg_r="$_sp_rest"
|
|
501
|
+
while :; do
|
|
502
|
+
_fg_depth="$_fg_depth/*"
|
|
503
|
+
case "$_fg_r" in
|
|
504
|
+
*/*) _fg_r="${_fg_r#*/}" ;;
|
|
505
|
+
*) break ;;
|
|
506
|
+
esac
|
|
507
|
+
done
|
|
508
|
+
find "$_sp_base" ! -path "$_sp_base" -type d \( -name .git -o -name .rnex -o -name node_modules -o -path "$_fg_depth" \) -prune \
|
|
509
|
+
-o -type f -path "$_sp_base/$_sp_rest" -print 2>/dev/null
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
# Resolve one normalized source entry (run from the workspace root)
|
|
513
|
+
_src_resolve_entry() {
|
|
514
|
+
_re="$1"
|
|
515
|
+
case "$_re" in
|
|
516
|
+
'**')
|
|
517
|
+
_src_find_dir "."
|
|
518
|
+
;;
|
|
519
|
+
*/'**')
|
|
520
|
+
_re="${_re%/\*\*}"
|
|
521
|
+
[ -d "$_re" ] && _src_find_dir "$_re"
|
|
522
|
+
;;
|
|
523
|
+
'**/'*)
|
|
524
|
+
_src_find_recursive "." "${_re#\*\*/}"
|
|
525
|
+
;;
|
|
526
|
+
*'/**/'*)
|
|
527
|
+
_src_find_recursive "${_re%%/\*\*/*}" "${_re#*/\*\*/}"
|
|
528
|
+
;;
|
|
529
|
+
*)
|
|
530
|
+
if [ -d "$_re" ]; then
|
|
531
|
+
_src_find_dir "$_re"
|
|
532
|
+
elif [ -f "$_re" ]; then
|
|
533
|
+
printf '%s\n' "$_re"
|
|
534
|
+
elif _src_has_glob "$_re"; then
|
|
535
|
+
_src_find_glob "$_re"
|
|
536
|
+
fi
|
|
537
|
+
;;
|
|
538
|
+
esac
|
|
539
|
+
return 0
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
# Resolve plugin source entries (directories, files, glob patterns) into a
|
|
543
|
+
# deduplicated, sorted list of workspace-relative file paths.
|
|
544
|
+
# Input: newline-separated entries as $1 (or stdin), e.g. from get_plugin_config_list.
|
|
545
|
+
# Files inside member repositories disabled via `enabled: false` are excluded.
|
|
546
|
+
resolve_plugin_sources() {
|
|
547
|
+
_rs_root="${NEXUS_DIR:-${RNEX_ROOT:-.}}"
|
|
548
|
+
|
|
549
|
+
_saved_wy="${WORKSPACE_YAML:-}"
|
|
550
|
+
_saved_wly="${WORKSPACE_LOCAL_YAML:-}"
|
|
551
|
+
[ -z "$WORKSPACE_YAML" ] && WORKSPACE_YAML="$_rs_root/${RNEX_CONFIG_FILE:-rnex.yaml}"
|
|
552
|
+
[ -z "$WORKSPACE_LOCAL_YAML" ] && WORKSPACE_LOCAL_YAML="$_rs_root/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
|
|
553
|
+
|
|
554
|
+
# Workspace-relative member repositories directory (default: repos)
|
|
555
|
+
_rs_rdir="$(_plugin_repos_rel_dir)"
|
|
556
|
+
|
|
557
|
+
# Space-separated list of disabled repositories (local config overrides repo config)
|
|
558
|
+
_rs_disabled="$(yaml_list_repos 2>/dev/null | while IFS= read -r _rs_r; do
|
|
559
|
+
[ -n "$_rs_r" ] || continue
|
|
560
|
+
yaml_repo_enabled "$_rs_r" || printf '%s ' "$_rs_r"
|
|
561
|
+
done)"
|
|
562
|
+
|
|
563
|
+
_rs_out="$(
|
|
564
|
+
if [ "$#" -gt 0 ]; then
|
|
565
|
+
printf '%s\n' "$1"
|
|
566
|
+
else
|
|
567
|
+
cat
|
|
568
|
+
fi | (
|
|
569
|
+
cd "$_rs_root" || exit 0
|
|
570
|
+
while IFS= read -r _rs_e || [ -n "$_rs_e" ]; do
|
|
571
|
+
_rs_e="$(printf '%s' "$_rs_e" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
572
|
+
while [ "${_rs_e#./}" != "$_rs_e" ]; do _rs_e="${_rs_e#./}"; done
|
|
573
|
+
[ "$_rs_e" = "." ] || _rs_e="${_rs_e%/}"
|
|
574
|
+
[ -n "$_rs_e" ] || continue
|
|
575
|
+
_src_resolve_entry "$_rs_e"
|
|
576
|
+
done
|
|
577
|
+
) | sed -e 's#^\(\./\)*##' | awk -v prefix="$_rs_rdir/" -v disabled="$_rs_disabled" '
|
|
578
|
+
BEGIN {
|
|
579
|
+
n = split(disabled, d, " ")
|
|
580
|
+
for (i = 1; i <= n; i++) off[d[i]] = 1
|
|
581
|
+
}
|
|
582
|
+
length($0) == 0 { next }
|
|
583
|
+
index($0, prefix) == 1 {
|
|
584
|
+
name = substr($0, length(prefix) + 1)
|
|
585
|
+
sub(/\/.*/, "", name)
|
|
586
|
+
if (name in off) next
|
|
587
|
+
}
|
|
588
|
+
{ print }
|
|
589
|
+
' | sort -u
|
|
590
|
+
)"
|
|
591
|
+
|
|
592
|
+
WORKSPACE_YAML="$_saved_wy"
|
|
593
|
+
WORKSPACE_LOCAL_YAML="$_saved_wly"
|
|
594
|
+
|
|
595
|
+
printf '%s\n' "$_rs_out" | awk 'NF'
|
|
596
|
+
}
|
package/lib/repos.sh
CHANGED
|
@@ -115,7 +115,9 @@ EOF
|
|
|
115
115
|
rm -rf "${_dest_pdir:?}/$_sub"
|
|
116
116
|
mkdir -p "$_dest_pdir/$_sub"
|
|
117
117
|
cp -R "$_pdir/$_sub"/. "$_dest_pdir/$_sub"/ 2>/dev/null || true
|
|
118
|
-
[ "$_sub" = "hooks" ]
|
|
118
|
+
if [ "$_sub" = "hooks" ]; then
|
|
119
|
+
chmod +x "$_dest_pdir/hooks"/* 2>/dev/null || true
|
|
120
|
+
fi
|
|
119
121
|
else
|
|
120
122
|
rm -rf "${_dest_pdir:?}/$_sub"
|
|
121
123
|
fi
|