reins-method 0.1.0

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/bin/reins ADDED
@@ -0,0 +1,750 @@
1
+ #!/usr/bin/env bash
2
+ # REINS Method CLI — install, update, and manage the global REINS workflow.
3
+ # POSIX-ish bash, no Node/Python runtime required.
4
+
5
+ set -euo pipefail
6
+
7
+ REINS_HOME="${REINS_HOME:-$HOME/.reins}"
8
+ CORE_DIR="$REINS_HOME/core"
9
+ USER_DIR="$REINS_HOME/user"
10
+ AGENTS_DIR="$REINS_HOME/agents"
11
+ CONFIG="$USER_DIR/config.yaml"
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # helpers
15
+ # ---------------------------------------------------------------------------
16
+
17
+ log() { printf '%s\n' "$*"; }
18
+ err() { printf 'Error: %s\n' "$*" >&2; }
19
+ die() { err "$*"; exit 1; }
20
+
21
+ config_get() {
22
+ # config_get <key> -> prints scalar value for "key: value" line, empty if absent
23
+ [ -f "$CONFIG" ] || return 0
24
+ grep -E "^${1}:" "$CONFIG" 2>/dev/null | head -n1 | sed -E "s/^${1}:[[:space:]]*//"
25
+ }
26
+
27
+ config_set() {
28
+ # config_set <key> <value> -> upsert "key: value" line
29
+ local key="$1" value="$2"
30
+ mkdir -p "$USER_DIR"
31
+ touch "$CONFIG"
32
+ if grep -qE "^${key}:" "$CONFIG"; then
33
+ sed -i.bak -E "s|^${key}:.*|${key}: ${value}|" "$CONFIG" && rm -f "${CONFIG}.bak"
34
+ else
35
+ printf '%s: %s\n' "$key" "$value" >> "$CONFIG"
36
+ fi
37
+ }
38
+
39
+ config_adapters() {
40
+ # prints one adapter name per line, from the "adapters:" YAML list
41
+ [ -f "$CONFIG" ] || return 0
42
+ awk '/^adapters:/{found=1; next} /^[a-zA-Z]/{found=0} found && /^[[:space:]]*-/{gsub(/^[[:space:]]*-[[:space:]]*/,""); print}' "$CONFIG"
43
+ }
44
+
45
+ ensure_managed_block() {
46
+ # ensure_managed_block <target-file> <content-line>
47
+ # Inserts/replaces a REINS-managed block in <target-file> without touching
48
+ # the rest of the file's content.
49
+ local target="$1" content="$2"
50
+ local begin="<!-- REINS:BEGIN -->" end="<!-- REINS:END -->"
51
+ mkdir -p "$(dirname "$target")"
52
+ touch "$target"
53
+ if grep -qF "$begin" "$target"; then
54
+ awk -v b="$begin" -v e="$end" -v c="$content" '
55
+ $0 == b {print; print c; skip=1; next}
56
+ $0 == e {print; skip=0; next}
57
+ skip {next}
58
+ {print}
59
+ ' "$target" > "$target.tmp" && mv "$target.tmp" "$target"
60
+ else
61
+ {
62
+ printf '\n%s\n' "$begin"
63
+ printf '%s\n' "$content"
64
+ printf '%s\n' "$end"
65
+ } >> "$target"
66
+ fi
67
+ }
68
+
69
+ remove_managed_block() {
70
+ # remove_managed_block <target-file>
71
+ # Strips the REINS-managed block (and the blank line preceding it) from
72
+ # <target-file>, if present. No-op if the file or block doesn't exist.
73
+ local target="$1"
74
+ local begin="<!-- REINS:BEGIN -->" end="<!-- REINS:END -->"
75
+ [ -f "$target" ] || return 0
76
+ grep -qF "$begin" "$target" || return 0
77
+ awk -v b="$begin" -v e="$end" '
78
+ $0 == b {skip=1; if (out ~ /\n\n$/) { sub(/\n$/, "", out) }; next}
79
+ $0 == e {skip=0; next}
80
+ skip {next}
81
+ {out = out $0 "\n"}
82
+ END {printf "%s", out}
83
+ ' "$target" > "$target.tmp" && mv "$target.tmp" "$target"
84
+ }
85
+
86
+ agent_bridge_target() {
87
+ # agent_bridge_target <agent> -> prints "<bridge-file>:<native-target>:<import-syntax>"
88
+ case "$1" in
89
+ claude-code) echo "$AGENTS_DIR/CLAUDE.md:$HOME/.claude/CLAUDE.md:import" ;;
90
+ gemini) echo "$AGENTS_DIR/GEMINI.md:$HOME/.gemini/GEMINI.md:import" ;;
91
+ copilot) echo "$AGENTS_DIR/copilot-instructions.md:$HOME/.copilot/instructions.md:inline" ;;
92
+ codex) echo "$AGENTS_DIR/AGENTS.md:$HOME/.codex/AGENTS.md:inline" ;;
93
+ aider|opencode|other)
94
+ echo "$AGENTS_DIR/AGENTS.md:$HOME/AGENTS.md:inline" ;;
95
+ *) return 1 ;;
96
+ esac
97
+ }
98
+
99
+ wire_block() {
100
+ # wire_block <bridge-file> <native-target> <import|inline> -> ensure_managed_block with the right content
101
+ local bridge="$1" native="$2" syntax="$3"
102
+ case "$syntax" in
103
+ import) ensure_managed_block "$native" "@${bridge}" ;;
104
+ inline) ensure_managed_block "$native" "$(printf 'See %s for the full REINS Method instructions (generated by `reins update`).' "$bridge")" ;;
105
+ esac
106
+ }
107
+
108
+ wire_all_agents() {
109
+ # Wires the REINS managed block into every AI agent detected on this machine
110
+ # (its config directory already exists), plus the generic ~/AGENTS.md
111
+ # fallback for Aider/OpenCode/Cursor/other — so skills and instructions are
112
+ # available no matter which agent you're using, without per-agent installs.
113
+ local wired=()
114
+
115
+ if [ -d "$HOME/.claude" ]; then
116
+ wire_block "$AGENTS_DIR/CLAUDE.md" "$HOME/.claude/CLAUDE.md" import
117
+ wired+=("Claude Code -> $HOME/.claude/CLAUDE.md")
118
+ fi
119
+ if [ -d "$HOME/.gemini" ]; then
120
+ wire_block "$AGENTS_DIR/GEMINI.md" "$HOME/.gemini/GEMINI.md" import
121
+ wired+=("Gemini CLI -> $HOME/.gemini/GEMINI.md")
122
+ fi
123
+ if [ -d "$HOME/.copilot" ]; then
124
+ wire_block "$AGENTS_DIR/copilot-instructions.md" "$HOME/.copilot/instructions.md" inline
125
+ wired+=("GitHub Copilot CLI -> $HOME/.copilot/instructions.md")
126
+ fi
127
+ if [ -d "$HOME/.codex" ]; then
128
+ wire_block "$AGENTS_DIR/AGENTS.md" "$HOME/.codex/AGENTS.md" inline
129
+ wired+=("Codex CLI -> $HOME/.codex/AGENTS.md")
130
+ fi
131
+ # Generic fallback — Aider/OpenCode/Cursor/other share a plain ~/AGENTS.md
132
+ # with no dedicated config directory to detect, so always wire it.
133
+ wire_block "$AGENTS_DIR/AGENTS.md" "$HOME/AGENTS.md" inline
134
+ wired+=("Aider / OpenCode / Cursor / other -> $HOME/AGENTS.md")
135
+
136
+ local w
137
+ for w in "${wired[@]}"; do
138
+ log "Wired: $w"
139
+ done
140
+ }
141
+
142
+ unwire_all_agents() {
143
+ # Removes the REINS managed block from every native config it may have been
144
+ # wired into (no-op for files without a managed block).
145
+ local t
146
+ for t in "$HOME/.claude/CLAUDE.md" "$HOME/.gemini/GEMINI.md" \
147
+ "$HOME/.copilot/instructions.md" "$HOME/.codex/AGENTS.md" \
148
+ "$HOME/AGENTS.md"; do
149
+ if [ -f "$t" ] && grep -qF "REINS:BEGIN" "$t" 2>/dev/null; then
150
+ remove_managed_block "$t"
151
+ log "Removed REINS block from $t"
152
+ fi
153
+ done
154
+ }
155
+
156
+ collect_skills() {
157
+ # prints "<skill-dir>\t<reins-managed-name>" for every SKILL.md found under
158
+ # core/skills, user/skills, and active adapters' skills/
159
+ #
160
+ # Every registered name carries the "reins-" prefix exactly once. Skill
161
+ # directories that already start with "reins-" (the convention for core/user
162
+ # skills) are used as-is; older, unprefixed directory names still get
163
+ # "reins-" prepended for backwards compatibility.
164
+ local d name
165
+ for d in "$CORE_DIR"/skills/*/; do
166
+ [ -f "${d}SKILL.md" ] || continue
167
+ name="$(basename "$d")"
168
+ case "$name" in
169
+ reins-*) printf '%s\t%s\n' "${d%/}" "$name" ;;
170
+ *) printf '%s\treins-%s\n' "${d%/}" "$name" ;;
171
+ esac
172
+ done
173
+ for d in "$USER_DIR"/skills/*/; do
174
+ [ -f "${d}SKILL.md" ] || continue
175
+ name="$(basename "$d")"
176
+ case "$name" in
177
+ reins-*) printf '%s\t%s\n' "${d%/}" "$name" ;;
178
+ *) printf '%s\treins-%s\n' "${d%/}" "$name" ;;
179
+ esac
180
+ done
181
+ while IFS= read -r adapter; do
182
+ [ -n "$adapter" ] || continue
183
+ for d in "$USER_DIR/adapters/$adapter/skills"/*/; do
184
+ [ -f "${d}SKILL.md" ] || continue
185
+ name="$(basename "$d")"
186
+ case "$name" in
187
+ reins-*) name="${name#reins-}" ;;
188
+ esac
189
+ printf '%s\treins-%s-%s\n' "${d%/}" "$adapter" "$name"
190
+ done
191
+ done < <(config_adapters)
192
+ }
193
+
194
+ skill_frontmatter() {
195
+ # skill_frontmatter <SKILL.md> <key> -> prints a one-line value for "key: value"
196
+ # or "key: >" (folded scalar) frontmatter fields, empty if absent
197
+ local file="$1" key="$2"
198
+ awk -v k="$key" '
199
+ /^---$/ { fm++; next }
200
+ fm != 1 { next }
201
+ $0 ~ "^"k":" {
202
+ line=$0
203
+ sub("^"k":[[:space:]]*", "", line)
204
+ sub(/^>[[:space:]]*/, "", line)
205
+ if (line != "") { print line; found=1; exit }
206
+ found=1; next
207
+ }
208
+ found && /^[[:space:]]+[^[:space:]]/ {
209
+ line=$0
210
+ sub(/^[[:space:]]+/, "", line)
211
+ print line
212
+ exit
213
+ }
214
+ found { exit }
215
+ ' "$file"
216
+ }
217
+
218
+ cleanup_claude_skill_links() {
219
+ local claude_skills="$HOME/.claude/skills"
220
+ [ -d "$claude_skills" ] || return 0
221
+ find "$claude_skills" -maxdepth 1 -type l -name 'reins-*' -exec rm -f {} +
222
+ }
223
+
224
+ sync_skills() {
225
+ # Refreshes ~/.claude/skills/reins-* symlinks (if Claude Code is installed) so
226
+ # Claude Code's native Agent Skills discovery picks up REINS skills directly —
227
+ # regardless of which agent is configured as primary.
228
+ cleanup_claude_skill_links
229
+
230
+ [ -d "$HOME/.claude" ] || return 0
231
+
232
+ local claude_skills="$HOME/.claude/skills"
233
+ mkdir -p "$claude_skills"
234
+ while IFS=$'\t' read -r dir reins_name; do
235
+ [ -n "$dir" ] || continue
236
+ ln -sf "$dir" "$claude_skills/$reins_name"
237
+ done < <(collect_skills)
238
+ }
239
+
240
+ generate_bridges() {
241
+ # Regenerate ~/.reins/agents/*.md from core + user content, then wire native configs.
242
+ mkdir -p "$AGENTS_DIR"
243
+ local agent
244
+ agent="$(config_get agent)"
245
+ [ -n "$agent" ] || { err "no agent configured — run 'reins install'"; return 1; }
246
+
247
+ local active_adapters
248
+ active_adapters="$(config_adapters | tr '\n' ' ')"
249
+ local historic
250
+ historic="$(config_get historic_mode)"
251
+
252
+ local skills_md=""
253
+ while IFS=$'\t' read -r dir reins_name; do
254
+ [ -n "$dir" ] || continue
255
+ local sname sdesc
256
+ sname="$(skill_frontmatter "$dir/SKILL.md" name)"
257
+ sdesc="$(skill_frontmatter "$dir/SKILL.md" description)"
258
+ skills_md="${skills_md}- **${sname:-$reins_name}** (\`$dir\`): ${sdesc:-(no description)}
259
+ "
260
+ done < <(collect_skills)
261
+
262
+ local body
263
+ body=$(cat <<EOF
264
+ # REINS Method — Agent Bridge
265
+
266
+ This file is generated by \`reins update\` / \`reins install\`. Do not edit by hand —
267
+ edit \`~/.reins/user/config.yaml\` and re-run \`reins update\` instead.
268
+
269
+ ## Always read first
270
+ - @${CORE_DIR}/workflow/1_orchestrator.md
271
+
272
+ ## User standards
273
+ - @${USER_DIR}/standards/company.md
274
+ - @${USER_DIR}/standards/personal.md
275
+
276
+ ## Active adapters
277
+ ${active_adapters:-(none configured — generic conventions only)}
278
+
279
+ ## Historic mode
280
+ ${historic:-off}
281
+
282
+ ## Available skills
283
+ On-demand only — never load proactively (see \`1_orchestrator.md\` §5).
284
+ ${skills_md:-(none)}
285
+ EOF
286
+ )
287
+ printf '%s\n' "$body" > "$AGENTS_DIR/CLAUDE.md"
288
+ printf '%s\n' "$body" > "$AGENTS_DIR/GEMINI.md"
289
+ printf '%s\n' "$body" > "$AGENTS_DIR/AGENTS.md"
290
+ printf '%s\n' "$body" > "$AGENTS_DIR/copilot-instructions.md"
291
+
292
+ wire_all_agents
293
+ sync_skills
294
+
295
+ log "Bridge files regenerated in $AGENTS_DIR"
296
+ }
297
+
298
+ # ---------------------------------------------------------------------------
299
+ # commands
300
+ # ---------------------------------------------------------------------------
301
+
302
+ cmd_install() {
303
+ local non_interactive=0
304
+ local arg_agent="" arg_standards="" arg_historic="" arg_adapter=""
305
+ for arg in "$@"; do
306
+ case "$arg" in
307
+ --non-interactive) non_interactive=1 ;;
308
+ --agent=*) arg_agent="${arg#--agent=}" ;;
309
+ --standards=*) arg_standards="${arg#--standards=}" ;;
310
+ --historic=*) arg_historic="${arg#--historic=}" ;;
311
+ --adapter=*) arg_adapter="${arg#--adapter=}" ;;
312
+ esac
313
+ done
314
+
315
+ # When run via `curl | bash`, stdin is the piped script, not a terminal, so
316
+ # `read -p` below would fail instantly. Re-attach stdin to the controlling
317
+ # terminal if one is available so the wizard can still prompt interactively.
318
+ if [ "$non_interactive" -eq 0 ] && [ ! -t 0 ] && [ -e /dev/tty ]; then
319
+ exec < /dev/tty
320
+ fi
321
+
322
+ log "Welcome to REINS Method"
323
+ log ""
324
+
325
+ if [ -f "$CONFIG" ] && [ "$non_interactive" -eq 0 ]; then
326
+ read -r -p "REINS is already configured at $REINS_HOME. Re-run the wizard? [y/N] " yn
327
+ case "$yn" in [yY]*) ;; *) log "Aborted."; return 0 ;; esac
328
+ fi
329
+
330
+ mkdir -p "$USER_DIR"/{standards,adapters,skills,projects,historic}
331
+
332
+ local agent
333
+ if [ "$non_interactive" -eq 1 ]; then
334
+ agent="${arg_agent:-other}"
335
+ else
336
+ log "? What's your primary AI Coding Agent?"
337
+ log " (REINS wires every agent it detects on this machine automatically — this"
338
+ log " choice just sets your default for 'reins status'/'reins doctor'.)"
339
+ log " 1) Claude Code"
340
+ log " 2) GitHub Copilot CLI"
341
+ log " 3) Codex CLI"
342
+ log " 4) Gemini CLI"
343
+ log " 5) Aider"
344
+ log " 6) OpenCode / Cursor / Other"
345
+ read -r -p "Choice [1-6]: " agent_choice
346
+ case "$agent_choice" in
347
+ 1) agent="claude-code" ;;
348
+ 2) agent="copilot" ;;
349
+ 3) agent="codex" ;;
350
+ 4) agent="gemini" ;;
351
+ 5) agent="aider" ;;
352
+ *) agent="other" ;;
353
+ esac
354
+ fi
355
+ config_set agent "$agent"
356
+
357
+ touch "$USER_DIR/standards/company.md" "$USER_DIR/standards/personal.md"
358
+ if [ ! -s "$USER_DIR/standards/company.md" ]; then
359
+ cat > "$USER_DIR/standards/company.md" <<'EOF'
360
+ ---
361
+ scope: always
362
+ precedence: 1 # floor — non-negotiable
363
+ ---
364
+
365
+ # Company / Project Standards
366
+
367
+ Fill this in with the non-negotiable conventions for your company or project.
368
+ This file is read by the orchestrator on every session.
369
+ EOF
370
+ fi
371
+ if [ ! -s "$USER_DIR/standards/personal.md" ]; then
372
+ cat > "$USER_DIR/standards/personal.md" <<'EOF'
373
+ ---
374
+ scope: always
375
+ precedence: 2 # personal style — must never conflict with company.md
376
+ ---
377
+
378
+ # Personal Standards
379
+
380
+ Fill this in with your personal style preferences on top of company.md.
381
+ If anything here conflicts with company.md, the orchestrator will stop and ask.
382
+ EOF
383
+ fi
384
+ if [ "$non_interactive" -eq 1 ]; then
385
+ # The Node wizard (npx reins-method install) handles opening an editor
386
+ # for company.md itself, after this script returns — it has the TTY.
387
+ :
388
+ else
389
+ read -r -p "? Do you have company code standards to configure? [y/N] " yn
390
+ case "$yn" in
391
+ [yY]*)
392
+ "${EDITOR:-vi}" "$USER_DIR/standards/company.md"
393
+ ;;
394
+ esac
395
+ fi
396
+
397
+ if [ "$non_interactive" -eq 1 ]; then
398
+ case "$arg_historic" in
399
+ on) cmd_historic on --quiet ;;
400
+ *) config_set historic_mode off ;;
401
+ esac
402
+ else
403
+ read -r -p "? Do you want to enable Historic Mode (performance tracking)? [y/N] " yn
404
+ case "$yn" in
405
+ [yY]*) cmd_historic on --quiet ;;
406
+ *) config_set historic_mode off ;;
407
+ esac
408
+ fi
409
+
410
+ local adapter_path
411
+ if [ "$non_interactive" -eq 1 ]; then
412
+ adapter_path="$arg_adapter"
413
+ else
414
+ read -r -p "? Install an adapter pack now? Path to local pack (or leave empty to skip): " adapter_path
415
+ fi
416
+ if [ -n "$adapter_path" ] && [ -d "$adapter_path" ]; then
417
+ local adapter_name
418
+ adapter_name="$(basename "$adapter_path")"
419
+ mkdir -p "$USER_DIR/adapters"
420
+ cp -R "$adapter_path" "$USER_DIR/adapters/$adapter_name"
421
+ if grep -qE "^adapters:" "$CONFIG" 2>/dev/null; then
422
+ printf ' - %s\n' "$adapter_name" >> "$CONFIG"
423
+ else
424
+ printf 'adapters:\n - %s\n' "$adapter_name" >> "$CONFIG"
425
+ fi
426
+ log "Adapter '$adapter_name' installed."
427
+ fi
428
+
429
+ generate_bridges
430
+
431
+ # Make `reins` available on PATH
432
+ local target_bin="/usr/local/bin/reins"
433
+ if [ -w "$(dirname "$target_bin")" ] 2>/dev/null; then
434
+ ln -sf "$REINS_HOME/bin/reins" "$target_bin" 2>/dev/null || true
435
+ fi
436
+ if [ ! -x "$target_bin" ]; then
437
+ for rc in "$HOME/.zshrc" "$HOME/.bashrc"; do
438
+ [ -f "$rc" ] || continue
439
+ grep -qF "$REINS_HOME/bin" "$rc" 2>/dev/null || \
440
+ printf '\nexport PATH="%s/bin:$PATH"\n' "$REINS_HOME" >> "$rc"
441
+ done
442
+ fi
443
+
444
+ log ""
445
+ log "REINS Method installed to $REINS_HOME"
446
+ log "Run: source ~/.zshrc (or restart your terminal) to activate the 'reins' command"
447
+ log ""
448
+ log "Optional companion tools (see README.md 'Companion tools'):"
449
+ log " - headroom: token-efficient context compression (pip install \"headroom-ai[all]\")"
450
+ log " - graphify: codebase knowledge graph for richer architecture context (pip install graphifyy)"
451
+ log ""
452
+ log "Getting started: reins status"
453
+ }
454
+
455
+ cmd_update() {
456
+ if [ -d "$REINS_HOME/.git" ]; then
457
+ if ! git -C "$REINS_HOME" pull --ff-only; then
458
+ err "git pull failed — if this checkout has no upstream tracking branch, run:"
459
+ err " git -C $REINS_HOME branch --set-upstream-to=<remote>/<branch>"
460
+ generate_bridges
461
+ return 1
462
+ fi
463
+ else
464
+ die "$REINS_HOME is not a git checkout. Re-run install.sh to update, or 'git init' it manually."
465
+ fi
466
+ generate_bridges
467
+ log "REINS core updated. ~/.reins/user/ was not touched."
468
+ }
469
+
470
+ cmd_new_adapter() {
471
+ local name="${1:-}"
472
+ [ -n "$name" ] || die "usage: reins new-adapter <name>"
473
+ local dir="$USER_DIR/adapters/$name"
474
+ [ -e "$dir" ] && die "$dir already exists"
475
+ mkdir -p "$dir"/{standards,workflow,skills}
476
+ cat > "$dir/ADAPTER.md" <<EOF
477
+ ---
478
+ name: $name
479
+ stacks: [] # e.g. [ruby], [node], [python] — see core/workflow/1_orchestrator.md §2
480
+ author: $(whoami)
481
+ version: 0.1.0
482
+ description: >
483
+ Describe what this adapter is for and what conventions/skills it adds.
484
+ ---
485
+ EOF
486
+ cat > "$dir/standards/floor.md" <<'EOF'
487
+ ---
488
+ scope: always
489
+ precedence: 1 # floor — non-negotiable
490
+ ---
491
+
492
+ # Standards — Floor
493
+
494
+ Non-negotiable conventions for this stack.
495
+ EOF
496
+ log "Adapter scaffolded at $dir"
497
+ log "Edit ADAPTER.md (set 'stacks'), then standards/floor.md."
498
+ if grep -qE "^adapters:" "$CONFIG" 2>/dev/null; then
499
+ printf ' - %s\n' "$name" >> "$CONFIG"
500
+ else
501
+ printf 'adapters:\n - %s\n' "$name" >> "$CONFIG"
502
+ fi
503
+ generate_bridges
504
+ }
505
+
506
+ cmd_new_skill() {
507
+ local name="${1:-}"
508
+ [ -n "$name" ] || die "usage: reins new-skill <name>"
509
+ local dir="$USER_DIR/skills/$name"
510
+ [ -e "$dir" ] && die "$dir already exists"
511
+ mkdir -p "$dir"
512
+ cat > "$dir/SKILL.md" <<EOF
513
+ ---
514
+ name: $name
515
+ description: TODO — what this skill does and when to use it.
516
+ ---
517
+
518
+ # $name
519
+
520
+ ## Trigger
521
+ TODO
522
+
523
+ ## Context
524
+ TODO
525
+
526
+ ## Steps
527
+ TODO
528
+
529
+ ## Output
530
+ TODO
531
+ EOF
532
+ log "Skill scaffolded at $dir/SKILL.md — fill it in (see core/templates/skill.md)."
533
+ generate_bridges >/dev/null
534
+ }
535
+
536
+ cmd_sync() {
537
+ generate_bridges
538
+ }
539
+
540
+ cmd_link_agents() {
541
+ # Wire newly-installed AI agents into the existing bridge files without a
542
+ # full bridge regeneration — handy after installing a new agent on this
543
+ # machine (e.g. you set up Gemini CLI after running `reins install`).
544
+ [ -d "$AGENTS_DIR" ] && [ -n "$(ls -A "$AGENTS_DIR" 2>/dev/null)" ] || { err "no bridge files yet — run 'reins update' or 'reins sync' first"; return 1; }
545
+ wire_all_agents
546
+ sync_skills
547
+ log "Done."
548
+ }
549
+
550
+ cmd_historic() {
551
+ local mode="${1:-}"
552
+ case "$mode" in
553
+ on)
554
+ mkdir -p "$USER_DIR/historic"
555
+ config_set historic_mode on
556
+ local month_file="$USER_DIR/historic/$(date +%Y-%m).md"
557
+ if [ ! -f "$month_file" ]; then
558
+ cp "$CORE_DIR/evaluation/templates/monthly.md" "$month_file"
559
+ [ "${2:-}" = "--quiet" ] || log "Created $month_file — fill in this period's priorities."
560
+ fi
561
+ [ "${2:-}" = "--quiet" ] || log "Historic mode: on"
562
+ ;;
563
+ off)
564
+ config_set historic_mode off
565
+ log "Historic mode: off (data preserved at $USER_DIR/historic/)"
566
+ ;;
567
+ *) die "usage: reins historic on|off" ;;
568
+ esac
569
+ generate_bridges >/dev/null
570
+ }
571
+
572
+ cmd_status() {
573
+ log "REINS_HOME: $REINS_HOME"
574
+ if [ -d "$REINS_HOME/.git" ]; then
575
+ log "Version: $(git -C "$REINS_HOME" rev-parse --short HEAD 2>/dev/null) ($(git -C "$REINS_HOME" log -1 --format=%cd --date=short 2>/dev/null))"
576
+ fi
577
+ log "Agent: $(config_get agent || echo 'not configured')"
578
+ log "Historic mode: $(config_get historic_mode || echo 'off')"
579
+ local adapters
580
+ adapters="$(config_adapters | tr '\n' ' ')"
581
+ log "Adapters: ${adapters:-none}"
582
+
583
+ if git rev-parse --show-toplevel >/dev/null 2>&1; then
584
+ local slug
585
+ slug="$(basename "$(git rev-parse --show-toplevel)")"
586
+ local ctx_dir="$USER_DIR/projects/$slug/contexts"
587
+ if [ -d "$ctx_dir" ]; then
588
+ local active
589
+ active="$(grep -lE '^status:[[:space:]]*active' "$ctx_dir"/*.md 2>/dev/null || true)"
590
+ if [ -n "$active" ]; then
591
+ log "Active context (this project): $(basename "$active")"
592
+ else
593
+ log "Active context (this project): none"
594
+ fi
595
+ fi
596
+ fi
597
+ }
598
+
599
+ cmd_doctor() {
600
+ local problems=0
601
+
602
+ [ -d "$CORE_DIR" ] || { err "missing $CORE_DIR"; problems=$((problems+1)); }
603
+ [ -f "$CONFIG" ] || { err "missing $CONFIG — run 'reins install'"; problems=$((problems+1)); }
604
+
605
+ local agent
606
+ agent="$(config_get agent)"
607
+ if [ -n "$agent" ]; then
608
+ local entry
609
+ if entry="$(agent_bridge_target "$agent" 2>/dev/null)"; then
610
+ local bridge="${entry%%:*}"; local rest="${entry#*:}"
611
+ local native="${rest%%:*}"
612
+ [ -f "$bridge" ] || { err "missing bridge file $bridge — run 'reins update'"; problems=$((problems+1)); }
613
+ if [ -f "$native" ]; then
614
+ grep -qF "REINS:BEGIN" "$native" || { err "$native is missing the REINS managed block — run 'reins update'"; problems=$((problems+1)); }
615
+ else
616
+ err "$native does not exist — run 'reins update'"; problems=$((problems+1))
617
+ fi
618
+ else
619
+ err "unknown agent '$agent' in config"; problems=$((problems+1))
620
+ fi
621
+ else
622
+ err "no agent configured — run 'reins install'"; problems=$((problems+1))
623
+ fi
624
+
625
+ while IFS= read -r adapter; do
626
+ [ -n "$adapter" ] || continue
627
+ local dir="$USER_DIR/adapters/$adapter"
628
+ [ -d "$dir" ] || { err "adapter '$adapter' listed in config but missing at $dir"; problems=$((problems+1)); }
629
+ [ -f "$dir/ADAPTER.md" ] || { err "adapter '$adapter' missing ADAPTER.md"; problems=$((problems+1)); }
630
+ done < <(config_adapters)
631
+
632
+ if [ "$problems" -eq 0 ]; then
633
+ log "Everything looks good."
634
+ else
635
+ log ""
636
+ log "$problems issue(s) found."
637
+ return 1
638
+ fi
639
+ }
640
+
641
+ cmd_uninstall() {
642
+ log "Uninstalling REINS Method..."
643
+
644
+ # Remove the managed block from every native config REINS may have wired
645
+ unwire_all_agents
646
+
647
+ # Remove REINS-managed Claude Code skill symlinks, if any
648
+ if [ -d "$HOME/.claude/skills" ] && find "$HOME/.claude/skills" -maxdepth 1 -type l -name 'reins-*' -print -quit | grep -q .; then
649
+ cleanup_claude_skill_links
650
+ log "Removed REINS skill symlinks from $HOME/.claude/skills"
651
+ fi
652
+
653
+ # Remove the /usr/local/bin/reins symlink, if it points into REINS_HOME
654
+ local target_bin="/usr/local/bin/reins"
655
+ if [ -L "$target_bin" ] && [ "$(readlink "$target_bin")" = "$REINS_HOME/bin/reins" ]; then
656
+ rm -f "$target_bin"
657
+ log "Removed $target_bin"
658
+ fi
659
+
660
+ # Remove the PATH export line from shell rc files
661
+ for rc in "$HOME/.zshrc" "$HOME/.bashrc"; do
662
+ [ -f "$rc" ] || continue
663
+ if grep -qF "$REINS_HOME/bin" "$rc" 2>/dev/null; then
664
+ grep -vF "export PATH=\"$REINS_HOME/bin:\$PATH\"" "$rc" > "$rc.tmp" && mv "$rc.tmp" "$rc"
665
+ log "Removed PATH export from $rc"
666
+ fi
667
+ done
668
+
669
+ log ""
670
+ log "REINS is unhooked from your AI agent and shell."
671
+ log "$REINS_HOME still contains your data, including \$REINS_HOME/user/ (standards,"
672
+ log "adapters, skills, project contexts, historic records)."
673
+ read -r -p "Delete $REINS_HOME entirely, including user/? This cannot be undone. [y/N] " yn
674
+ case "$yn" in
675
+ [yY]*)
676
+ # Offer to back up user-created skills/adapters before deleting everything
677
+ local has_custom=""
678
+ [ -d "$USER_DIR/skills" ] && find "$USER_DIR/skills" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q . && has_custom=1
679
+ [ -d "$USER_DIR/adapters" ] && find "$USER_DIR/adapters" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q . && has_custom=1
680
+ if [ -n "$has_custom" ]; then
681
+ read -r -p "Back up your custom skills/adapters before deleting? [y/N] " backup_yn
682
+ case "$backup_yn" in
683
+ [yY]*)
684
+ read -r -p "Directory to copy them to: " backup_dir
685
+ if [ -n "$backup_dir" ]; then
686
+ mkdir -p "$backup_dir"
687
+ [ -d "$USER_DIR/skills" ] && cp -R "$USER_DIR/skills" "$backup_dir/"
688
+ [ -d "$USER_DIR/adapters" ] && cp -R "$USER_DIR/adapters" "$backup_dir/"
689
+ log "Copied $USER_DIR/skills and $USER_DIR/adapters to $backup_dir"
690
+ else
691
+ log "No directory given — skipping backup."
692
+ fi
693
+ ;;
694
+ esac
695
+ fi
696
+ rm -rf "$REINS_HOME"
697
+ log "Removed $REINS_HOME"
698
+ ;;
699
+ *)
700
+ log "Left $REINS_HOME in place."
701
+ ;;
702
+ esac
703
+ }
704
+
705
+ # ---------------------------------------------------------------------------
706
+ # dispatch
707
+ # ---------------------------------------------------------------------------
708
+
709
+ usage() {
710
+ cat <<'EOF'
711
+ REINS Method CLI
712
+
713
+ Usage:
714
+ reins install First-time setup (interactive wizard)
715
+ reins install --non-interactive --agent=<id> [--standards=yes|no]
716
+ [--historic=on|off] [--adapter=<path>]
717
+ Non-interactive setup, driven by flags
718
+ (used by `npx reins-method install`)
719
+ reins update Pull latest core, regenerate agent bridge files
720
+ reins new-adapter <name> Scaffold a new adapter pack
721
+ reins new-skill <name> Scaffold a new skill
722
+ reins sync Regenerate agent bridges + skill registration (no git pull)
723
+ reins link-agents Wire any newly-installed AI agents into existing bridges
724
+ reins historic on|off Enable/disable historic mode
725
+ reins status Show installed version, agent, adapters, historic mode
726
+ reins doctor Validate the installation
727
+ reins uninstall Unhook REINS from your agent/shell, optionally delete ~/.reins
728
+ EOF
729
+ }
730
+
731
+ main() {
732
+ local cmd="${1:-}"
733
+ [ -n "$cmd" ] && shift || true
734
+ case "$cmd" in
735
+ install) cmd_install "$@" ;;
736
+ update) cmd_update "$@" ;;
737
+ new-adapter) cmd_new_adapter "$@" ;;
738
+ new-skill) cmd_new_skill "$@" ;;
739
+ sync) cmd_sync "$@" ;;
740
+ link-agents) cmd_link_agents "$@" ;;
741
+ historic) cmd_historic "$@" ;;
742
+ status) cmd_status "$@" ;;
743
+ doctor) cmd_doctor "$@" ;;
744
+ uninstall) cmd_uninstall "$@" ;;
745
+ -h|--help|help|"") usage ;;
746
+ *) err "unknown command: $cmd"; usage; exit 1 ;;
747
+ esac
748
+ }
749
+
750
+ main "$@"