youmd 0.8.0 → 0.8.2

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.
Files changed (115) hide show
  1. package/dist/commands/chat.d.ts.map +1 -1
  2. package/dist/commands/chat.js +3 -2
  3. package/dist/commands/chat.js.map +1 -1
  4. package/dist/commands/env.d.ts +2 -0
  5. package/dist/commands/env.d.ts.map +1 -1
  6. package/dist/commands/env.js +7 -3
  7. package/dist/commands/env.js.map +1 -1
  8. package/dist/commands/login.d.ts +1 -0
  9. package/dist/commands/login.d.ts.map +1 -1
  10. package/dist/commands/login.js +20 -4
  11. package/dist/commands/login.js.map +1 -1
  12. package/dist/commands/machine.d.ts +28 -1
  13. package/dist/commands/machine.d.ts.map +1 -1
  14. package/dist/commands/machine.js +529 -4
  15. package/dist/commands/machine.js.map +1 -1
  16. package/dist/commands/project.d.ts.map +1 -1
  17. package/dist/commands/project.js +862 -17
  18. package/dist/commands/project.js.map +1 -1
  19. package/dist/commands/prompts.d.ts +19 -0
  20. package/dist/commands/prompts.d.ts.map +1 -1
  21. package/dist/commands/prompts.js +304 -1
  22. package/dist/commands/prompts.js.map +1 -1
  23. package/dist/commands/pull.d.ts.map +1 -1
  24. package/dist/commands/pull.js +28 -8
  25. package/dist/commands/pull.js.map +1 -1
  26. package/dist/commands/push.d.ts +3 -1
  27. package/dist/commands/push.d.ts.map +1 -1
  28. package/dist/commands/push.js +26 -12
  29. package/dist/commands/push.js.map +1 -1
  30. package/dist/commands/skill.d.ts.map +1 -1
  31. package/dist/commands/skill.js +19 -15
  32. package/dist/commands/skill.js.map +1 -1
  33. package/dist/commands/stack.d.ts.map +1 -1
  34. package/dist/commands/stack.js +14 -17
  35. package/dist/commands/stack.js.map +1 -1
  36. package/dist/commands/status.d.ts.map +1 -1
  37. package/dist/commands/status.js +31 -0
  38. package/dist/commands/status.js.map +1 -1
  39. package/dist/commands/sync.d.ts +1 -0
  40. package/dist/commands/sync.d.ts.map +1 -1
  41. package/dist/commands/sync.js +51 -2
  42. package/dist/commands/sync.js.map +1 -1
  43. package/dist/index.js +119 -29
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib/api.d.ts +324 -0
  46. package/dist/lib/api.d.ts.map +1 -1
  47. package/dist/lib/api.js +68 -0
  48. package/dist/lib/api.js.map +1 -1
  49. package/dist/lib/daemon.d.ts +23 -0
  50. package/dist/lib/daemon.d.ts.map +1 -0
  51. package/dist/lib/daemon.js +166 -0
  52. package/dist/lib/daemon.js.map +1 -0
  53. package/dist/lib/first-run.d.ts +1 -1
  54. package/dist/lib/first-run.d.ts.map +1 -1
  55. package/dist/lib/first-run.js +8 -0
  56. package/dist/lib/first-run.js.map +1 -1
  57. package/dist/lib/machine-bootstrap-prompt.d.ts +17 -0
  58. package/dist/lib/machine-bootstrap-prompt.d.ts.map +1 -0
  59. package/dist/lib/machine-bootstrap-prompt.js +305 -0
  60. package/dist/lib/machine-bootstrap-prompt.js.map +1 -0
  61. package/dist/lib/machine-projects.d.ts +71 -0
  62. package/dist/lib/machine-projects.d.ts.map +1 -0
  63. package/dist/lib/machine-projects.js +510 -0
  64. package/dist/lib/machine-projects.js.map +1 -0
  65. package/dist/lib/machine-verify.d.ts +175 -0
  66. package/dist/lib/machine-verify.d.ts.map +1 -0
  67. package/dist/lib/machine-verify.js +684 -0
  68. package/dist/lib/machine-verify.js.map +1 -0
  69. package/dist/lib/portfolio-audit.d.ts +34 -0
  70. package/dist/lib/portfolio-audit.d.ts.map +1 -0
  71. package/dist/lib/portfolio-audit.js +254 -0
  72. package/dist/lib/portfolio-audit.js.map +1 -0
  73. package/dist/lib/portfolio-graph.d.ts +104 -0
  74. package/dist/lib/portfolio-graph.d.ts.map +1 -0
  75. package/dist/lib/portfolio-graph.js +281 -0
  76. package/dist/lib/portfolio-graph.js.map +1 -0
  77. package/dist/lib/project-strategy.d.ts +45 -0
  78. package/dist/lib/project-strategy.d.ts.map +1 -0
  79. package/dist/lib/project-strategy.js +234 -0
  80. package/dist/lib/project-strategy.js.map +1 -0
  81. package/dist/lib/reusable-patterns.d.ts +30 -0
  82. package/dist/lib/reusable-patterns.d.ts.map +1 -0
  83. package/dist/lib/reusable-patterns.js +375 -0
  84. package/dist/lib/reusable-patterns.js.map +1 -0
  85. package/dist/lib/skill-catalog.d.ts.map +1 -1
  86. package/dist/lib/skill-catalog.js +20 -0
  87. package/dist/lib/skill-catalog.js.map +1 -1
  88. package/dist/lib/skills.d.ts +1 -0
  89. package/dist/lib/skills.d.ts.map +1 -1
  90. package/dist/lib/skills.js +43 -13
  91. package/dist/lib/skills.js.map +1 -1
  92. package/dist/lib/version.d.ts +2 -0
  93. package/dist/lib/version.d.ts.map +1 -0
  94. package/dist/lib/version.js +63 -0
  95. package/dist/lib/version.js.map +1 -0
  96. package/dist/mcp/registry.d.ts +5 -1
  97. package/dist/mcp/registry.d.ts.map +1 -1
  98. package/dist/mcp/registry.js +426 -2
  99. package/dist/mcp/registry.js.map +1 -1
  100. package/dist/mcp/server.d.ts +2 -0
  101. package/dist/mcp/server.d.ts.map +1 -1
  102. package/dist/mcp/server.js +50 -5
  103. package/dist/mcp/server.js.map +1 -1
  104. package/package.json +1 -1
  105. package/scripts/env-vault/README.md +12 -2
  106. package/scripts/env-vault/backup.sh +24 -1
  107. package/scripts/env-vault/restore.sh +45 -3
  108. package/scripts/skillstack-sync/README.md +13 -6
  109. package/scripts/skillstack-sync/capture-agent-config.sh +1 -0
  110. package/scripts/skillstack-sync/com.youmd.context-sync.plist +38 -0
  111. package/scripts/skillstack-sync/com.youmd.identity-sync.plist +3 -1
  112. package/scripts/skillstack-sync/com.youmd.skillstack-sync.plist +1 -1
  113. package/scripts/skillstack-sync/context-sync.sh +69 -1
  114. package/scripts/skillstack-sync/install-daemons.sh +7 -3
  115. package/skills/machine-bootstrap.md +243 -0
@@ -28,6 +28,9 @@ top of `backup.sh` and add the matching entry to `AGENT_AUTH_MAP` in
28
28
  # via CLI (preferred — ships in the npm package)
29
29
  youmd env backup
30
30
 
31
+ # Check local encryption tooling and env discovery without writing a vault:
32
+ youmd env backup --preflight
33
+
31
34
  # or directly via bash (from the youmd repo root)
32
35
  cd /Users/houstongolden/Desktop/CODE_2025/youmd
33
36
  bash cli/scripts/env-vault/backup.sh
@@ -52,6 +55,7 @@ Do NOT transfer via unencrypted email or public Slack.
52
55
 
53
56
  ```bash
54
57
  # Install dependencies first (e.g. Node, pnpm, etc.), then:
58
+ youmd env restore /path/to/env-vault-<date>.tar.<ext> --list
55
59
  youmd env restore /path/to/env-vault-<date>.tar.<ext>
56
60
 
57
61
  # If files already exist on the new Mac:
@@ -63,10 +67,12 @@ bash cli/scripts/env-vault/restore.sh --force /path/to/env-vault-<date>.tar.<ext
63
67
  ```
64
68
 
65
69
  The script:
66
- 1. Creates `<CODE_2025>/<project>/.env.local` for each project.
67
- 2. Restores agent-auth files to their absolute home paths (`~/.codex/auth.json`, `~/.cursor/mcp.json`, `~/.claude.json`), creating parent directories as needed.
70
+ 1. Lists vault contents without writing files when `--list` is provided.
71
+ 2. Creates `<CODE_2025>/<project>/.env.local` for each project during restore.
72
+ 3. Restores agent-auth files to their absolute home paths (`~/.codex/auth.json`, `~/.cursor/mcp.json`, `~/.claude.json`), creating parent directories as needed.
68
73
 
69
74
  Existing files are backed up to `<path>.bak.<timestamp>` before overwrite (or silently overwritten with `--force`).
75
+ List mode prints project env target paths, variable names/counts, and agent-auth file presence only. It never prints secret values.
70
76
 
71
77
  ---
72
78
 
@@ -101,10 +107,12 @@ and are exposed as first-class CLI commands:
101
107
 
102
108
  ```bash
103
109
  youmd env backup # back up all .env.local files
110
+ youmd env backup --preflight # verify tooling/discovery without writing
104
111
  youmd env backup --root <dir> # custom search root
105
112
  youmd env backup --out <dir> # custom output dir
106
113
 
107
114
  youmd env restore <vault> # restore from an encrypted vault
115
+ youmd env restore <vault> --list # list vault contents without writing
108
116
  youmd env restore --force # overwrite existing .env.local files
109
117
  youmd env restore --root <dir> # restore into a custom root
110
118
  ```
@@ -121,6 +129,7 @@ your terminal unchanged.
121
129
 
122
130
  | Flag | Default | Description |
123
131
  |------|---------|-------------|
132
+ | `--preflight` | off | Verify encryption tooling/discovery without writing a vault |
124
133
  | `--root <dir>` | `~/Desktop/CODE_2025` | Where to discover `.env.local` files |
125
134
  | `--out <dir>` | `youmd/.env-vault/` | Where to write encrypted vault + manifest |
126
135
 
@@ -128,6 +137,7 @@ your terminal unchanged.
128
137
 
129
138
  | Flag | Default | Description |
130
139
  |------|---------|-------------|
140
+ | `--list` | off | List vault contents without writing files |
131
141
  | `--root <dir>` | `~/Desktop/CODE_2025` | Where to restore project `.env.local` files |
132
142
  | `--force` | off | Overwrite existing `.env.local` instead of skipping |
133
143
 
@@ -4,7 +4,7 @@
4
4
  # echoes, logs, or writes any secret VALUE. Only variable NAMES, counts, and
5
5
  # file paths appear in plaintext output.
6
6
  #
7
- # Usage: bash backup.sh [--root <search-root>] [--out <output-dir>]
7
+ # Usage: bash backup.sh [--preflight] [--root <search-root>] [--out <output-dir>]
8
8
  # Defaults:
9
9
  # search-root: /Users/houstongolden/Desktop/CODE_2025
10
10
  # output-dir: /Users/houstongolden/Desktop/CODE_2025/youmd/.env-vault
@@ -14,6 +14,7 @@ set -euo pipefail
14
14
  # ── configurable defaults ──────────────────────────────────────────────────────
15
15
  SEARCH_ROOT="${ENV_VAULT_ROOT:-/Users/houstongolden/Desktop/CODE_2025}"
16
16
  OUTPUT_DIR="${ENV_VAULT_OUT:-/Users/houstongolden/Desktop/CODE_2025/youmd/.env-vault}"
17
+ PREFLIGHT=false
17
18
 
18
19
  # ── agent-auth secret files ────────────────────────────────────────────────────
19
20
  # Absolute paths to hard agent-auth secrets that must travel with the vault.
@@ -28,6 +29,7 @@ AGENT_SECRET_FILES=(
28
29
  # ── parse flags ────────────────────────────────────────────────────────────────
29
30
  while [[ $# -gt 0 ]]; do
30
31
  case $1 in
32
+ --preflight) PREFLIGHT=true; shift ;;
31
33
  --root) SEARCH_ROOT="$2"; shift 2 ;;
32
34
  --out) OUTPUT_DIR="$2"; shift 2 ;;
33
35
  *) echo "Unknown flag: $1" >&2; exit 1 ;;
@@ -66,11 +68,32 @@ done < <(find "${SEARCH_ROOT}" -maxdepth 2 -name ".env.local" -print0 2>/dev/nul
66
68
 
67
69
  if [[ ${#ENV_FILES[@]} -eq 0 ]]; then
68
70
  echo "No .env.local files found under ${SEARCH_ROOT}" >&2
71
+ if [[ "${PREFLIGHT}" == "true" ]]; then
72
+ echo "Preflight only: encryption tooling is available, but no .env.local files were found."
73
+ exit 0
74
+ fi
69
75
  exit 1
70
76
  fi
71
77
 
72
78
  echo "Found ${#ENV_FILES[@]} .env.local file(s)."
73
79
 
80
+ if [[ "${PREFLIGHT}" == "true" ]]; then
81
+ echo "Output dir: ${OUTPUT_DIR}"
82
+ echo "Agent-auth candidates:"
83
+ for ENTRY in "${AGENT_SECRET_FILES[@]}"; do
84
+ SRC_PATH="${ENTRY%%:*}"
85
+ ARCHIVE_NAME="${ENTRY##*:}"
86
+ if [[ -f "${SRC_PATH}" ]]; then
87
+ FILE_BYTES=$(wc -c < "${SRC_PATH}" | tr -d ' ')
88
+ echo " present: agent-auth/${ARCHIVE_NAME} (${FILE_BYTES} bytes)"
89
+ else
90
+ echo " missing: agent-auth/${ARCHIVE_NAME}"
91
+ fi
92
+ done
93
+ echo "Preflight only: no vault written and no secret values printed."
94
+ exit 0
95
+ fi
96
+
74
97
  # ── build plaintext manifest (names + counts, NO values) ─────────────────────
75
98
  mkdir -p "${OUTPUT_DIR}"
76
99
  UTC_DATE="$(date -u +%Y-%m-%dT%H%MZ)"
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env bash
2
2
  # env-vault/restore.sh — restores .env.local files AND agent-auth secrets from
3
3
  # an encrypted vault created by backup.sh. NEVER prints, echoes, or logs any
4
- # secret VALUES. Only prints which file paths were restored or skipped.
4
+ # secret VALUES. Restore prints which file paths were restored or skipped; list
5
+ # mode prints target paths plus variable names/counts only.
5
6
  #
6
- # Usage: bash restore.sh [--force] [--root <target-root>] <vault-file>
7
+ # Usage: bash restore.sh [--list] [--force] [--root <target-root>] <vault-file>
7
8
  # vault-file: path to env-vault-*.tar.age / .tar.gpg / .tar.enc
8
9
  # --force: overwrite existing files without backing them up
9
10
  # --root: destination root for .env.local files
@@ -29,11 +30,13 @@ AGENT_AUTH_MAP=(
29
30
  # ── defaults ──────────────────────────────────────────────────────────────────
30
31
  TARGET_ROOT="${ENV_VAULT_RESTORE_ROOT:-/Users/houstongolden/Desktop/CODE_2025}"
31
32
  FORCE=false
33
+ LIST_ONLY=false
32
34
  VAULT_FILE=""
33
35
 
34
36
  # ── parse args ─────────────────────────────────────────────────────────────────
35
37
  while [[ $# -gt 0 ]]; do
36
38
  case $1 in
39
+ --list) LIST_ONLY=true; shift ;;
37
40
  --force) FORCE=true; shift ;;
38
41
  --root) TARGET_ROOT="$2"; shift 2 ;;
39
42
  -*) echo "Unknown flag: $1" >&2; exit 1 ;;
@@ -49,7 +52,7 @@ while [[ $# -gt 0 ]]; do
49
52
  done
50
53
 
51
54
  if [[ -z "${VAULT_FILE}" ]]; then
52
- echo "Usage: bash restore.sh [--force] [--root <path>] <vault-file>" >&2
55
+ echo "Usage: bash restore.sh [--list] [--force] [--root <path>] <vault-file>" >&2
53
56
  echo " vault-file: env-vault-*.tar.age | .tar.gpg | .tar.enc" >&2
54
57
  exit 1
55
58
  fi
@@ -119,6 +122,45 @@ EXTRACT_DIR="${WORK_DIR}/extracted"
119
122
  mkdir -p "${EXTRACT_DIR}"
120
123
  tar -xf "${TAR_PATH}" -C "${EXTRACT_DIR}"
121
124
 
125
+ if [[ "${LIST_ONLY}" == "true" ]]; then
126
+ ENV_COUNT=0
127
+ AGENT_PRESENT=0
128
+ AGENT_MISSING=0
129
+
130
+ echo "Vault contents (secret-safe inventory):"
131
+ while IFS= read -r -d '' ENV_FILE; do
132
+ REL_PATH="${ENV_FILE#${EXTRACT_DIR}/}"
133
+ PROJECT_NAME="$(dirname "${REL_PATH}")"
134
+ DEST="${TARGET_ROOT}/${PROJECT_NAME}/.env.local"
135
+ VAR_COUNT=$(grep -cE '^[A-Za-z_][A-Za-z0-9_]*=' "${ENV_FILE}" 2>/dev/null || true)
136
+ VAR_NAMES=$(grep -oE '^[A-Za-z_][A-Za-z0-9_]*' "${ENV_FILE}" 2>/dev/null \
137
+ | grep -v '^$' | sort | paste -sd ',' - || true)
138
+ echo " env: ${PROJECT_NAME}/.env.local -> ${DEST} (${VAR_COUNT} variable(s) [${VAR_NAMES}])"
139
+ (( ENV_COUNT++ )) || true
140
+ done < <(find "${EXTRACT_DIR}" -name ".env.local" -print0)
141
+
142
+ AGENT_AUTH_SRC="${EXTRACT_DIR}/agent-auth"
143
+ echo " agent-auth:"
144
+ for ENTRY in "${AGENT_AUTH_MAP[@]}"; do
145
+ ARCHIVE_NAME="${ENTRY%%:*}"
146
+ DEST_PATH="${ENTRY##*:}"
147
+ SRC_FILE="${AGENT_AUTH_SRC}/${ARCHIVE_NAME}"
148
+ if [[ -f "${SRC_FILE}" ]]; then
149
+ FILE_BYTES=$(wc -c < "${SRC_FILE}" | tr -d ' ')
150
+ echo " present: agent-auth/${ARCHIVE_NAME} -> ${DEST_PATH} (${FILE_BYTES} bytes)"
151
+ (( AGENT_PRESENT++ )) || true
152
+ else
153
+ echo " missing: agent-auth/${ARCHIVE_NAME}"
154
+ (( AGENT_MISSING++ )) || true
155
+ fi
156
+ done
157
+
158
+ echo ""
159
+ echo "List summary: ${ENV_COUNT} .env.local file(s), ${AGENT_PRESENT} agent-auth file(s) present, ${AGENT_MISSING} agent-auth file(s) missing."
160
+ echo "No files were written and no secret values were printed."
161
+ exit 0
162
+ fi
163
+
122
164
  # ── restore each .env.local ───────────────────────────────────────────────────
123
165
  RESTORED=0
124
166
  SKIPPED=0
@@ -28,7 +28,8 @@ Cross-machine sync toolkit for Houston Golden's agent skill/stack setup.
28
28
 
29
29
  ### Complementary sync planes
30
30
 
31
- - **Identity plane:** `youmd sync` (Convex ↔ local profile, memories, preferences). Automated via `com.youmd.identity-sync` daemon.
31
+ - **Identity plane:** `youmd sync --daemon` (Convex ↔ local profile, memories, preferences). Automated via `com.youmd.identity-sync` daemon. Daemon mode refreshes local files/skills and skips unsafe lossy pushes instead of forcing over richer server data.
32
+ - **Project-context plane:** `youmd stack context-sync` safely commits and syncs only `AGENTS.md`, `CLAUDE.md`, `project-context/`, and `.claude/` in curated Houston-owned repos. It refuses to merge remote app-code changes.
32
33
  - **Env-vault plane:** `cli/scripts/env-vault/restore.sh` for secrets (manual, invoked on new machine); also available as `youmd env restore <vault>`.
33
34
 
34
35
  ---
@@ -38,10 +39,11 @@ Cross-machine sync toolkit for Houston Golden's agent skill/stack setup.
38
39
  ```
39
40
  cli/scripts/skillstack-sync/
40
41
  ├── sync.sh Core syncer (bash 3.2 compatible)
41
- ├── install-daemons.sh Installs both LaunchAgents
42
+ ├── install-daemons.sh Installs all resident LaunchAgents
42
43
  ├── bootstrap-new-mac.sh New machine setup script
43
44
  ├── com.youmd.skillstack-sync.plist LaunchAgent: runs `youmd stack sync` every 5 min
44
- ├── com.youmd.identity-sync.plist LaunchAgent: runs `youmd sync` every 5 min
45
+ ├── com.youmd.identity-sync.plist LaunchAgent: runs `youmd sync --daemon` every 5 min
46
+ ├── com.youmd.context-sync.plist LaunchAgent: runs `youmd stack context-sync` every 15 min
45
47
  └── README.md This file
46
48
  ```
47
49
 
@@ -90,7 +92,7 @@ GIT_USER_EMAIL="houston@bamf.ai" # commit email (default)
90
92
  The preferred way is via the CLI:
91
93
 
92
94
  ```bash
93
- youmd stack daemon install # installs both LaunchAgents
95
+ youmd stack daemon install # installs all resident LaunchAgents
94
96
  youmd stack daemon status # check loaded/not-loaded
95
97
  youmd stack daemon uninstall # remove plists from ~/Library/LaunchAgents/
96
98
  ```
@@ -101,9 +103,10 @@ Or run the raw script directly:
101
103
  bash cli/scripts/skillstack-sync/install-daemons.sh
102
104
  ```
103
105
 
104
- Installs two LaunchAgents that fire every 300 seconds (5 minutes):
106
+ Installs three LaunchAgents:
105
107
  - `com.youmd.skillstack-sync` → runs `youmd stack sync`
106
- - `com.youmd.identity-sync` → runs `youmd sync`
108
+ - `com.youmd.identity-sync` → runs `youmd sync --daemon`
109
+ - `com.youmd.context-sync` → runs `youmd stack context-sync`
107
110
 
108
111
  Check status:
109
112
  ```bash
@@ -111,6 +114,7 @@ youmd stack daemon status
111
114
  # or directly:
112
115
  launchctl list com.youmd.skillstack-sync
113
116
  launchctl list com.youmd.identity-sync
117
+ launchctl list com.youmd.context-sync
114
118
  ```
115
119
 
116
120
  Check logs:
@@ -118,6 +122,7 @@ Check logs:
118
122
  tail -f ~/.youmd/logs/skillstack-sync.log
119
123
  tail -f ~/.youmd/logs/skillstack-sync.out.log
120
124
  tail -f ~/.youmd/logs/identity-sync.out.log
125
+ tail -f ~/.youmd/logs/context-sync.log
121
126
  ```
122
127
 
123
128
  ### Uninstall daemons
@@ -130,8 +135,10 @@ Or manually:
130
135
  ```bash
131
136
  launchctl unload ~/Library/LaunchAgents/com.youmd.skillstack-sync.plist
132
137
  launchctl unload ~/Library/LaunchAgents/com.youmd.identity-sync.plist
138
+ launchctl unload ~/Library/LaunchAgents/com.youmd.context-sync.plist
133
139
  rm ~/Library/LaunchAgents/com.youmd.skillstack-sync.plist
134
140
  rm ~/Library/LaunchAgents/com.youmd.identity-sync.plist
141
+ rm ~/Library/LaunchAgents/com.youmd.context-sync.plist
135
142
  ```
136
143
 
137
144
  ---
@@ -85,6 +85,7 @@ capture_plist() {
85
85
  }
86
86
  capture_plist "com.youmd.skillstack-sync"
87
87
  capture_plist "com.youmd.identity-sync"
88
+ capture_plist "com.youmd.context-sync"
88
89
  capture_plist "com.houstongolden.agent-runtime-guard"
89
90
 
90
91
  if ! is_dry; then
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.youmd.context-sync</string>
7
+
8
+ <key>ProgramArguments</key>
9
+ <array>
10
+ <string>/bin/zsh</string>
11
+ <string>-lc</string>
12
+ <string>youmd stack context-sync</string>
13
+ </array>
14
+
15
+ <key>StartInterval</key>
16
+ <integer>900</integer>
17
+
18
+ <key>RunAtLoad</key>
19
+ <true/>
20
+
21
+ <key>KeepAlive</key>
22
+ <false/>
23
+
24
+ <key>StandardOutPath</key>
25
+ <string>__HOME__/.youmd/logs/context-sync.out.log</string>
26
+
27
+ <key>StandardErrorPath</key>
28
+ <string>__HOME__/.youmd/logs/context-sync.err.log</string>
29
+
30
+ <key>EnvironmentVariables</key>
31
+ <dict>
32
+ <key>HOME</key>
33
+ <string>__HOME__</string>
34
+ <key>PATH</key>
35
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
36
+ </dict>
37
+ </dict>
38
+ </plist>
@@ -9,7 +9,7 @@
9
9
  <array>
10
10
  <string>/bin/zsh</string>
11
11
  <string>-lc</string>
12
- <string>youmd sync</string>
12
+ <string>youmd sync --daemon</string>
13
13
  </array>
14
14
 
15
15
  <key>StartInterval</key>
@@ -31,6 +31,8 @@
31
31
  <dict>
32
32
  <key>HOME</key>
33
33
  <string>__HOME__</string>
34
+ <key>PATH</key>
35
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
34
36
  </dict>
35
37
  </dict>
36
38
  </plist>
@@ -32,7 +32,7 @@
32
32
  <key>HOME</key>
33
33
  <string>__HOME__</string>
34
34
  <key>PATH</key>
35
- <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
35
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
36
36
  </dict>
37
37
  </dict>
38
38
  </plist>
@@ -146,6 +146,49 @@ existing_context_paths() {
146
146
  echo "${found}"
147
147
  }
148
148
 
149
+ path_is_context() {
150
+ local changed="$1"
151
+ local p
152
+ for p in ${CONTEXT_PATHSPECS}; do
153
+ if [ "${changed}" = "${p}" ]; then
154
+ return 0
155
+ fi
156
+ case "${changed}" in
157
+ "${p}/"*) return 0 ;;
158
+ esac
159
+ done
160
+ return 1
161
+ }
162
+
163
+ remote_has_non_context_changes() {
164
+ local repo="$1" repo_name="$2" upstream="$3"
165
+ local changed non_context line
166
+ changed="$(git -C "${repo}" diff --name-only "HEAD..${upstream}" 2>/dev/null || true)"
167
+ if [ -z "${changed}" ]; then
168
+ return 1
169
+ fi
170
+
171
+ non_context=""
172
+ while IFS= read -r line; do
173
+ [ -z "${line}" ] && continue
174
+ if ! path_is_context "${line}"; then
175
+ if [ -z "${non_context}" ]; then
176
+ non_context="${line}"
177
+ else
178
+ non_context="${non_context}, ${line}"
179
+ fi
180
+ fi
181
+ done <<EOF
182
+ ${changed}
183
+ EOF
184
+
185
+ if [ -n "${non_context}" ]; then
186
+ log_warn "${repo_name}: upstream has non-context changes (${non_context}). Skipping pull/push so context-sync never merges application code."
187
+ return 0
188
+ fi
189
+ return 1
190
+ }
191
+
149
192
  # ---------------------------------------------------------------------------
150
193
  # SYNC ONE REPO
151
194
  # Returns 0 on success, 1 on non-fatal failure.
@@ -263,8 +306,33 @@ sync_context_repo() {
263
306
 
264
307
  # ---- Pull (merge, no rebase) ---------------------------------------------
265
308
  if is_dry; then
266
- log_dry "Would git pull --no-rebase --no-edit in ${repo_name}"
309
+ log_dry "Would fetch upstream and pull only if remote changes are limited to context paths in ${repo_name}"
267
310
  else
311
+ local upstream
312
+ upstream="$(git -C "${repo}" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)"
313
+ if [ -z "${upstream}" ]; then
314
+ log_warn "Skipping pull/push for ${repo_name}: no upstream branch configured."
315
+ return 0
316
+ fi
317
+
318
+ local fetch_log
319
+ fetch_log="$(mktemp -t context_sync_fetch)"
320
+ local fetch_ok=0
321
+ git -C "${repo}" fetch --quiet >"${fetch_log}" 2>&1 || fetch_ok=$?
322
+ if [ "${fetch_ok}" -ne 0 ]; then
323
+ while IFS= read -r line; do
324
+ log "fetch|${repo_name}: ${line}"
325
+ done < "${fetch_log}"
326
+ rm -f "${fetch_log}"
327
+ log_error "Fetch FAILED for ${repo_name}. Skipping pull/push."
328
+ return 1
329
+ fi
330
+ rm -f "${fetch_log}"
331
+
332
+ if remote_has_non_context_changes "${repo}" "${repo_name}" "${upstream}"; then
333
+ return 0
334
+ fi
335
+
268
336
  local pull_log
269
337
  # BSD mktemp: no -p, use /tmp explicitly
270
338
  pull_log="$(mktemp -t context_sync_pull)"
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # install-daemons.sh — install skillstack-sync and identity-sync LaunchAgents
2
+ # install-daemons.sh — install resident You.md sync LaunchAgents
3
3
  #
4
4
  # Idempotent: safe to re-run. Unloads before reloading.
5
5
  # Does NOT need sudo — LaunchAgents run as the current user.
@@ -12,7 +12,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
12
  LAUNCH_AGENTS_DIR="${HOME}/Library/LaunchAgents"
13
13
  LOG_DIR="${HOME}/.youmd/logs"
14
14
 
15
- PLISTS="com.youmd.skillstack-sync com.youmd.identity-sync"
15
+ PLISTS="com.youmd.skillstack-sync com.youmd.identity-sync com.youmd.context-sync"
16
16
 
17
17
  echo "==> Creating log directory: ${LOG_DIR}"
18
18
  mkdir -p "${LOG_DIR}"
@@ -55,7 +55,7 @@ done
55
55
 
56
56
  echo ""
57
57
  echo "================================================================"
58
- echo "Both daemons installed and running."
58
+ echo "Resident You.md daemons installed and running."
59
59
  echo ""
60
60
  echo "Log files:"
61
61
  echo " ${LOG_DIR}/skillstack-sync.log (combined sync log)"
@@ -63,10 +63,14 @@ echo " ${LOG_DIR}/skillstack-sync.out.log (daemon stdout)"
63
63
  echo " ${LOG_DIR}/skillstack-sync.err.log (daemon stderr)"
64
64
  echo " ${LOG_DIR}/identity-sync.out.log (youmd sync stdout)"
65
65
  echo " ${LOG_DIR}/identity-sync.err.log (youmd sync stderr)"
66
+ echo " ${LOG_DIR}/context-sync.log (combined project-context sync log)"
67
+ echo " ${LOG_DIR}/context-sync.out.log (daemon stdout)"
68
+ echo " ${LOG_DIR}/context-sync.err.log (daemon stderr)"
66
69
  echo ""
67
70
  echo "Check daemon status:"
68
71
  echo " launchctl list com.youmd.skillstack-sync"
69
72
  echo " launchctl list com.youmd.identity-sync"
73
+ echo " launchctl list com.youmd.context-sync"
70
74
  echo ""
71
75
  echo "To uninstall:"
72
76
  for label in ${PLISTS}; do