repo-nexus 0.8.7 → 0.8.8
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/README.md +16 -5
- package/lib/ai.sh +227 -78
- package/lib/commands/init.sh +1 -4
- package/lib/commands/plugin.sh +1 -9
- package/lib/commands/status.sh +29 -19
- package/lib/commands/sync.sh +1 -4
- package/lib/core.sh +2 -2
- package/lib/hooks.sh +1 -4
- package/lib/plugins.sh +36 -7
- package/lib/repos.sh +0 -5
- package/lib/sdk.sh +110 -31
- package/lib/workspace.sh +4 -0
- package/lib/yaml.sh +11 -50
- package/package.json +1 -1
- package/rnex +1 -0
- package/toolkit/plugins/claude/hooks/after_sync.sh +5 -0
- package/toolkit/plugins/claude/hooks/before_plugin_disable.sh +6 -0
- package/toolkit/plugins/codex/hooks/after_sync.sh +5 -0
- package/toolkit/plugins/codex/hooks/before_plugin_disable.sh +6 -0
- package/toolkit/plugins/copilot/hooks/after_sync.sh +5 -0
- package/toolkit/plugins/copilot/hooks/before_plugin_disable.sh +6 -0
- package/toolkit/plugins/cursor/hooks/after_sync.sh +5 -0
- package/toolkit/plugins/cursor/hooks/before_plugin_disable.sh +6 -0
- package/toolkit/plugins/gemini/hooks/after_sync.sh +5 -0
- package/toolkit/plugins/gemini/hooks/before_plugin_disable.sh +6 -0
- package/toolkit/plugins/karpathy-llm/README.md +3 -3
- package/toolkit/plugins/karpathy-llm/commands/ingest.sh +23 -290
- package/toolkit/plugins/karpathy-llm/commands/lint.sh +9 -30
- package/toolkit/plugins/karpathy-llm/hooks/_common.sh +330 -2
- package/toolkit/plugins/karpathy-llm/hooks/after_sync.sh +11 -245
- package/toolkit/plugins/karpathy-llm/hooks/before_plugin_disable.sh +5 -0
- package/toolkit/plugins/windsurf/hooks/after_sync.sh +5 -0
- package/toolkit/plugins/windsurf/hooks/before_plugin_disable.sh +6 -0
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ A **Virtual Meta-Repo** companion for multi-repo workspaces and shared AI contex
|
|
|
49
49
|
|
|
50
50
|
| What It Is | What It Isn't |
|
|
51
51
|
| :--- | :--- |
|
|
52
|
-
| **A lightweight companion utility** (
|
|
52
|
+
| **A lightweight companion utility** (modular pure POSIX `/bin/sh` CLI). | **NOT a replacement for your workspace or tools.** It doesn't replace VS Code, Cursor, JetBrains, or your terminal. |
|
|
53
53
|
| **A Virtual Meta-Repo orchestrator** managing member clones in `./repos/`. | **NOT a build tool or monorepo orchestrator.** It doesn't replace tools like Nx, Turborepo, Cargo, or Gradle. |
|
|
54
54
|
| **Zero Git friction.** Repositories remain normal, autonomous Git repos. | **NOT Git submodules or subtrees.** No `.gitmodules` files, no detached HEADs, no commit coordination lock-in. |
|
|
55
55
|
| **Strictly encapsulated.** Internal rnex assets live exclusively in `.rnex/`. | **NOT invasive.** Member repositories remain clean; workspace-related context is isolated to `repos/<name>/.rnex/`. |
|
|
@@ -134,10 +134,21 @@ All declared member repositories are cloned and wired up automatically!
|
|
|
134
134
|
|
|
135
135
|
## Project Structure
|
|
136
136
|
|
|
137
|
-
```
|
|
137
|
+
```text
|
|
138
138
|
repo-nexus/
|
|
139
139
|
├── package.json # npm package manifest (single version source of truth)
|
|
140
|
-
├── rnex # CLI
|
|
140
|
+
├── rnex # CLI entrypoint (pure POSIX /bin/sh)
|
|
141
|
+
├── lib/ # Modular POSIX shell libraries
|
|
142
|
+
│ ├── core.sh # Terminal colors, logging, path & version helpers
|
|
143
|
+
│ ├── yaml.sh # Two-level YAML parser & config mutators
|
|
144
|
+
│ ├── workspace.sh # Workspace root discovery & auto-sync engine
|
|
145
|
+
│ ├── repos.sh # Member repo path resolution & on-demand .rnex/
|
|
146
|
+
│ ├── plugins.sh # Plugin discovery, template & command engine
|
|
147
|
+
│ ├── sdk.sh # Plugin Hook SDK & User Skill generator
|
|
148
|
+
│ ├── ai.sh # AI routing instructions, .gitignore & .code-workspace
|
|
149
|
+
│ ├── hooks.sh # Declarative Git hooks & lifecycle event dispatcher
|
|
150
|
+
│ ├── commands.sh # CLI subcommand module loader
|
|
151
|
+
│ └── commands/ # Subcommand implementations (init, sync, repo, etc.)
|
|
141
152
|
├── AGENTS.md # Routing-only universal AI instructions
|
|
142
153
|
├── CHANGELOG.md # Release history and migration notes
|
|
143
154
|
├── CONTRIBUTION.md # POSIX standards, testing setup, and plugin contract
|
|
@@ -158,9 +169,9 @@ repo-nexus/
|
|
|
158
169
|
├── tests/
|
|
159
170
|
│ ├── test_cli.sh # Main test runner
|
|
160
171
|
│ ├── test_helper.sh # Shared test harness and utilities
|
|
161
|
-
│ └── suites/ # Modular test suites
|
|
172
|
+
│ └── suites/ # Modular test suites (01–07)
|
|
162
173
|
├── toolkit/ # Built-in plugins and core agentic skills
|
|
163
|
-
│ ├── plugins/ # Scoped workspace plugins
|
|
174
|
+
│ ├── plugins/ # Scoped workspace & AI provider plugins
|
|
164
175
|
│ └── skills/ # Core workspace agentic skills
|
|
165
176
|
└── README.md
|
|
166
177
|
```
|
package/lib/ai.sh
CHANGED
|
@@ -6,16 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
# ---- Gitignore & Search Tool Ignore Reconciliation -------------------------
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
# -
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if [ ! -f "$_gi" ]; then
|
|
18
|
-
cat > "$_gi" <<GI_EOF
|
|
9
|
+
GITIGNORE_MARKER_START="# REPO-NEXUS:START"
|
|
10
|
+
GITIGNORE_MARKER_END="# REPO-NEXUS:END"
|
|
11
|
+
|
|
12
|
+
# Emit the delimited Repo Nexus .gitignore section synced with git.ignore configuration.
|
|
13
|
+
_build_rnex_gitignore_section() {
|
|
14
|
+
cat <<GI_SECTION_EOF
|
|
15
|
+
$GITIGNORE_MARKER_START
|
|
16
|
+
# Managed by Repo Nexus (rnex) — synced with git.ignore in workspace configuration
|
|
19
17
|
# Member repos (managed by rnex, ignored at workspace level)
|
|
20
18
|
repos/*
|
|
21
19
|
!repos/.gitkeep
|
|
@@ -29,7 +27,52 @@ rnex*/
|
|
|
29
27
|
# Local machine-specific configuration
|
|
30
28
|
$RNEX_LOCAL_CONFIG_FILE
|
|
31
29
|
.rnex.local.yaml
|
|
32
|
-
|
|
30
|
+
GI_SECTION_EOF
|
|
31
|
+
|
|
32
|
+
_custom_ignores="$(yaml_get_git_ignore 2>/dev/null || true)"
|
|
33
|
+
if [ -n "$_custom_ignores" ]; then
|
|
34
|
+
_added_custom_header=0
|
|
35
|
+
_seen_custom=""
|
|
36
|
+
printf '%s\n' "$_custom_ignores" | while IFS= read -r _ci || [ -n "$_ci" ]; do
|
|
37
|
+
[ -n "$_ci" ] || continue
|
|
38
|
+
case "$_ci" in
|
|
39
|
+
"repos/"|"repos/*"|"!repos/.gitkeep"|"**/.rnex*/"|"**/rnex*/"|".rnex*/"|"rnex*/"|"$RNEX_LOCAL_CONFIG_FILE"|".rnex.local.yaml")
|
|
40
|
+
continue
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
case "
|
|
44
|
+
$_seen_custom
|
|
45
|
+
" in
|
|
46
|
+
*"
|
|
47
|
+
$_ci
|
|
48
|
+
"*) continue ;;
|
|
49
|
+
esac
|
|
50
|
+
_seen_custom="${_seen_custom:+$_seen_custom
|
|
51
|
+
}$_ci"
|
|
52
|
+
if [ "$_added_custom_header" -eq 0 ]; then
|
|
53
|
+
printf '\n# Custom entries from git.ignore in workspace configuration\n'
|
|
54
|
+
_added_custom_header=1
|
|
55
|
+
fi
|
|
56
|
+
printf '%s\n' "$_ci"
|
|
57
|
+
done
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
printf '%s\n' "$GITIGNORE_MARKER_END"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Reconcile workspace root .gitignore and .ignore:
|
|
64
|
+
# - Maintains a delimited (# REPO-NEXUS:START ... # REPO-NEXUS:END) section in .gitignore
|
|
65
|
+
# - Synchronizes the section with git.ignore from rnex.yaml and .local.rnex.yaml
|
|
66
|
+
# - Preserves user-authored .gitignore rules outside the managed section
|
|
67
|
+
# - Ensures .ignore un-ignores repos/* so ripgrep and AI tools can index member files
|
|
68
|
+
reconcile_gitignore() {
|
|
69
|
+
_dir="${1:-$NEXUS_DIR}"
|
|
70
|
+
_gi="$_dir/.gitignore"
|
|
71
|
+
_sec_tmp="$(mktemp)"
|
|
72
|
+
_build_rnex_gitignore_section > "$_sec_tmp"
|
|
73
|
+
|
|
74
|
+
if [ ! -f "$_gi" ]; then
|
|
75
|
+
mv "$_sec_tmp" "$_gi"
|
|
33
76
|
log_ok "Created .gitignore (ignoring repos/, recursive .rnex directories, and $RNEX_LOCAL_CONFIG_FILE)"
|
|
34
77
|
else
|
|
35
78
|
# 1. Strip any legacy lint_trigger_counter entries if present
|
|
@@ -39,72 +82,192 @@ GI_EOF
|
|
|
39
82
|
log_ok "Removed legacy lint trigger counter entries from .gitignore"
|
|
40
83
|
fi
|
|
41
84
|
|
|
42
|
-
# 2.
|
|
43
|
-
if grep -
|
|
85
|
+
# 2. If delimited section exists, replace it in-place with the newly synced section
|
|
86
|
+
if grep -qFx -- "$GITIGNORE_MARKER_START" "$_gi" 2>/dev/null; then
|
|
44
87
|
_tmp="$(mktemp)"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
88
|
+
awk -v s="$GITIGNORE_MARKER_START" -v e="$GITIGNORE_MARKER_END" -v src="$_sec_tmp" '
|
|
89
|
+
$0 == s {
|
|
90
|
+
in_b = 1
|
|
91
|
+
while ((getline line < src) > 0) print line
|
|
92
|
+
close(src)
|
|
93
|
+
next
|
|
94
|
+
}
|
|
95
|
+
$0 == e {
|
|
96
|
+
in_b = 0
|
|
97
|
+
next
|
|
98
|
+
}
|
|
99
|
+
!in_b { print }
|
|
100
|
+
' "$_gi" > "$_tmp" && mv "$_tmp" "$_gi"
|
|
101
|
+
rm -f "$_sec_tmp"
|
|
102
|
+
else
|
|
103
|
+
# 3. Upgrade legacy un-delimited .gitignore or append managed section to existing user .gitignore
|
|
104
|
+
_user_tmp="$(mktemp)"
|
|
105
|
+
awk -v local_cfg="$RNEX_LOCAL_CONFIG_FILE" '
|
|
106
|
+
/^[ ]*#[ ]*Member repos \(managed by rnex/ { next }
|
|
107
|
+
/^[ ]*#[ ]*Recursive Repo Nexus directories/ { next }
|
|
108
|
+
/^[ ]*#?[ ]*Repo Nexus internal state/ { next }
|
|
109
|
+
/^[ ]*#[ ]*Local machine-specific configuration/ { next }
|
|
110
|
+
/^[ ]*#[ ]*Custom entries from git\.ignore in workspace configuration/ { next }
|
|
111
|
+
$0 == "repos/*" || $0 == "!repos/.gitkeep" { next }
|
|
112
|
+
$0 == "**/.rnex*/" || $0 == "**/rnex*/" || $0 == ".rnex*/" || $0 == "rnex*/" { next }
|
|
113
|
+
$0 == ".rnex" || $0 == "rnex" { next }
|
|
114
|
+
$0 == local_cfg || $0 == ".rnex.local.yaml" { next }
|
|
115
|
+
{ lines[++n] = $0 }
|
|
116
|
+
END {
|
|
117
|
+
start = 1
|
|
118
|
+
while (start <= n && lines[start] ~ /^[[:space:]]*$/) start++
|
|
119
|
+
end = n
|
|
120
|
+
while (end >= start && lines[end] ~ /^[[:space:]]*$/) end--
|
|
121
|
+
for (i = start; i <= end; i++) print lines[i]
|
|
122
|
+
}
|
|
123
|
+
' "$_gi" > "$_user_tmp"
|
|
53
124
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if [ "$_added_rnex_rules" -eq 0 ]; then
|
|
59
|
-
printf '\n# Recursive Repo Nexus directories (trailing slashes prevent ignoring rnex binary or rnex.yaml)\n' >> "$_gi"
|
|
60
|
-
_added_rnex_rules=1
|
|
61
|
-
fi
|
|
62
|
-
printf '%s\n' "$_pat" >> "$_gi"
|
|
125
|
+
_tmp="$(mktemp)"
|
|
126
|
+
if [ -s "$_user_tmp" ]; then
|
|
127
|
+
cat "$_user_tmp" > "$_tmp"
|
|
128
|
+
printf '\n' >> "$_tmp"
|
|
63
129
|
fi
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if ! grep -qF "$RNEX_LOCAL_CONFIG_FILE" "$_gi" 2>/dev/null; then
|
|
68
|
-
printf '\n# Local machine-specific configuration\n%s\n' "$RNEX_LOCAL_CONFIG_FILE" >> "$_gi"
|
|
69
|
-
log_dim "Added $RNEX_LOCAL_CONFIG_FILE to .gitignore"
|
|
70
|
-
fi
|
|
71
|
-
if ! grep -qF ".rnex.local.yaml" "$_gi" 2>/dev/null; then
|
|
72
|
-
printf '.rnex.local.yaml\n' >> "$_gi"
|
|
130
|
+
cat "$_sec_tmp" >> "$_tmp"
|
|
131
|
+
mv "$_tmp" "$_gi"
|
|
132
|
+
rm -f "$_user_tmp" "$_sec_tmp"
|
|
73
133
|
fi
|
|
74
134
|
fi
|
|
75
135
|
|
|
76
|
-
# 6. Append any custom file and directory patterns from git.ignore
|
|
77
|
-
_custom_ignores="$(yaml_get_git_ignore 2>/dev/null || true)"
|
|
78
|
-
if [ -n "$_custom_ignores" ]; then
|
|
79
|
-
_added_custom_header=0
|
|
80
|
-
_old_ifs="$IFS"
|
|
81
|
-
IFS='
|
|
82
|
-
'
|
|
83
|
-
for _ci in $_custom_ignores; do
|
|
84
|
-
IFS="$_old_ifs"
|
|
85
|
-
[ -n "$_ci" ] || continue
|
|
86
|
-
if [ "$_ci" = "repos/" ] && grep -qFx -- "repos/*" "$_gi" 2>/dev/null; then
|
|
87
|
-
continue
|
|
88
|
-
fi
|
|
89
|
-
if ! grep -qFx -- "$_ci" "$_gi" 2>/dev/null; then
|
|
90
|
-
if [ "$_added_custom_header" -eq 0 ]; then
|
|
91
|
-
printf '\n# Custom entries from git.ignore in workspace configuration\n' >> "$_gi"
|
|
92
|
-
_added_custom_header=1
|
|
93
|
-
fi
|
|
94
|
-
printf '%s\n' "$_ci" >> "$_gi"
|
|
95
|
-
fi
|
|
96
|
-
done
|
|
97
|
-
IFS="$_old_ifs"
|
|
98
|
-
fi
|
|
99
|
-
|
|
100
136
|
# Universal search tool ignore file (ripgrep, Claude Code, VS Code, Zed)
|
|
101
137
|
ensure_provider_ignore "$_dir/.ignore" \
|
|
102
138
|
"Member repos (un-ignored for ripgrep, Claude Code, VS Code, and search tools)" \
|
|
103
139
|
"Created .ignore (un-ignoring member repos for search tools)"
|
|
104
140
|
}
|
|
105
141
|
|
|
142
|
+
# Clean managed Repo Nexus ignore block from .gitignore, removing file if empty
|
|
143
|
+
clean_rnex_gitignore() {
|
|
144
|
+
_dir="${1:-$NEXUS_DIR}"
|
|
145
|
+
_gi="$_dir/.gitignore"
|
|
146
|
+
[ -f "$_gi" ] || return 0
|
|
147
|
+
if grep -qFx -- "${GITIGNORE_MARKER_START:-# REPO-NEXUS:START}" "$_gi" 2>/dev/null; then
|
|
148
|
+
_user_content="$(awk -v s="${GITIGNORE_MARKER_START:-# REPO-NEXUS:START}" -v e="${GITIGNORE_MARKER_END:-# REPO-NEXUS:END}" '
|
|
149
|
+
$0 == s { in_b = 1; next }
|
|
150
|
+
$0 == e { in_b = 0; next }
|
|
151
|
+
!in_b { print }
|
|
152
|
+
' "$_gi" | sed -e '/^[[:space:]]*$/d' -e '/^[[:space:]]*#/d')"
|
|
153
|
+
if [ -z "$_user_content" ]; then
|
|
154
|
+
rm -f "$_gi"
|
|
155
|
+
[ "${RNEX_QUIET:-0}" -eq 0 ] && [ "${_sync_quiet:-0}" -eq 0 ] && log_ok "Removed .gitignore"
|
|
156
|
+
else
|
|
157
|
+
_tmp="$(mktemp)"
|
|
158
|
+
awk -v s="${GITIGNORE_MARKER_START:-# REPO-NEXUS:START}" -v e="${GITIGNORE_MARKER_END:-# REPO-NEXUS:END}" '
|
|
159
|
+
$0 == s { in_b = 1; next }
|
|
160
|
+
$0 == e { in_b = 0; next }
|
|
161
|
+
!in_b { print }
|
|
162
|
+
' "$_gi" > "$_tmp" && mv "$_tmp" "$_gi"
|
|
163
|
+
[ "${RNEX_QUIET:-0}" -eq 0 ] && [ "${_sync_quiet:-0}" -eq 0 ] && log_ok "Removed Repo Nexus rules from .gitignore"
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
}
|
|
167
|
+
|
|
106
168
|
# ---- AI Instructions Compilation (.rnex/ai-instructions.md) ----------------
|
|
107
169
|
|
|
170
|
+
# Resolve the primary instruction file for an AI provider plugin
|
|
171
|
+
plugin_get_instruction_file() {
|
|
172
|
+
_pif_name="$1"
|
|
173
|
+
_pif_pdir="${2:-}"
|
|
174
|
+
[ -n "$_pif_pdir" ] || _pif_pdir="$(find_plugin_dir "$_pif_name" 2>/dev/null || true)"
|
|
175
|
+
_pif_file=""
|
|
176
|
+
if [ -n "$_pif_pdir" ]; then
|
|
177
|
+
_pif_file="$(plugin_get_field "$_pif_pdir" "instruction_file" 2>/dev/null || true)"
|
|
178
|
+
fi
|
|
179
|
+
if [ -z "$_pif_file" ]; then
|
|
180
|
+
case "$_pif_name" in
|
|
181
|
+
claude) _pif_file="CLAUDE.md" ;;
|
|
182
|
+
gemini) _pif_file="GEMINI.md" ;;
|
|
183
|
+
cursor) _pif_file=".cursorrules" ;;
|
|
184
|
+
windsurf) _pif_file=".windsurfrules" ;;
|
|
185
|
+
codex) _pif_file="CODEX.md" ;;
|
|
186
|
+
copilot) _pif_file=".github/copilot-instructions.md" ;;
|
|
187
|
+
agents|agent) _pif_file="AGENTS.md" ;;
|
|
188
|
+
esac
|
|
189
|
+
fi
|
|
190
|
+
printf '%s' "$_pif_file"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Enumerate primary instruction files for currently enabled AI provider plugins
|
|
194
|
+
get_active_provider_instruction_files() {
|
|
195
|
+
_gap_dir="${1:-$NEXUS_DIR}"
|
|
196
|
+
_gap_files=""
|
|
197
|
+
for _p in $(yaml_list_plugins 2>/dev/null || true); do
|
|
198
|
+
[ -n "$_p" ] || continue
|
|
199
|
+
yaml_plugin_enabled "$_p" 2>/dev/null || continue
|
|
200
|
+
_ifile="$(plugin_get_instruction_file "$_p" 2>/dev/null || true)"
|
|
201
|
+
if [ -n "$_ifile" ]; then
|
|
202
|
+
case " $_gap_files " in
|
|
203
|
+
*" $_ifile "*) ;;
|
|
204
|
+
*) _gap_files="${_gap_files:+$_gap_files }$_ifile" ;;
|
|
205
|
+
esac
|
|
206
|
+
fi
|
|
207
|
+
done
|
|
208
|
+
printf '%s' "$_gap_files"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# Check if an instruction file contains custom user instructions outside the rnex block
|
|
212
|
+
file_has_user_content() {
|
|
213
|
+
_fhuc_file="$1"
|
|
214
|
+
[ -f "$_fhuc_file" ] || return 1
|
|
215
|
+
_content=""
|
|
216
|
+
if grep -qF "${AI_MARKER_START:-<!-- REPO-NEXUS:START -->}" "$_fhuc_file" 2>/dev/null; then
|
|
217
|
+
_content="$(awk -v s="${AI_MARKER_START:-<!-- REPO-NEXUS:START -->}" -v e="${AI_MARKER_END:-<!-- REPO-NEXUS:END -->}" '
|
|
218
|
+
$0 == s { in_b = 1; next }
|
|
219
|
+
$0 == e { in_b = 0; next }
|
|
220
|
+
!in_b { print }
|
|
221
|
+
' "$_fhuc_file" | sed -e '/^[[:space:]]*$/d')"
|
|
222
|
+
else
|
|
223
|
+
_content="$(grep -v "CRITICAL: You MUST read and strictly follow all instructions in \`.rnex/ai-instructions.md\`" "$_fhuc_file" 2>/dev/null | grep -v "Multi-Repo AI Workspace Context" | sed -e '/^[[:space:]]*$/d')"
|
|
224
|
+
fi
|
|
225
|
+
[ -n "$_content" ]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Reconcile AI provider instruction files:
|
|
229
|
+
# - When provider plugins are enabled (e.g. Gemini, Claude), creates/updates their specific instruction files (GEMINI.md, CLAUDE.md)
|
|
230
|
+
# - Only creates AGENTS.md if it is the main instructions file for an enabled AI provider or no provider plugins are enabled
|
|
231
|
+
# - Cleans up instruction files that have no user content when their provider is disabled or inactive
|
|
232
|
+
reconcile_ai_instructions_files() {
|
|
233
|
+
_raif_dir="${1:-$NEXUS_DIR}"
|
|
234
|
+
_active_files="$(get_active_provider_instruction_files "$_raif_dir" 2>/dev/null || true)"
|
|
235
|
+
|
|
236
|
+
if [ -n "$_active_files" ]; then
|
|
237
|
+
# Active AI providers are configured with their own instruction files
|
|
238
|
+
for _af in $_active_files; do
|
|
239
|
+
merge_rnex_instructions "$_raif_dir/$_af"
|
|
240
|
+
done
|
|
241
|
+
|
|
242
|
+
# If AGENTS.md is not an active provider instruction file, clean rnex instructions from it
|
|
243
|
+
# (deleting it if solely rnex-managed, or stripping the rnex block if user content exists)
|
|
244
|
+
case " $_active_files " in
|
|
245
|
+
*" AGENTS.md "*) ;;
|
|
246
|
+
*)
|
|
247
|
+
if [ -f "$_raif_dir/AGENTS.md" ]; then
|
|
248
|
+
clean_rnex_instructions "$_raif_dir/AGENTS.md"
|
|
249
|
+
fi
|
|
250
|
+
;;
|
|
251
|
+
esac
|
|
252
|
+
else
|
|
253
|
+
# No provider plugins enabled: AGENTS.md serves as the default main instructions file
|
|
254
|
+
merge_rnex_instructions "$_raif_dir/AGENTS.md"
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
# For any existing instruction files on disk that are NOT currently active:
|
|
258
|
+
# Clean rnex instructions (removing file if empty outside block, or stripping rnex block)
|
|
259
|
+
for _if in CLAUDE.md GEMINI.md CODEX.md .cursorrules .windsurfrules .github/copilot-instructions.md; do
|
|
260
|
+
case " $_active_files " in
|
|
261
|
+
*" $_if "*) ;;
|
|
262
|
+
*)
|
|
263
|
+
if [ -f "$_raif_dir/$_if" ]; then
|
|
264
|
+
clean_rnex_instructions "$_raif_dir/$_if"
|
|
265
|
+
fi
|
|
266
|
+
;;
|
|
267
|
+
esac
|
|
268
|
+
done
|
|
269
|
+
}
|
|
270
|
+
|
|
108
271
|
# Compile the unified .rnex/ai-instructions.md file consumed by AI coding agents.
|
|
109
272
|
# Aggregates sample template instructions and active plugin instruction sections.
|
|
110
273
|
build_ai_instructions() {
|
|
@@ -299,21 +462,7 @@ get_active_provider_skill_dirs() {
|
|
|
299
462
|
[ -n "$_p" ] || continue
|
|
300
463
|
yaml_plugin_enabled "$_p" 2>/dev/null || continue
|
|
301
464
|
if provider_option_enabled "$_p" "skills" 2>/dev/null; then
|
|
302
|
-
|
|
303
|
-
_sdir=""
|
|
304
|
-
if [ -n "$_pdir" ]; then
|
|
305
|
-
_sdir="$(plugin_get_field "$_pdir" "skill_dir" 2>/dev/null || true)"
|
|
306
|
-
fi
|
|
307
|
-
if [ -z "$_sdir" ]; then
|
|
308
|
-
case "$_p" in
|
|
309
|
-
copilot) _sdir=".github/skills" ;;
|
|
310
|
-
cursor) _sdir=".cursor/rules" ;;
|
|
311
|
-
claude) _sdir=".claude/skills" ;;
|
|
312
|
-
windsurf) _sdir=".windsurf/rules" ;;
|
|
313
|
-
gemini) _sdir=".agents/skills" ;;
|
|
314
|
-
codex) _sdir=".codex/skills" ;;
|
|
315
|
-
esac
|
|
316
|
-
fi
|
|
465
|
+
_sdir="$(plugin_get_skill_dir "$_p")"
|
|
317
466
|
if [ -n "$_sdir" ]; then
|
|
318
467
|
case " $_dirs " in
|
|
319
468
|
*" $_sdir "*) ;;
|
package/lib/commands/init.sh
CHANGED
|
@@ -194,10 +194,7 @@ YAML_EOF
|
|
|
194
194
|
WORKSPACE_LOCAL_YAML="$_init_dir/$RNEX_LOCAL_CONFIG_FILE"
|
|
195
195
|
|
|
196
196
|
# ---- 2. Merge routing instructions into AI instructions files ------------
|
|
197
|
-
|
|
198
|
-
for _if in CLAUDE.md GEMINI.md CODEX.md .cursorrules .windsurfrules .github/copilot-instructions.md; do
|
|
199
|
-
[ -f "$_init_dir/$_if" ] && merge_rnex_instructions "$_init_dir/$_if"
|
|
200
|
-
done
|
|
197
|
+
reconcile_ai_instructions_files "$_init_dir"
|
|
201
198
|
|
|
202
199
|
# ---- 3. Create repos directory with .gitkeep -----------------------------
|
|
203
200
|
_repos_dir="$_init_dir/repos"
|
package/lib/commands/plugin.sh
CHANGED
|
@@ -82,15 +82,7 @@ cmd_plugin_info() {
|
|
|
82
82
|
[ -n "$_home" ] && printf '%bHomepage:%b %s\n' "$_BOLD" "$_NC" "$_home"
|
|
83
83
|
printf '%bLocation:%b %s\n' "$_BOLD" "$_NC" "$_pdir"
|
|
84
84
|
|
|
85
|
-
_skill_target="$(
|
|
86
|
-
[ -z "$_skill_target" ] && case "$_name" in
|
|
87
|
-
copilot) _skill_target=".github/skills/" ;;
|
|
88
|
-
claude) _skill_target=".claude/skills/" ;;
|
|
89
|
-
gemini) _skill_target=".agents/skills/" ;;
|
|
90
|
-
cursor) _skill_target=".cursor/rules/" ;;
|
|
91
|
-
windsurf) _skill_target=".windsurf/rules/" ;;
|
|
92
|
-
codex) _skill_target=".codex/skills/" ;;
|
|
93
|
-
esac
|
|
85
|
+
_skill_target="$(plugin_get_skill_dir "$_name" "$_pdir")"
|
|
94
86
|
case "$_skill_target" in
|
|
95
87
|
*/|"") ;;
|
|
96
88
|
*) _skill_target="${_skill_target}/" ;;
|
package/lib/commands/status.sh
CHANGED
|
@@ -4,21 +4,7 @@
|
|
|
4
4
|
# lib/commands/status.sh — Workspace health and status diagnostics
|
|
5
5
|
# ============================================================================
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
_status_active_plugin_trigger() {
|
|
9
|
-
for _p in $(yaml_list_plugins 2>/dev/null || true); do
|
|
10
|
-
yaml_plugin_enabled "$_p" 2>/dev/null || continue
|
|
11
|
-
_trig="$(yaml_plugin_get "$_p" "lint_trigger" 2>/dev/null || true)"
|
|
12
|
-
[ -z "$_trig" ] && _trig="$(yaml_plugin_get "$_p" "trigger" 2>/dev/null || true)"
|
|
13
|
-
if [ -n "$_trig" ] && [ "$_trig" != "manual" ]; then
|
|
14
|
-
printf '%s' "$_trig"
|
|
15
|
-
return 0
|
|
16
|
-
fi
|
|
17
|
-
done
|
|
18
|
-
return 0
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
# Check if a directory contains any rnex-* user skills or skill symlinks.
|
|
7
|
+
# Check if a directory contains any rnex-* user skill pointers or skill bundles.
|
|
22
8
|
_status_has_rnex_skills() {
|
|
23
9
|
[ -d "$1" ] && { ls "$1"/rnex-*.md >/dev/null 2>&1 || ls -d "$1"/rnex-* >/dev/null 2>&1; }
|
|
24
10
|
}
|
|
@@ -80,15 +66,31 @@ cmd_status() {
|
|
|
80
66
|
fi
|
|
81
67
|
|
|
82
68
|
_gi="$NEXUS_DIR/.gitignore"
|
|
83
|
-
_gi_repos=0; _gi_local=0; _gi_has_counter=0; _gi_has_rnex=0
|
|
69
|
+
_gi_repos=0; _gi_local=0; _gi_has_counter=0; _gi_has_rnex=0; _gi_has_markers=0; _gi_custom_ok=1
|
|
84
70
|
if [ -f "$_gi" ]; then
|
|
85
71
|
grep -q '^repos' "$_gi" 2>/dev/null && _gi_repos=1
|
|
86
72
|
grep -qF "$RNEX_LOCAL_CONFIG_FILE" "$_gi" 2>/dev/null && _gi_local=1
|
|
87
73
|
grep -q "lint_trigger_counter" "$_gi" 2>/dev/null && _gi_has_counter=1
|
|
88
74
|
grep -q '\*\*/\.rnex\*/' "$_gi" 2>/dev/null && _gi_has_rnex=1
|
|
75
|
+
if grep -qFx -- "${GITIGNORE_MARKER_START:-# REPO-NEXUS:START}" "$_gi" 2>/dev/null && \
|
|
76
|
+
grep -qFx -- "${GITIGNORE_MARKER_END:-# REPO-NEXUS:END}" "$_gi" 2>/dev/null; then
|
|
77
|
+
_gi_has_markers=1
|
|
78
|
+
_gi_exp_tmp="$(mktemp)"
|
|
79
|
+
_gi_act_tmp="$(mktemp)"
|
|
80
|
+
_build_rnex_gitignore_section > "$_gi_exp_tmp"
|
|
81
|
+
awk -v s="${GITIGNORE_MARKER_START:-# REPO-NEXUS:START}" -v e="${GITIGNORE_MARKER_END:-# REPO-NEXUS:END}" '
|
|
82
|
+
$0 == s { in_b = 1 }
|
|
83
|
+
in_b { print }
|
|
84
|
+
$0 == e { in_b = 0; exit }
|
|
85
|
+
' "$_gi" > "$_gi_act_tmp"
|
|
86
|
+
if ! cmp -s "$_gi_exp_tmp" "$_gi_act_tmp"; then
|
|
87
|
+
_gi_custom_ok=0
|
|
88
|
+
fi
|
|
89
|
+
rm -f "$_gi_exp_tmp" "$_gi_act_tmp"
|
|
90
|
+
fi
|
|
89
91
|
fi
|
|
90
92
|
|
|
91
|
-
if [ "$_gi_repos" -eq 1 ] && [ "$_gi_local" -eq 1 ] && [ "$_gi_has_counter" -eq 0 ] && [ "$_gi_has_rnex" -eq 1 ]; then
|
|
93
|
+
if [ "$_gi_repos" -eq 1 ] && [ "$_gi_local" -eq 1 ] && [ "$_gi_has_counter" -eq 0 ] && [ "$_gi_has_rnex" -eq 1 ] && [ "$_gi_has_markers" -eq 1 ] && [ "$_gi_custom_ok" -eq 1 ]; then
|
|
92
94
|
printf ' %b✓%b Git Ignore: repos/, %s, and **/.rnex*/ ignored in .gitignore\n' "$_G" "$_NC" "$RNEX_LOCAL_CONFIG_FILE"
|
|
93
95
|
else
|
|
94
96
|
printf ' %b!%b Git Ignore: recommend running "rnex sync" to update .gitignore (repos/, local configs)\n' "$_Y" "$_NC"
|
|
@@ -126,10 +128,11 @@ cmd_status() {
|
|
|
126
128
|
else
|
|
127
129
|
printf ' %b✗%b %s %b(compiled AI instructions — MISSING; run "rnex sync" to create)%b\n' "$_R" "$_NC" ".rnex/ai-instructions.md" "$_R" "$_NC"
|
|
128
130
|
fi
|
|
131
|
+
|
|
132
|
+
_has_any_instructions=0
|
|
129
133
|
if [ -f "$NEXUS_DIR/AGENTS.md" ]; then
|
|
130
134
|
printf ' %b✓%b %s %b(standard instructions directive)%b\n' "$_G" "$_NC" "AGENTS.md" "$_DIM" "$_NC"
|
|
131
|
-
|
|
132
|
-
printf ' %b✗%b %s %b(standard instructions directive — MISSING; run "rnex sync" to create)%b\n' "$_R" "$_NC" "AGENTS.md" "$_R" "$_NC"
|
|
135
|
+
_has_any_instructions=1
|
|
133
136
|
fi
|
|
134
137
|
for _pair in \
|
|
135
138
|
"CLAUDE.md:Claude" \
|
|
@@ -142,8 +145,15 @@ cmd_status() {
|
|
|
142
145
|
_ilbl="${_pair#*:}"
|
|
143
146
|
if [ -f "$NEXUS_DIR/$_ifile" ]; then
|
|
144
147
|
printf ' %b✓%b %s %b(%s instructions)%b\n' "$_G" "$_NC" "$_ifile" "$_DIM" "$_ilbl" "$_NC"
|
|
148
|
+
_has_any_instructions=1
|
|
145
149
|
fi
|
|
146
150
|
done
|
|
151
|
+
if [ "$_has_any_instructions" -eq 0 ]; then
|
|
152
|
+
_active_ifiles=""
|
|
153
|
+
command -v get_active_provider_instruction_files >/dev/null 2>&1 && _active_ifiles="$(get_active_provider_instruction_files "$NEXUS_DIR" 2>/dev/null || true)"
|
|
154
|
+
_target_ifile="${_active_ifiles:-AGENTS.md}"
|
|
155
|
+
printf ' %b✗%b %s %b(instructions directive — MISSING; run "rnex sync" to create)%b\n' "$_R" "$_NC" "$_target_ifile" "$_R" "$_NC"
|
|
156
|
+
fi
|
|
147
157
|
if [ -n "$CODE_WORKSPACE_FILE" ]; then
|
|
148
158
|
if [ -f "$NEXUS_DIR/$CODE_WORKSPACE_FILE" ]; then
|
|
149
159
|
printf ' %b✓%b %s %b(VS Code / Cursor workspace)%b\n' "$_G" "$_NC" "$CODE_WORKSPACE_FILE" "$_DIM" "$_NC"
|
package/lib/commands/sync.sh
CHANGED
|
@@ -84,10 +84,7 @@ cmd_sync() {
|
|
|
84
84
|
|
|
85
85
|
# 1.2. Reconcile universal workspace instructions in AGENTS.md (permanent main context file)
|
|
86
86
|
# and all existing permanent provider instructions files
|
|
87
|
-
|
|
88
|
-
for _if in CLAUDE.md GEMINI.md CODEX.md .cursorrules .windsurfrules .github/copilot-instructions.md; do
|
|
89
|
-
[ -f "$NEXUS_DIR/$_if" ] && merge_rnex_instructions "$NEXUS_DIR/$_if"
|
|
90
|
-
done
|
|
87
|
+
reconcile_ai_instructions_files "$NEXUS_DIR"
|
|
91
88
|
|
|
92
89
|
# 1.3. Generate auto-sync script (hardcoded harness)
|
|
93
90
|
_write_auto_sync_script "$NEXUS_DIR"
|
package/lib/core.sh
CHANGED
|
@@ -99,7 +99,7 @@ normalize_rel_path() {
|
|
|
99
99
|
# ---- Version Resolution (package.json is single source of truth) ----------
|
|
100
100
|
|
|
101
101
|
# Dynamically locate the nearest package.json and extract its "version" field.
|
|
102
|
-
# Falls back to 0.
|
|
102
|
+
# Falls back to 0.8.7 if package.json cannot be read.
|
|
103
103
|
resolve_version() {
|
|
104
104
|
_sdir="$(dirname "$SCRIPT_LOCATION")"
|
|
105
105
|
for _cand in "$SCRIPT_LOCATION/package.json" "$_sdir/package.json" "$_sdir/../package.json"; do
|
|
@@ -111,7 +111,7 @@ resolve_version() {
|
|
|
111
111
|
fi
|
|
112
112
|
fi
|
|
113
113
|
done
|
|
114
|
-
printf '0.
|
|
114
|
+
printf '0.8.7'
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
RNEX_VERSION="$(resolve_version)"
|
package/lib/hooks.sh
CHANGED
|
@@ -306,10 +306,7 @@ install_git_hooks() {
|
|
|
306
306
|
esac
|
|
307
307
|
[ -d "$_git_dir" ] || return 0
|
|
308
308
|
_hooks_dir="$_git_dir/hooks"
|
|
309
|
-
|
|
310
|
-
for _h in $RNEX_MANAGED_GIT_HOOKS; do
|
|
311
|
-
_create_hook_script "$_hooks_dir/$_h" "$_h"
|
|
312
|
-
done
|
|
309
|
+
_install_managed_hook_scripts "$_hooks_dir"
|
|
313
310
|
# Clean up legacy .rnex/hooks if core.hooksPath was previously set to it
|
|
314
311
|
_hp="$(git -C "$_ws_dir" config --get core.hooksPath 2>/dev/null || true)"
|
|
315
312
|
if [ "$_hp" = ".rnex/hooks" ] || [ "$_hp" = "$_ws_dir/.rnex/hooks" ]; then
|
package/lib/plugins.sh
CHANGED
|
@@ -72,6 +72,28 @@ plugin_get_field() {
|
|
|
72
72
|
' "$_pdir/plugin.yaml"
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
# Resolve target user skill directory for a provider plugin from plugin.yaml (with fallback).
|
|
76
|
+
plugin_get_skill_dir() {
|
|
77
|
+
_pgs_name="$1"
|
|
78
|
+
_pgs_pdir="${2:-}"
|
|
79
|
+
[ -n "$_pgs_pdir" ] || _pgs_pdir="$(find_plugin_dir "$_pgs_name" 2>/dev/null || true)"
|
|
80
|
+
_pgs_dir=""
|
|
81
|
+
if [ -n "$_pgs_pdir" ]; then
|
|
82
|
+
_pgs_dir="$(plugin_get_field "$_pgs_pdir" "skill_dir" 2>/dev/null || true)"
|
|
83
|
+
fi
|
|
84
|
+
if [ -z "$_pgs_dir" ]; then
|
|
85
|
+
case "$_pgs_name" in
|
|
86
|
+
copilot) _pgs_dir=".github/skills" ;;
|
|
87
|
+
cursor) _pgs_dir=".cursor/rules" ;;
|
|
88
|
+
claude) _pgs_dir=".claude/skills" ;;
|
|
89
|
+
windsurf) _pgs_dir=".windsurf/rules" ;;
|
|
90
|
+
gemini) _pgs_dir=".agents/skills" ;;
|
|
91
|
+
codex) _pgs_dir=".codex/skills" ;;
|
|
92
|
+
esac
|
|
93
|
+
fi
|
|
94
|
+
printf '%s' "$_pgs_dir"
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
# Parse declared template source-to-destination mappings from plugin.yaml.
|
|
76
98
|
plugin_list_templates() {
|
|
77
99
|
_pdir="$1"
|
|
@@ -108,6 +130,9 @@ plugin_resolve_placeholders() {
|
|
|
108
130
|
_p="$1"; _pd="$2"; _str="$3"
|
|
109
131
|
_res="$(printf '%s' "$_str" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"")"
|
|
110
132
|
|
|
133
|
+
# Normalize legacy static prefixes into placeholders prior to expansion
|
|
134
|
+
_res="$(printf '%s' "$_res" | sed -e 's|^\.\{0,1\}\/\{0,1\}wiki/|{wiki_dir}/|' -e 's|^\.\{0,1\}\/\{0,1\}raw/|{raw_dir}/|')"
|
|
135
|
+
|
|
111
136
|
if [ -f "$_pd/plugin.yaml" ]; then
|
|
112
137
|
_keys="$(awk '
|
|
113
138
|
/^config_defaults:[ ]*$/ || /^config_defaults:[ ]*#/ { in_c=1; next }
|
|
@@ -140,22 +165,23 @@ plugin_resolve_placeholders() {
|
|
|
140
165
|
_v="$(normalize_rel_path "$_v")"
|
|
141
166
|
;;
|
|
142
167
|
esac
|
|
143
|
-
_res="$(printf '%s' "$_res" | sed -e "s|{$_k}|$_v|g")"
|
|
168
|
+
[ -n "$_v" ] && _res="$(printf '%s' "$_res" | sed -e "s|{$_k}|$_v|g")"
|
|
144
169
|
done
|
|
145
170
|
fi
|
|
146
171
|
|
|
147
|
-
# Fallback resolution for core wiki/raw placeholders
|
|
172
|
+
# Fallback resolution for core wiki/raw placeholders
|
|
148
173
|
_w_val="$(yaml_plugin_get "$_p" "wiki_dir" 2>/dev/null || true)"
|
|
149
174
|
[ -n "$_w_val" ] || _w_val="wiki"
|
|
150
175
|
_w_val="$(normalize_rel_path "$_w_val")"
|
|
176
|
+
[ -n "$_w_val" ] || _w_val="wiki"
|
|
151
177
|
|
|
152
178
|
# {raw_dir} (legacy alias) resolves to the first explicit directory source; when no
|
|
153
179
|
# directory source exists it stays unresolved so callers can skip the destination.
|
|
154
180
|
_r_val="$(plugin_primary_source_dir "$_p" 2>/dev/null || true)"
|
|
155
181
|
|
|
156
|
-
_res="$(printf '%s' "$_res" | sed -e "s|{wiki_dir}|${_w_val}|g"
|
|
182
|
+
_res="$(printf '%s' "$_res" | sed -e "s|{wiki_dir}|${_w_val}|g")"
|
|
157
183
|
if [ -n "$_r_val" ]; then
|
|
158
|
-
_res="$(printf '%s' "$_res" | sed -e "s|{raw_dir}|${_r_val}|g"
|
|
184
|
+
_res="$(printf '%s' "$_res" | sed -e "s|{raw_dir}|${_r_val}|g")"
|
|
159
185
|
fi
|
|
160
186
|
|
|
161
187
|
_res="$(printf '%s' "$_res" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d "'\"" | sed -e 's#^\./##')"
|
|
@@ -168,7 +194,8 @@ plugin_instantiate_template() {
|
|
|
168
194
|
|
|
169
195
|
_w_val="$(yaml_plugin_get "$_p" "wiki_dir" 2>/dev/null || true)"
|
|
170
196
|
[ -z "$_w_val" ] && _w_val="wiki"
|
|
171
|
-
_w_val="$(
|
|
197
|
+
_w_val="$(normalize_rel_path "$_w_val")"
|
|
198
|
+
[ -z "$_w_val" ] && _w_val="wiki"
|
|
172
199
|
|
|
173
200
|
_r_val="$(plugin_primary_source_dir "$_p" 2>/dev/null || true)"
|
|
174
201
|
[ -z "$_r_val" ] && _r_val="docs"
|
|
@@ -214,8 +241,10 @@ sync_plugin_bundle() {
|
|
|
214
241
|
# Clean up obsolete leaked root ./raw and ./wiki directories if non-default paths are configured
|
|
215
242
|
_clean_leaked_plugin_dirs() {
|
|
216
243
|
_cld_root="${1:-$NEXUS_DIR}"
|
|
217
|
-
_cld_raw="$2"
|
|
218
|
-
_cld_wiki="$3"
|
|
244
|
+
_cld_raw="$(normalize_rel_path "$2")"
|
|
245
|
+
_cld_wiki="$(normalize_rel_path "$3")"
|
|
246
|
+
[ -n "$_cld_raw" ] || _cld_raw="docs"
|
|
247
|
+
[ -n "$_cld_wiki" ] || _cld_wiki="wiki"
|
|
219
248
|
|
|
220
249
|
if [ "$_cld_raw" != "raw" ] && [ -d "$_cld_root/raw" ] && \
|
|
221
250
|
! plugin_source_dirs "karpathy-llm" 2>/dev/null | grep -qx 'raw'; then
|
package/lib/repos.sh
CHANGED
|
@@ -63,11 +63,6 @@ repo_readonly() {
|
|
|
63
63
|
yaml_repo_readonly "$1"
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
# Backwards-compatibility alias for checking if member .rnex exists
|
|
67
|
-
repo_rnex_dir_enabled() {
|
|
68
|
-
repo_has_rnex_dir "$1"
|
|
69
|
-
}
|
|
70
|
-
|
|
71
66
|
# Cleanly tear down .rnex in a member repository.
|
|
72
67
|
# Removes auto-generated plugins and boilerplate README, pruning empty directories
|
|
73
68
|
# while preserving any user-customized files.
|