youmd 0.7.0 → 0.8.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.
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env bash
2
+ # context-sync.sh — per-project agent context syncer (cross-machine, code-safe)
3
+ # Compatible: bash 3.2+, macOS BSD coreutils (NO mapfile, NO GNU mktemp flags)
4
+ #
5
+ # WHAT IT DOES
6
+ # ============
7
+ # For each of Houston's own git repos, commits ONLY the agent-context paths
8
+ # (AGENTS.md, CLAUDE.md, project-context/, .claude/) and pull+pushes, so
9
+ # agent context travels across machines without ever touching application code.
10
+ #
11
+ # SAFETY GUARANTEES
12
+ # =================
13
+ # 1. Only stages named context paths — NEVER uses `git add -A` or `git add .`
14
+ # 2. Never stashes, resets, checks out, or cleans anything in the working tree
15
+ # 3. Never touches a repo that is mid-merge or mid-rebase
16
+ # 4. Never touches a repo with no `origin` remote
17
+ # 5. If the working tree has uncommitted non-context changes, commits context
18
+ # locally but SKIPS pull/push to avoid merge conflicts touching his code;
19
+ # logs a clear "push manually" warning
20
+ # 6. On pull conflict: aborts merge, skips push, logs loudly — never force
21
+ # 7. Per-repo failures are non-fatal; the loop always continues
22
+ # 8. --dry-run logs intended actions; writes nothing to disk
23
+ #
24
+ # WHAT IS EXCLUDED (by design)
25
+ # =============================
26
+ # - Third-party / fork repos (disler/*, coffeefuelbump/*, etc.) — Houston
27
+ # doesn't own the remote, so auto-pushing context there is wrong
28
+ # - Repos with no origin remote or push-disabled remotes — detected at runtime
29
+ # - gstack, agent-shared, scistack — covered by sync.sh (skillstack layer)
30
+ #
31
+ # Usage:
32
+ # ./context-sync.sh # sync all context repos
33
+ # ./context-sync.sh --dry-run # log intended actions only, write nothing
34
+ #
35
+ # Env overrides:
36
+ # CONTEXT_REPOS space-separated list of repo DIRECTORY NAMES under
37
+ # ~/Desktop/CODE_2025/ (not full paths)
38
+ # GIT_USER_NAME commit author name (default: Houston Golden)
39
+ # GIT_USER_EMAIL commit author email (default: houston@bamf.ai)
40
+
41
+ set -euo pipefail
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Config
45
+ # ---------------------------------------------------------------------------
46
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
47
+ LOG_DIR="${HOME}/.youmd/logs"
48
+ LOG_FILE="${LOG_DIR}/context-sync.log"
49
+
50
+ GIT_USER_NAME="${GIT_USER_NAME:-Houston Golden}"
51
+ GIT_USER_EMAIL="${GIT_USER_EMAIL:-houston@bamf.ai}"
52
+
53
+ CODE_ROOT="${HOME}/Desktop/CODE_2025"
54
+
55
+ # Curated list of Houston's OWN repos with rich agent context.
56
+ # These all have houstongolden/* or hubify-projects/* remotes — confirmed safe
57
+ # to push to. Override via env CONTEXT_REPOS (space-separated dir names).
58
+ #
59
+ # Excluded by design:
60
+ # - disler/*, coffeefuelbump/*, etc. → not Houston's remote
61
+ # - repos with no origin → detected and skipped at runtime
62
+ # - gstack, agent-shared, scistack → handled by sync.sh
63
+ DEFAULT_CONTEXT_REPOS="bamfaiapp bigbounce hubify-aios myo hubifycode badapp claws"
64
+ CONTEXT_REPOS="${CONTEXT_REPOS:-${DEFAULT_CONTEXT_REPOS}}"
65
+
66
+ # The ONLY paths we will ever stage. Nothing else is touched.
67
+ CONTEXT_PATHSPECS="AGENTS.md CLAUDE.md project-context .claude"
68
+
69
+ DRY_RUN=0
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Parse flags
73
+ # ---------------------------------------------------------------------------
74
+ for arg in "$@"; do
75
+ case "$arg" in
76
+ --dry-run) DRY_RUN=1 ;;
77
+ --once) : ;; # accepted for explicit invocation parity with sync.sh
78
+ *) echo "Unknown flag: $arg" >&2; exit 1 ;;
79
+ esac
80
+ done
81
+
82
+ # ---------------------------------------------------------------------------
83
+ # Logging
84
+ # ---------------------------------------------------------------------------
85
+ mkdir -p "${LOG_DIR}"
86
+
87
+ log() {
88
+ local ts
89
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
90
+ local msg="[${ts}] $*"
91
+ echo "${msg}"
92
+ echo "${msg}" >> "${LOG_FILE}"
93
+ }
94
+
95
+ log_warn() {
96
+ log "WARN $*"
97
+ }
98
+
99
+ log_error() {
100
+ log "ERROR $*"
101
+ }
102
+
103
+ log_dry() {
104
+ log "DRY $*"
105
+ }
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # Helpers
109
+ # ---------------------------------------------------------------------------
110
+
111
+ hostname_short() {
112
+ # BSD hostname (no --short flag on macOS)
113
+ hostname | cut -d. -f1
114
+ }
115
+
116
+ is_dry() {
117
+ [ "${DRY_RUN}" -eq 1 ]
118
+ }
119
+
120
+ # Check if repo is in a mid-merge/rebase state
121
+ repo_is_in_progress() {
122
+ local repo="$1"
123
+ if [ -f "${repo}/.git/MERGE_HEAD" ] \
124
+ || [ -d "${repo}/.git/rebase-merge" ] \
125
+ || [ -d "${repo}/.git/rebase-apply" ]; then
126
+ return 0
127
+ fi
128
+ return 1
129
+ }
130
+
131
+ # Collect context pathspecs that actually exist in this repo.
132
+ # Returns a space-separated list (possibly empty).
133
+ existing_context_paths() {
134
+ local repo="$1"
135
+ local found=""
136
+ local p
137
+ for p in ${CONTEXT_PATHSPECS}; do
138
+ if [ -e "${repo}/${p}" ]; then
139
+ if [ -z "${found}" ]; then
140
+ found="${p}"
141
+ else
142
+ found="${found} ${p}"
143
+ fi
144
+ fi
145
+ done
146
+ echo "${found}"
147
+ }
148
+
149
+ # ---------------------------------------------------------------------------
150
+ # SYNC ONE REPO
151
+ # Returns 0 on success, 1 on non-fatal failure.
152
+ #
153
+ # Pull/push safety decision (dirty non-context working tree):
154
+ # ============================================================
155
+ # After committing context changes, the working tree may still have untracked
156
+ # or modified files (Houston's WIP code). A `git pull` that brings in remote
157
+ # changes could attempt to merge those dirty files, causing conflicts or
158
+ # accidentally modifying them. The SAFE choice:
159
+ # - If the working tree has ANY remaining dirty files after the context
160
+ # commit, SKIP pull/push and log a clear warning.
161
+ # - The context commit is still made locally, so the changes are preserved
162
+ # and Houston can push manually when he's ready.
163
+ # - We never stash, reset, or touch his code.
164
+ # ---------------------------------------------------------------------------
165
+ sync_context_repo() {
166
+ local repo_name="$1"
167
+ local repo="${CODE_ROOT}/${repo_name}"
168
+
169
+ log "=== Syncing context for: ${repo_name} ==="
170
+
171
+ # Guard: directory must exist
172
+ if [ ! -d "${repo}" ]; then
173
+ log_warn "Skipping ${repo_name}: directory not found at ${repo}."
174
+ return 1
175
+ fi
176
+
177
+ # Guard: must be a git repo
178
+ if [ ! -d "${repo}/.git" ]; then
179
+ log_warn "Skipping ${repo_name}: not a git repository."
180
+ return 1
181
+ fi
182
+
183
+ # Guard: must not be mid-merge/rebase
184
+ if repo_is_in_progress "${repo}"; then
185
+ log_warn "Skipping ${repo_name}: repository is mid-merge or mid-rebase. Resolve manually first."
186
+ return 1
187
+ fi
188
+
189
+ # Guard: must have an origin remote
190
+ if ! git -C "${repo}" remote get-url origin >/dev/null 2>&1; then
191
+ log_warn "Skipping ${repo_name}: no 'origin' remote configured."
192
+ return 1
193
+ fi
194
+
195
+ # Determine which context paths exist in this repo
196
+ local paths
197
+ paths="$(existing_context_paths "${repo}")"
198
+
199
+ if [ -z "${paths}" ]; then
200
+ log "No context paths found in ${repo_name} (none of: ${CONTEXT_PATHSPECS}). Skipping."
201
+ return 0
202
+ fi
203
+
204
+ log "Context paths present in ${repo_name}: ${paths}"
205
+
206
+ # ---- Stage ONLY the context paths (never git add -A or git add .) --------
207
+ if is_dry; then
208
+ log_dry "Would stage in ${repo_name}: ${paths}"
209
+ else
210
+ # Build the git add command with only existing paths.
211
+ # We pass each path as a separate argument — safe because they come from
212
+ # our controlled CONTEXT_PATHSPECS list, not user input.
213
+ local add_args=""
214
+ local p
215
+ for p in ${paths}; do
216
+ add_args="${add_args} ${p}"
217
+ done
218
+ # shellcheck disable=SC2086
219
+ git -C "${repo}" add -- ${add_args}
220
+ log "Staged context paths in ${repo_name}: ${paths}"
221
+ fi
222
+
223
+ # ---- Commit if staged changes exist ---------------------------------------
224
+ local context_committed=0
225
+ if is_dry; then
226
+ log_dry "Would check for staged changes and commit context in ${repo_name} if any"
227
+ context_committed=1
228
+ else
229
+ if ! git -C "${repo}" diff --cached --quiet 2>/dev/null; then
230
+ local ts host commit_msg
231
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
232
+ host="$(hostname_short)"
233
+ commit_msg="context-sync: ${host} ${ts}"
234
+
235
+ git -C "${repo}" \
236
+ -c "user.name=${GIT_USER_NAME}" \
237
+ -c "user.email=${GIT_USER_EMAIL}" \
238
+ commit -m "${commit_msg}"
239
+ log "Committed context changes in ${repo_name}: '${commit_msg}'"
240
+ context_committed=1
241
+ else
242
+ log "No staged context changes in ${repo_name} (already up to date)."
243
+ context_committed=0
244
+ fi
245
+ fi
246
+
247
+ # ---- Check for remaining dirty working tree (WIP code) -------------------
248
+ # After committing context, if there are still dirty files, skip pull/push
249
+ # to avoid merge conflicts or accidental code modification.
250
+ if ! is_dry; then
251
+ local remaining_dirty
252
+ remaining_dirty="$(git -C "${repo}" status --porcelain 2>/dev/null)"
253
+ if [ -n "${remaining_dirty}" ]; then
254
+ log_warn "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
255
+ log_warn "${repo_name} has uncommitted non-context changes (WIP code)."
256
+ log_warn "Context was committed locally. Skipping pull/push to avoid"
257
+ log_warn "touching your code. Push manually when your code is ready:"
258
+ log_warn " cd ${repo} && git pull --no-rebase && git push"
259
+ log_warn "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
260
+ return 0
261
+ fi
262
+ fi
263
+
264
+ # ---- Pull (merge, no rebase) ---------------------------------------------
265
+ if is_dry; then
266
+ log_dry "Would git pull --no-rebase --no-edit in ${repo_name}"
267
+ else
268
+ local pull_log
269
+ # BSD mktemp: no -p, use /tmp explicitly
270
+ pull_log="$(mktemp -t context_sync_pull)"
271
+ local pull_ok=0
272
+ git -C "${repo}" pull --no-rebase --no-edit >"${pull_log}" 2>&1 || pull_ok=$?
273
+
274
+ while IFS= read -r line; do
275
+ log "pull|${repo_name}: ${line}"
276
+ done < "${pull_log}"
277
+ rm -f "${pull_log}"
278
+
279
+ if [ "${pull_ok}" -ne 0 ]; then
280
+ log_error "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
281
+ log_error "CONFLICT in ${repo_name} — pull failed."
282
+ log_error "Aborting merge and skipping push."
283
+ log_error "Manual resolution needed: cd ${repo} && git status"
284
+ log_error "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
285
+ git -C "${repo}" merge --abort 2>/dev/null || true
286
+ git -C "${repo}" rebase --abort 2>/dev/null || true
287
+ return 1
288
+ fi
289
+ log "Pull succeeded for ${repo_name}."
290
+ fi
291
+
292
+ # ---- Push ----------------------------------------------------------------
293
+ if is_dry; then
294
+ log_dry "Would git push in ${repo_name}"
295
+ else
296
+ local push_log
297
+ push_log="$(mktemp -t context_sync_push)"
298
+ local push_ok=0
299
+ git -C "${repo}" push >"${push_log}" 2>&1 || push_ok=$?
300
+
301
+ while IFS= read -r line; do
302
+ log "push|${repo_name}: ${line}"
303
+ done < "${push_log}"
304
+ rm -f "${push_log}"
305
+
306
+ if [ "${push_ok}" -ne 0 ]; then
307
+ log_error "Push FAILED for ${repo_name}. Check connectivity/auth."
308
+ return 1
309
+ fi
310
+ log "Push succeeded for ${repo_name}."
311
+ fi
312
+
313
+ log "=== Done: ${repo_name} ==="
314
+ return 0
315
+ }
316
+
317
+ # ---------------------------------------------------------------------------
318
+ # MAIN
319
+ # ---------------------------------------------------------------------------
320
+ main() {
321
+ local mode
322
+ if is_dry; then
323
+ mode="DRY-RUN"
324
+ else
325
+ mode="LIVE"
326
+ fi
327
+
328
+ log "============================================================"
329
+ log "context-sync START [${mode}] on $(hostname_short)"
330
+ log "Repos: ${CONTEXT_REPOS}"
331
+ log "Context paths: ${CONTEXT_PATHSPECS}"
332
+ log "============================================================"
333
+
334
+ local repo_name
335
+ for repo_name in ${CONTEXT_REPOS}; do
336
+ sync_context_repo "${repo_name}" || true
337
+ done
338
+
339
+ log "============================================================"
340
+ log "context-sync DONE [${mode}]"
341
+ log "============================================================"
342
+ }
343
+
344
+ main "$@"
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env bash
2
+ # restore-agent-config.sh — apply ~/.agent-shared/agent-config/ back onto this machine
3
+ #
4
+ # Mirror-image of capture-agent-config.sh. Takes each item in agent-config/
5
+ # and copies it to its canonical home location, backing up any existing
6
+ # file/dir to <dest>.bak.<UTC-ts> first (unless --force skips the backup).
7
+ #
8
+ # Restored mappings:
9
+ # agent-config/claude/* → ~/.claude/
10
+ # agent-config/codex/config.toml → ~/.codex/config.toml
11
+ # agent-config/warp/* → ~/.warp/
12
+ # agent-config/agents-skills/* → ~/.agents/skills/
13
+ # agent-config/automations/launchd/*.plist → ~/Library/LaunchAgents/ (HOME templated back)
14
+ # agent-config/automations/crontab.txt → PRINTED ONLY (user must merge manually)
15
+ #
16
+ # bash 3.2 / BSD safe. Idempotent. --dry-run to preview without writing.
17
+ # --force to skip per-file backups (still idempotent, overwrites in place).
18
+ #
19
+ # SECRETS: there should be none in agent-config (the capture script guards
20
+ # against it), but this script never prints file contents — only paths.
21
+ #
22
+ # Usage: bash restore-agent-config.sh [--dry-run] [--force]
23
+
24
+ set -euo pipefail
25
+
26
+ AGENT_SHARED="${HOME}/.agent-shared"
27
+ SRC="${AGENT_SHARED}/agent-config"
28
+ LOG_DIR="${HOME}/.youmd/logs"
29
+ LOG_FILE="${LOG_DIR}/restore-agent-config.log"
30
+
31
+ DRY_RUN=0
32
+ FORCE=0
33
+ for arg in "$@"; do
34
+ case "$arg" in
35
+ --dry-run) DRY_RUN=1 ;;
36
+ --force) FORCE=1 ;;
37
+ *) echo "Unknown flag: $arg" >&2; exit 1 ;;
38
+ esac
39
+ done
40
+
41
+ mkdir -p "${LOG_DIR}"
42
+ log() { local ts; ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"; echo "[${ts}] $*"; echo "[${ts}] $*" >> "${LOG_FILE}"; }
43
+ is_dry() { [ "${DRY_RUN}" -eq 1 ]; }
44
+ is_force() { [ "${FORCE}" -eq 1 ]; }
45
+
46
+ if [ ! -d "${AGENT_SHARED}/.git" ]; then
47
+ log "ERROR: ${AGENT_SHARED} is not a git repo — run 'youmd machine setup' first."
48
+ exit 1
49
+ fi
50
+
51
+ if [ ! -d "${SRC}" ]; then
52
+ log "ERROR: ${SRC} does not exist — nothing to restore. Run 'youmd machine capture' on the source machine first."
53
+ exit 1
54
+ fi
55
+
56
+ log "=== restore-agent-config START ($([ "${DRY_RUN}" -eq 1 ] && echo DRY || echo LIVE) $([ "${FORCE}" -eq 1 ] && echo FORCE || echo SAFE)) ==="
57
+
58
+ # backup_dest <path>
59
+ # If the dest exists and --force is not set, back it up.
60
+ backup_dest() {
61
+ local dest="$1"
62
+ if [ -e "${dest}" ] && ! is_force; then
63
+ local ts; ts="$(date -u +"%Y%m%dT%H%M%SZ")"
64
+ local bak="${dest}.bak.${ts}"
65
+ if is_dry; then
66
+ log "DRY would backup ${dest} → ${bak}"
67
+ else
68
+ mv "${dest}" "${bak}"
69
+ log "backed up ${dest} → ${bak}"
70
+ fi
71
+ fi
72
+ }
73
+
74
+ # copy_out <src-relative-to-agent-config> <absolute-dest>
75
+ # Copy a file or directory from agent-config onto this machine.
76
+ copy_out() {
77
+ local rel="$1" dest="$2" src
78
+ src="${SRC}/${rel}"
79
+ if [ ! -e "${src}" ]; then return 0; fi
80
+
81
+ if is_dry; then
82
+ log "DRY would restore agent-config/${rel} → ${dest}"
83
+ return 0
84
+ fi
85
+
86
+ mkdir -p "$(dirname "${dest}")"
87
+ backup_dest "${dest}"
88
+ if [ -d "${src}" ]; then
89
+ rsync -a "${src}/" "${dest}/"
90
+ else
91
+ cp "${src}" "${dest}"
92
+ fi
93
+ log "restored agent-config/${rel} → ${dest}"
94
+ }
95
+
96
+ # ── Claude config ──────────────────────────────────────────────────────────────
97
+ copy_out "claude/settings.json" "${HOME}/.claude/settings.json"
98
+ copy_out "claude/settings.local.json" "${HOME}/.claude/settings.local.json"
99
+ copy_out "claude/commands" "${HOME}/.claude/commands"
100
+ copy_out "claude/mcp.json" "${HOME}/.claude/mcp.json"
101
+ copy_out "claude/plugins/installed_plugins.json" "${HOME}/.claude/plugins/installed_plugins.json"
102
+
103
+ # ── Codex config ───────────────────────────────────────────────────────────────
104
+ copy_out "codex/config.toml" "${HOME}/.codex/config.toml"
105
+
106
+ # ── Warp ───────────────────────────────────────────────────────────────────────
107
+ copy_out "warp/settings.toml" "${HOME}/.warp/settings.toml"
108
+ copy_out "warp/keybindings.yaml" "${HOME}/.warp/keybindings.yaml"
109
+
110
+ # ── Orphaned ~/.agents skills ──────────────────────────────────────────────────
111
+ copy_out "agents-skills/find-skills" "${HOME}/.agents/skills/find-skills"
112
+ copy_out "agents-skills/ui-ux-pro-max" "${HOME}/.agents/skills/ui-ux-pro-max"
113
+
114
+ # ── Automations: launchd plists (HOME-templated → real HOME) ───────────────────
115
+ LAUNCH_AGENTS_DIR="${HOME}/Library/LaunchAgents"
116
+ LAUNCHD_SRC="${SRC}/automations/launchd"
117
+ if [ -d "${LAUNCHD_SRC}" ]; then
118
+ # BSD-compatible glob: use find to enumerate plists
119
+ while IFS= read -r plist_src; do
120
+ name="$(basename "${plist_src}")"
121
+ dest="${LAUNCH_AGENTS_DIR}/${name}"
122
+ if is_dry; then
123
+ log "DRY would restore plist ${name} → ${dest} (with __HOME__ → \$HOME)"
124
+ else
125
+ mkdir -p "${LAUNCH_AGENTS_DIR}"
126
+ backup_dest "${dest}"
127
+ # Replace __HOME__ placeholder with the real HOME value (BSD sed, no -i extension trick)
128
+ sed "s|__HOME__|${HOME}|g" "${plist_src}" > "${dest}"
129
+ log "restored plist ${name} → ${dest}"
130
+ fi
131
+ log "NOTE: to load this daemon run: launchctl load -w ${dest}"
132
+ log " (or run: youmd stack daemon install — covers the youmd ones)"
133
+ done < <(find "${LAUNCHD_SRC}" -maxdepth 1 -name "*.plist" 2>/dev/null || true)
134
+ fi
135
+
136
+ # ── Crontab — PRINT ONLY, never overwrite automatically ────────────────────────
137
+ CRONTAB_SRC="${SRC}/automations/crontab.txt"
138
+ if [ -f "${CRONTAB_SRC}" ]; then
139
+ log "crontab.txt found — NOT writing automatically (clobbering crontab is destructive)."
140
+ log "Inspect and merge manually with: crontab -e"
141
+ echo ""
142
+ echo " ── captured crontab entries (merge manually with 'crontab -e') ──"
143
+ # Print each line with a leading " " indent so it's visually distinct.
144
+ # Use a while-read loop (no cat) to stay shell-safe.
145
+ while IFS= read -r line; do
146
+ echo " ${line}"
147
+ done < "${CRONTAB_SRC}"
148
+ echo " ── end crontab ──"
149
+ echo ""
150
+ fi
151
+
152
+ log "=== restore-agent-config DONE ==="
@@ -30,12 +30,15 @@ SCISTACK="${HOME}/.claude/scistack"
30
30
  LOOSE_SKILLS="agent-runtime-guard agent-stack-sync continue skill-governor"
31
31
 
32
32
  # Repos to sync (space-separated absolute paths; override via env).
33
- # v1 default is agent-shared ONLY — it's purpose-built for sync and safe to
34
- # auto-commit. scistack is intentionally NOT auto-synced by default: it holds
35
- # protected learning-loop IP where auto-committing WIP on a timer is undesirable.
36
- # To opt scistack (or other repos) in, set:
37
- # SKILLSTACK_REPOS="$HOME/.agent-shared $HOME/.claude/scistack"
38
- DEFAULT_REPOS="${AGENT_SHARED}"
33
+ # Houston's agent skill/stack layer syncs by DEFAULT:
34
+ # - ~/.agent-shared → shared agent config + loose skills (houstongolden/agent-shared)
35
+ # - ~/.claude/scistack ALL science skills (Hubify-Projects/scistack): 68 skill
36
+ # symlink targets under hubstack/ + astrostack/ + extensions/, incl. the
37
+ # learning-loop IP. Conflict-safe (commit→merge-pull→push, abort on conflict),
38
+ # so auto-syncing his own repo is exactly the cross-machine behavior intended.
39
+ # gstack is NOT here (upstream garrytan/gstack, reinstalled via gstack-upgrade).
40
+ # Add more repos by setting SKILLSTACK_REPOS to a space-separated path list.
41
+ DEFAULT_REPOS="${AGENT_SHARED} ${SCISTACK}"
39
42
  SKILLSTACK_REPOS="${SKILLSTACK_REPOS:-${DEFAULT_REPOS}}"
40
43
 
41
44
  DRY_RUN=0
@@ -291,6 +294,17 @@ main() {
291
294
  log "skillstack-sync START [${mode}] on $(hostname_short)"
292
295
  log "============================================================"
293
296
 
297
+ # Step 0: Capture non-secret agent config (claude/codex/warp settings, slash
298
+ # commands, plugin list, orphan skills, automations) into agent-shared so it
299
+ # travels with the sync. Secret-leak-guarded; never blocks the sync on failure.
300
+ if [ -x "${SCRIPT_DIR}/capture-agent-config.sh" ]; then
301
+ if is_dry; then
302
+ bash "${SCRIPT_DIR}/capture-agent-config.sh" --dry-run || log_warn "capture-agent-config dry-run failed (non-fatal)"
303
+ else
304
+ bash "${SCRIPT_DIR}/capture-agent-config.sh" || log_warn "capture-agent-config failed (non-fatal — continuing sync)"
305
+ fi
306
+ fi
307
+
294
308
  # Step 1: Mirror loose skills into agent-shared BEFORE syncing it
295
309
  mirror_loose_skills_to_repo
296
310