repo-nexus 0.7.26 → 0.7.29
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 +22 -5
- package/docs/rnex.example.yaml +4 -2
- package/lib/commands/init.sh +1 -9
- package/lib/commands/plugin.sh +4 -0
- package/lib/commands/status.sh +3 -0
- package/lib/commands/sync.sh +4 -0
- package/lib/hooks.sh +29 -8
- package/lib/plugins.sh +263 -15
- package/lib/repos.sh +3 -1
- package/lib/workspace.sh +94 -0
- package/lib/yaml.sh +172 -17
- package/package.json +1 -1
- package/templates/rnex.example.yaml +4 -2
- 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 +7 -13
- package/toolkit/plugins/karpathy-llm/agents/rnex-wiki-curator.agent.md +1 -1
- package/toolkit/plugins/karpathy-llm/hooks/after_sync.sh +82 -38
- 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 +4 -4
- 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,13 +93,16 @@ 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
|
|
|
105
108
|
# Enable or disable
|
|
@@ -109,17 +112,31 @@ plugins:
|
|
|
109
112
|
| Option | Type | Default | Description |
|
|
110
113
|
|:---|:---|:---|:---|
|
|
111
114
|
| `wiki_dir` | string | `.rnex/wiki` | Directory containing curated atomic markdown pages. |
|
|
112
|
-
| `
|
|
115
|
+
| `sources` | list | `[docs, "repos/**/README.md"]` | Intake directories and/or glob patterns relative to workspace root. Overridable in `.local.rnex.yaml`. |
|
|
113
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`). |
|
|
114
|
-
| `notify_on_changes` | boolean | `true` | Show notification banner when changes are committed to
|
|
117
|
+
| `notify_on_changes` | boolean | `true` | Show notification banner when changes are committed to intake sources or `.rnex/wiki/`. |
|
|
115
118
|
| `enabled` | boolean | `true` | Enable or disable the plugin. Overridable in `.local.rnex.yaml`. |
|
|
116
119
|
|
|
117
120
|
---
|
|
118
121
|
|
|
122
|
+
### Intake Sources (`sources`)
|
|
123
|
+
|
|
124
|
+
The `sources` setting defines where the Karpathy LLM Wiki discovers raw intake documents for `wiki-ingest` and change verification:
|
|
125
|
+
|
|
126
|
+
- **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.
|
|
127
|
+
- **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.
|
|
128
|
+
- **Single-Level Globs (e.g. `repos/*/README.md` or `docs/*.md`)**: Matches files at a fixed directory level.
|
|
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.
|
|
130
|
+
- **Two-Level Configuration Precedence**: If `sources` is declared in `.local.rnex.yaml`, it completely overrides the `sources` list defined in `rnex.yaml`.
|
|
131
|
+
- **Legacy Migration**: Existing configurations with `raw_dir: <dir>` are automatically rewritten to a `sources` list during `rnex sync`.
|
|
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.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
119
136
|
## Automated Git Hooks Integration
|
|
120
137
|
|
|
121
138
|
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
|
|
139
|
+
- **`post-merge`**: Detects if incoming commits touched intake sources or `.rnex/wiki/`, warning the developer to review or re-lint.
|
|
123
140
|
- **`post-commit`**: Reminds the developer to run `wiki-lint` or `wiki-ingest` if wiki files were committed.
|
|
124
141
|
- **`pre-commit` & `pre-push`**: Emits trigger notices if unvalidated wiki files are staged or about to be pushed.
|
|
125
142
|
|
package/docs/rnex.example.yaml
CHANGED
|
@@ -101,11 +101,13 @@ 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.
|
|
105
108
|
# wiki_dir: ".rnex/wiki" # Directory for curated atomic markdown pages (default: .rnex/wiki)
|
|
106
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)
|
|
107
110
|
# notify_on_changes: true # Show notification when changes are committed to ./docs or .rnex/wiki
|
|
108
|
-
# ingest_member_readmes: true # Automatically ingest member repository READMEs into atomic wiki pages (default: true)
|
|
109
111
|
|
|
110
112
|
|
|
111
113
|
# ------------------------------------------------------------------------------
|
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 — run "rnex sync" to rewrite to sources%b\n' "$_Y" "$_NC"
|
|
232
|
+
fi
|
|
230
233
|
fi
|
|
231
234
|
fi
|
|
232
235
|
else
|
package/lib/commands/sync.sh
CHANGED
|
@@ -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/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,12 +164,17 @@ 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
|
|
|
@@ -189,15 +193,15 @@ sync_plugins() {
|
|
|
189
193
|
done
|
|
190
194
|
|
|
191
195
|
# Clean up obsolete leaked root ./raw and ./wiki directories if non-default paths are configured
|
|
192
|
-
_raw_cfg="$(
|
|
196
|
+
_raw_cfg="$(plugin_primary_source_dir "karpathy-llm" 2>/dev/null || true)"
|
|
193
197
|
[ -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
198
|
|
|
196
199
|
_wiki_cfg="$(yaml_plugin_get "karpathy-llm" "wiki_dir" 2>/dev/null || true)"
|
|
197
200
|
[ -z "$_wiki_cfg" ] && _wiki_cfg=".rnex/wiki"
|
|
198
201
|
_wiki_cfg="$(printf '%s' "$_wiki_cfg" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^\./##' -e 's#^/##' -e 's#/$##')"
|
|
199
202
|
|
|
200
|
-
if [ "$_raw_cfg" != "raw" ] && [ -d "$NEXUS_DIR/raw" ]
|
|
203
|
+
if [ "$_raw_cfg" != "raw" ] && [ -d "$NEXUS_DIR/raw" ] && \
|
|
204
|
+
! plugin_source_dirs "karpathy-llm" 2>/dev/null | grep -qx 'raw'; then
|
|
201
205
|
if ! rmdir "$NEXUS_DIR/raw" 2>/dev/null; then
|
|
202
206
|
_can_clean=1
|
|
203
207
|
for _f in "$NEXUS_DIR/raw"/* "$NEXUS_DIR/raw"/.[!.]* "$NEXUS_DIR/raw"/..?*; do
|
|
@@ -293,7 +297,9 @@ sync_plugins() {
|
|
|
293
297
|
rm -rf "${_scoped_dest:?}/$_sub"
|
|
294
298
|
mkdir -p "$_scoped_dest/$_sub"
|
|
295
299
|
cp -R "$_pdir/$_sub"/. "$_scoped_dest/$_sub"/ 2>/dev/null || true
|
|
296
|
-
[ "$_sub" = "hooks" ]
|
|
300
|
+
if [ "$_sub" = "hooks" ]; then
|
|
301
|
+
chmod +x "$_scoped_dest/hooks"/* 2>/dev/null || true
|
|
302
|
+
fi
|
|
297
303
|
else
|
|
298
304
|
rm -rf "${_scoped_dest:?}/$_sub"
|
|
299
305
|
fi
|
|
@@ -311,6 +317,17 @@ sync_plugins() {
|
|
|
311
317
|
fi
|
|
312
318
|
fi
|
|
313
319
|
|
|
320
|
+
# Provision explicit directory sources declared under plugins.<name>.sources (e.g. ./docs)
|
|
321
|
+
for _sdir in $(plugin_source_dirs "$_pname" 2>/dev/null); do
|
|
322
|
+
[ -n "$_sdir" ] || continue
|
|
323
|
+
_full_sdir="$NEXUS_DIR/$_sdir"
|
|
324
|
+
if [ ! -d "$_full_sdir" ]; then
|
|
325
|
+
mkdir -p "$_full_sdir"
|
|
326
|
+
[ -f "$_full_sdir/.gitkeep" ] || touch "$_full_sdir/.gitkeep"
|
|
327
|
+
log_ok "Created intake source directory: $_sdir"
|
|
328
|
+
fi
|
|
329
|
+
done
|
|
330
|
+
|
|
314
331
|
# Initialize templates declared in plugin (functional external dirs like raw/, wiki/)
|
|
315
332
|
plugin_list_templates "$_pdir" | while read -r _item; do
|
|
316
333
|
[ -n "$_item" ] || continue
|
|
@@ -321,6 +338,11 @@ sync_plugins() {
|
|
|
321
338
|
|
|
322
339
|
_d="$(plugin_resolve_placeholders "$_pname" "$_pdir" "$_d")"
|
|
323
340
|
|
|
341
|
+
# No explicit directory source configured (e.g. glob-only sources): nothing to provision
|
|
342
|
+
case "$_d" in
|
|
343
|
+
*"{raw_dir}"*) continue ;;
|
|
344
|
+
esac
|
|
345
|
+
|
|
324
346
|
# Defensive guard: never create directories or files if placeholder expansion failed or quotes leaked
|
|
325
347
|
case "$_d" in
|
|
326
348
|
*"{"*"}"*|*\"*|*\'*)
|
|
@@ -335,9 +357,235 @@ sync_plugins() {
|
|
|
335
357
|
if [ -f "$_src_path" ] && [ ! -f "$_dest_path" ]; then
|
|
336
358
|
mkdir -p "$(dirname "$_dest_path")"
|
|
337
359
|
plugin_instantiate_template "$_pname" "$_pdir" "$_src_path" "$_dest_path"
|
|
338
|
-
[ -x "$_src_path" ]
|
|
360
|
+
if [ -x "$_src_path" ]; then
|
|
361
|
+
chmod +x "$_dest_path" 2>/dev/null || true
|
|
362
|
+
fi
|
|
339
363
|
log_ok "Initialized template: $_d"
|
|
340
364
|
fi
|
|
341
365
|
done
|
|
342
366
|
done
|
|
343
367
|
}
|
|
368
|
+
|
|
369
|
+
# ----------------------------------------------------------------------------
|
|
370
|
+
# Plugin source resolution (directories + POSIX glob patterns)
|
|
371
|
+
# ----------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
# True if the argument contains a glob metacharacter (*, ?, [)
|
|
374
|
+
_src_has_glob() {
|
|
375
|
+
case "$1" in
|
|
376
|
+
*'*'*|*'?'*|*'['*) return 0 ;;
|
|
377
|
+
esac
|
|
378
|
+
return 1
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
# Workspace-relative member repositories directory (default: repos)
|
|
382
|
+
_plugin_repos_rel_dir() {
|
|
383
|
+
_prd="${REPOS_DIR:-}"
|
|
384
|
+
if [ -n "$_prd" ]; then
|
|
385
|
+
_prd="${_prd#"${NEXUS_DIR:-.}"/}"
|
|
386
|
+
else
|
|
387
|
+
_prd="repos"
|
|
388
|
+
fi
|
|
389
|
+
while [ "${_prd#./}" != "$_prd" ]; do _prd="${_prd#./}"; done
|
|
390
|
+
printf '%s' "${_prd%/}"
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# Normalize a source entry: trim whitespace/CR, strip leading ./ and trailing /
|
|
394
|
+
_src_normalize_entry() {
|
|
395
|
+
_sne="$(printf '%s' "$1" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
396
|
+
while [ "${_sne#./}" != "$_sne" ]; do _sne="${_sne#./}"; done
|
|
397
|
+
[ "$_sne" = "." ] || _sne="${_sne%/}"
|
|
398
|
+
printf '%s' "$_sne"
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
# True if a normalized entry names a file rather than a directory
|
|
402
|
+
# (glob pattern, .md/.txt suffix, or an existing regular file)
|
|
403
|
+
_src_is_file_like() {
|
|
404
|
+
_src_has_glob "$1" && return 0
|
|
405
|
+
case "$1" in
|
|
406
|
+
*.md|*.txt) return 0 ;;
|
|
407
|
+
esac
|
|
408
|
+
[ -f "${NEXUS_DIR:-.}/$1" ] && return 0
|
|
409
|
+
return 1
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
# List explicit directory sources of a plugin that rnex may provision (mkdir).
|
|
413
|
+
# Skips glob patterns, file entries, absolute paths, '..' traversal, the workspace
|
|
414
|
+
# root, and paths inside the member repositories directory.
|
|
415
|
+
plugin_source_dirs() {
|
|
416
|
+
_psd_rdir="$(_plugin_repos_rel_dir)"
|
|
417
|
+
get_plugin_config_list "$1" "sources" 2>/dev/null | while IFS= read -r _psd_e || [ -n "$_psd_e" ]; do
|
|
418
|
+
_psd_e="$(_src_normalize_entry "$_psd_e")"
|
|
419
|
+
case "$_psd_e" in
|
|
420
|
+
''|.|/*|..|../*|*/..|*/../*) continue ;;
|
|
421
|
+
esac
|
|
422
|
+
_src_is_file_like "$_psd_e" && continue
|
|
423
|
+
case "$_psd_e" in
|
|
424
|
+
"$_psd_rdir"|"$_psd_rdir"/*) continue ;;
|
|
425
|
+
esac
|
|
426
|
+
printf '%s\n' "$_psd_e"
|
|
427
|
+
done
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# First provisionable directory source (legacy {raw_dir} placeholder); may be empty
|
|
431
|
+
plugin_primary_source_dir() {
|
|
432
|
+
plugin_source_dirs "$1" | sed -n '1p'
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# Human-readable source list for templates, e.g.: `docs/`, `repos/**/README.md`
|
|
436
|
+
plugin_sources_display() {
|
|
437
|
+
get_plugin_config_list "$1" "sources" 2>/dev/null | while IFS= read -r _psy_e || [ -n "$_psy_e" ]; do
|
|
438
|
+
_psy_e="$(_src_normalize_entry "$_psy_e")"
|
|
439
|
+
[ -n "$_psy_e" ] || continue
|
|
440
|
+
if _src_is_file_like "$_psy_e"; then
|
|
441
|
+
printf '\140%s\140\n' "$_psy_e"
|
|
442
|
+
else
|
|
443
|
+
printf '\140%s/\140\n' "$_psy_e"
|
|
444
|
+
fi
|
|
445
|
+
done | awk '{ out = (NR == 1) ? $0 : out ", " $0 } END { if (NR) print out }'
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
# Split a pattern into its leading wildcard-free directory prefix and the rest.
|
|
449
|
+
# The last component always stays in the rest. Sets _sp_base (default ".") and _sp_rest.
|
|
450
|
+
_src_static_prefix() {
|
|
451
|
+
_sp_base=""
|
|
452
|
+
_sp_rest="$1"
|
|
453
|
+
while :; do
|
|
454
|
+
_sp_comp="${_sp_rest%%/*}"
|
|
455
|
+
[ "$_sp_comp" = "$_sp_rest" ] && break
|
|
456
|
+
_src_has_glob "$_sp_comp" && break
|
|
457
|
+
_sp_base="${_sp_base:+$_sp_base/}$_sp_comp"
|
|
458
|
+
_sp_rest="${_sp_rest#*/}"
|
|
459
|
+
done
|
|
460
|
+
[ -z "$_sp_base" ] && _sp_base="."
|
|
461
|
+
return 0
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
# List all files under a directory, pruning hidden directories/files and node_modules
|
|
465
|
+
_src_find_dir() {
|
|
466
|
+
find "$1" ! -path "$1" -type d \( -name '.*' -o -name node_modules \) -prune -o -type f ! -name '.*' -print 2>/dev/null
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
# Resolve a recursive glob: <base>/**/<rest> (base "." for a leading **/)
|
|
470
|
+
_src_find_recursive() {
|
|
471
|
+
_fr_base="$1"; _fr_rest="$2"
|
|
472
|
+
if [ "${_fr_rest#*/}" = "$_fr_rest" ] && ! _src_has_glob "$_fr_base"; then
|
|
473
|
+
# Single trailing component: exact semantics via -name at any depth
|
|
474
|
+
[ -d "$_fr_base" ] || return 0
|
|
475
|
+
find "$_fr_base" ! -path "$_fr_base" -type d \( -name .git -o -name .rnex -o -name node_modules \) -prune \
|
|
476
|
+
-o -type f -name "$_fr_rest" -print 2>/dev/null
|
|
477
|
+
return 0
|
|
478
|
+
fi
|
|
479
|
+
_fr_start="$_fr_base"
|
|
480
|
+
if _src_has_glob "$_fr_base"; then
|
|
481
|
+
_src_static_prefix "$_fr_base/x"
|
|
482
|
+
_fr_start="$_sp_base"
|
|
483
|
+
fi
|
|
484
|
+
[ -d "$_fr_start" ] || return 0
|
|
485
|
+
find "$_fr_start" ! -path "$_fr_start" -type d \( -name .git -o -name .rnex -o -name node_modules \) -prune \
|
|
486
|
+
-o -type f \( -path "$_fr_base/$_fr_rest" -o -path "$_fr_base/*/$_fr_rest" \) -print 2>/dev/null
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
# Resolve a single-level glob (e.g. repos/*/README.md, docs/*.md) with bounded depth
|
|
490
|
+
_src_find_glob() {
|
|
491
|
+
_src_static_prefix "$1"
|
|
492
|
+
[ -d "$_sp_base" ] || return 0
|
|
493
|
+
# Prune directories at (or below) the pattern depth so find never descends further
|
|
494
|
+
_fg_depth="$_sp_base"
|
|
495
|
+
_fg_r="$_sp_rest"
|
|
496
|
+
while :; do
|
|
497
|
+
_fg_depth="$_fg_depth/*"
|
|
498
|
+
case "$_fg_r" in
|
|
499
|
+
*/*) _fg_r="${_fg_r#*/}" ;;
|
|
500
|
+
*) break ;;
|
|
501
|
+
esac
|
|
502
|
+
done
|
|
503
|
+
find "$_sp_base" ! -path "$_sp_base" -type d \( -name .git -o -name .rnex -o -name node_modules -o -path "$_fg_depth" \) -prune \
|
|
504
|
+
-o -type f -path "$_sp_base/$_sp_rest" -print 2>/dev/null
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
# Resolve one normalized source entry (run from the workspace root)
|
|
508
|
+
_src_resolve_entry() {
|
|
509
|
+
_re="$1"
|
|
510
|
+
case "$_re" in
|
|
511
|
+
'**')
|
|
512
|
+
_src_find_dir "."
|
|
513
|
+
;;
|
|
514
|
+
*/'**')
|
|
515
|
+
_re="${_re%/\*\*}"
|
|
516
|
+
[ -d "$_re" ] && _src_find_dir "$_re"
|
|
517
|
+
;;
|
|
518
|
+
'**/'*)
|
|
519
|
+
_src_find_recursive "." "${_re#\*\*/}"
|
|
520
|
+
;;
|
|
521
|
+
*'/**/'*)
|
|
522
|
+
_src_find_recursive "${_re%%/\*\*/*}" "${_re#*/\*\*/}"
|
|
523
|
+
;;
|
|
524
|
+
*)
|
|
525
|
+
if [ -d "$_re" ]; then
|
|
526
|
+
_src_find_dir "$_re"
|
|
527
|
+
elif [ -f "$_re" ]; then
|
|
528
|
+
printf '%s\n' "$_re"
|
|
529
|
+
elif _src_has_glob "$_re"; then
|
|
530
|
+
_src_find_glob "$_re"
|
|
531
|
+
fi
|
|
532
|
+
;;
|
|
533
|
+
esac
|
|
534
|
+
return 0
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
# Resolve plugin source entries (directories, files, glob patterns) into a
|
|
538
|
+
# deduplicated, sorted list of workspace-relative file paths.
|
|
539
|
+
# Input: newline-separated entries as $1 (or stdin), e.g. from get_plugin_config_list.
|
|
540
|
+
# Files inside member repositories disabled via `enabled: false` are excluded.
|
|
541
|
+
resolve_plugin_sources() {
|
|
542
|
+
_rs_root="${NEXUS_DIR:-${RNEX_ROOT:-.}}"
|
|
543
|
+
|
|
544
|
+
_saved_wy="${WORKSPACE_YAML:-}"
|
|
545
|
+
_saved_wly="${WORKSPACE_LOCAL_YAML:-}"
|
|
546
|
+
[ -z "$WORKSPACE_YAML" ] && WORKSPACE_YAML="$_rs_root/${RNEX_CONFIG_FILE:-rnex.yaml}"
|
|
547
|
+
[ -z "$WORKSPACE_LOCAL_YAML" ] && WORKSPACE_LOCAL_YAML="$_rs_root/${RNEX_LOCAL_CONFIG_FILE:-.local.rnex.yaml}"
|
|
548
|
+
|
|
549
|
+
# Workspace-relative member repositories directory (default: repos)
|
|
550
|
+
_rs_rdir="$(_plugin_repos_rel_dir)"
|
|
551
|
+
|
|
552
|
+
# Space-separated list of disabled repositories (local config overrides repo config)
|
|
553
|
+
_rs_disabled="$(yaml_list_repos 2>/dev/null | while IFS= read -r _rs_r; do
|
|
554
|
+
[ -n "$_rs_r" ] || continue
|
|
555
|
+
yaml_repo_enabled "$_rs_r" || printf '%s ' "$_rs_r"
|
|
556
|
+
done)"
|
|
557
|
+
|
|
558
|
+
_rs_out="$(
|
|
559
|
+
if [ "$#" -gt 0 ]; then
|
|
560
|
+
printf '%s\n' "$1"
|
|
561
|
+
else
|
|
562
|
+
cat
|
|
563
|
+
fi | (
|
|
564
|
+
cd "$_rs_root" || exit 0
|
|
565
|
+
while IFS= read -r _rs_e || [ -n "$_rs_e" ]; do
|
|
566
|
+
_rs_e="$(printf '%s' "$_rs_e" | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
567
|
+
while [ "${_rs_e#./}" != "$_rs_e" ]; do _rs_e="${_rs_e#./}"; done
|
|
568
|
+
[ "$_rs_e" = "." ] || _rs_e="${_rs_e%/}"
|
|
569
|
+
[ -n "$_rs_e" ] || continue
|
|
570
|
+
_src_resolve_entry "$_rs_e"
|
|
571
|
+
done
|
|
572
|
+
) | sed -e 's#^\(\./\)*##' | awk -v prefix="$_rs_rdir/" -v disabled="$_rs_disabled" '
|
|
573
|
+
BEGIN {
|
|
574
|
+
n = split(disabled, d, " ")
|
|
575
|
+
for (i = 1; i <= n; i++) off[d[i]] = 1
|
|
576
|
+
}
|
|
577
|
+
length($0) == 0 { next }
|
|
578
|
+
index($0, prefix) == 1 {
|
|
579
|
+
name = substr($0, length(prefix) + 1)
|
|
580
|
+
sub(/\/.*/, "", name)
|
|
581
|
+
if (name in off) next
|
|
582
|
+
}
|
|
583
|
+
{ print }
|
|
584
|
+
' | sort -u
|
|
585
|
+
)"
|
|
586
|
+
|
|
587
|
+
WORKSPACE_YAML="$_saved_wy"
|
|
588
|
+
WORKSPACE_LOCAL_YAML="$_saved_wly"
|
|
589
|
+
|
|
590
|
+
printf '%s\n' "$_rs_out" | awk 'NF'
|
|
591
|
+
}
|
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
|