jfl 0.1.0 → 0.2.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.
Files changed (262) hide show
  1. package/README.md +443 -145
  2. package/clawdbot-plugin/clawdbot.plugin.json +20 -0
  3. package/clawdbot-plugin/index.js +555 -0
  4. package/clawdbot-plugin/index.ts +582 -0
  5. package/clawdbot-skill/SKILL.md +33 -336
  6. package/clawdbot-skill/index.ts +491 -321
  7. package/clawdbot-skill/skill.json +4 -13
  8. package/dist/commands/clawdbot.d.ts +11 -0
  9. package/dist/commands/clawdbot.d.ts.map +1 -0
  10. package/dist/commands/clawdbot.js +215 -0
  11. package/dist/commands/clawdbot.js.map +1 -0
  12. package/dist/commands/context-hub.d.ts +5 -0
  13. package/dist/commands/context-hub.d.ts.map +1 -1
  14. package/dist/commands/context-hub.js +394 -28
  15. package/dist/commands/context-hub.js.map +1 -1
  16. package/dist/commands/gtm-process-update.d.ts +10 -0
  17. package/dist/commands/gtm-process-update.d.ts.map +1 -0
  18. package/dist/commands/gtm-process-update.js +101 -0
  19. package/dist/commands/gtm-process-update.js.map +1 -0
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +278 -4
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/login.d.ts.map +1 -1
  24. package/dist/commands/login.js +32 -33
  25. package/dist/commands/login.js.map +1 -1
  26. package/dist/commands/memory.d.ts +38 -0
  27. package/dist/commands/memory.d.ts.map +1 -0
  28. package/dist/commands/memory.js +229 -0
  29. package/dist/commands/memory.js.map +1 -0
  30. package/dist/commands/migrate-services.d.ts +8 -0
  31. package/dist/commands/migrate-services.d.ts.map +1 -0
  32. package/dist/commands/migrate-services.js +182 -0
  33. package/dist/commands/migrate-services.js.map +1 -0
  34. package/dist/commands/onboard.d.ts +24 -0
  35. package/dist/commands/onboard.d.ts.map +1 -0
  36. package/dist/commands/onboard.js +663 -0
  37. package/dist/commands/onboard.js.map +1 -0
  38. package/dist/commands/openclaw.d.ts +56 -0
  39. package/dist/commands/openclaw.d.ts.map +1 -0
  40. package/dist/commands/openclaw.js +700 -0
  41. package/dist/commands/openclaw.js.map +1 -0
  42. package/dist/commands/orchestrate.d.ts +14 -0
  43. package/dist/commands/orchestrate.d.ts.map +1 -0
  44. package/dist/commands/orchestrate.js +270 -0
  45. package/dist/commands/orchestrate.js.map +1 -0
  46. package/dist/commands/profile.d.ts +46 -0
  47. package/dist/commands/profile.d.ts.map +1 -0
  48. package/dist/commands/profile.js +498 -0
  49. package/dist/commands/profile.js.map +1 -0
  50. package/dist/commands/repair.d.ts.map +1 -1
  51. package/dist/commands/repair.js +37 -0
  52. package/dist/commands/repair.js.map +1 -1
  53. package/dist/commands/service-agent.d.ts +16 -0
  54. package/dist/commands/service-agent.d.ts.map +1 -0
  55. package/dist/commands/service-agent.js +375 -0
  56. package/dist/commands/service-agent.js.map +1 -0
  57. package/dist/commands/service-manager.d.ts +12 -0
  58. package/dist/commands/service-manager.d.ts.map +1 -0
  59. package/dist/commands/service-manager.js +967 -0
  60. package/dist/commands/service-manager.js.map +1 -0
  61. package/dist/commands/service-validate.d.ts +12 -0
  62. package/dist/commands/service-validate.d.ts.map +1 -0
  63. package/dist/commands/service-validate.js +611 -0
  64. package/dist/commands/service-validate.js.map +1 -0
  65. package/dist/commands/services-create.d.ts +15 -0
  66. package/dist/commands/services-create.d.ts.map +1 -0
  67. package/dist/commands/services-create.js +1452 -0
  68. package/dist/commands/services-create.js.map +1 -0
  69. package/dist/commands/services-scan.d.ts +13 -0
  70. package/dist/commands/services-scan.d.ts.map +1 -0
  71. package/dist/commands/services-scan.js +251 -0
  72. package/dist/commands/services-scan.js.map +1 -0
  73. package/dist/commands/services-sync-agents.d.ts +23 -0
  74. package/dist/commands/services-sync-agents.d.ts.map +1 -0
  75. package/dist/commands/services-sync-agents.js +207 -0
  76. package/dist/commands/services-sync-agents.js.map +1 -0
  77. package/dist/commands/services.d.ts +19 -0
  78. package/dist/commands/services.d.ts.map +1 -0
  79. package/dist/commands/services.js +742 -0
  80. package/dist/commands/services.js.map +1 -0
  81. package/dist/commands/session.d.ts +5 -1
  82. package/dist/commands/session.d.ts.map +1 -1
  83. package/dist/commands/session.js +68 -586
  84. package/dist/commands/session.js.map +1 -1
  85. package/dist/commands/status.d.ts.map +1 -1
  86. package/dist/commands/status.js +17 -0
  87. package/dist/commands/status.js.map +1 -1
  88. package/dist/commands/update.d.ts.map +1 -1
  89. package/dist/commands/update.js +75 -21
  90. package/dist/commands/update.js.map +1 -1
  91. package/dist/commands/validate-settings.d.ts +37 -0
  92. package/dist/commands/validate-settings.d.ts.map +1 -0
  93. package/dist/commands/validate-settings.js +197 -0
  94. package/dist/commands/validate-settings.js.map +1 -0
  95. package/dist/commands/voice.d.ts +0 -1
  96. package/dist/commands/voice.d.ts.map +1 -1
  97. package/dist/commands/voice.js +16 -15
  98. package/dist/commands/voice.js.map +1 -1
  99. package/dist/index.js +395 -141
  100. package/dist/index.js.map +1 -1
  101. package/dist/lib/agent-generator.d.ts +26 -0
  102. package/dist/lib/agent-generator.d.ts.map +1 -0
  103. package/dist/lib/agent-generator.js +331 -0
  104. package/dist/lib/agent-generator.js.map +1 -0
  105. package/dist/lib/memory-db.d.ts +102 -0
  106. package/dist/lib/memory-db.d.ts.map +1 -0
  107. package/dist/lib/memory-db.js +313 -0
  108. package/dist/lib/memory-db.js.map +1 -0
  109. package/dist/lib/memory-indexer.d.ts +47 -0
  110. package/dist/lib/memory-indexer.d.ts.map +1 -0
  111. package/dist/lib/memory-indexer.js +215 -0
  112. package/dist/lib/memory-indexer.js.map +1 -0
  113. package/dist/lib/memory-search.d.ts +41 -0
  114. package/dist/lib/memory-search.d.ts.map +1 -0
  115. package/dist/lib/memory-search.js +246 -0
  116. package/dist/lib/memory-search.js.map +1 -0
  117. package/dist/lib/openclaw-registry.d.ts +48 -0
  118. package/dist/lib/openclaw-registry.d.ts.map +1 -0
  119. package/dist/lib/openclaw-registry.js +181 -0
  120. package/dist/lib/openclaw-registry.js.map +1 -0
  121. package/dist/lib/openclaw-sdk.d.ts +107 -0
  122. package/dist/lib/openclaw-sdk.d.ts.map +1 -0
  123. package/dist/lib/openclaw-sdk.js +208 -0
  124. package/dist/lib/openclaw-sdk.js.map +1 -0
  125. package/dist/lib/peer-agent-generator.d.ts +44 -0
  126. package/dist/lib/peer-agent-generator.d.ts.map +1 -0
  127. package/dist/lib/peer-agent-generator.js +286 -0
  128. package/dist/lib/peer-agent-generator.js.map +1 -0
  129. package/dist/lib/service-dependencies.d.ts +44 -0
  130. package/dist/lib/service-dependencies.d.ts.map +1 -0
  131. package/dist/lib/service-dependencies.js +314 -0
  132. package/dist/lib/service-dependencies.js.map +1 -0
  133. package/dist/lib/service-detector.d.ts +61 -0
  134. package/dist/lib/service-detector.d.ts.map +1 -0
  135. package/dist/lib/service-detector.js +521 -0
  136. package/dist/lib/service-detector.js.map +1 -0
  137. package/dist/lib/service-gtm.d.ts +157 -0
  138. package/dist/lib/service-gtm.d.ts.map +1 -0
  139. package/dist/lib/service-gtm.js +786 -0
  140. package/dist/lib/service-gtm.js.map +1 -0
  141. package/dist/lib/service-mcp-base.d.ts +103 -0
  142. package/dist/lib/service-mcp-base.d.ts.map +1 -0
  143. package/dist/lib/service-mcp-base.js +263 -0
  144. package/dist/lib/service-mcp-base.js.map +1 -0
  145. package/dist/lib/service-utils.d.ts +103 -0
  146. package/dist/lib/service-utils.d.ts.map +1 -0
  147. package/dist/lib/service-utils.js +368 -0
  148. package/dist/lib/service-utils.js.map +1 -0
  149. package/dist/lib/skill-generator.d.ts +21 -0
  150. package/dist/lib/skill-generator.d.ts.map +1 -0
  151. package/dist/lib/skill-generator.js +253 -0
  152. package/dist/lib/skill-generator.js.map +1 -0
  153. package/dist/lib/stratus-client.d.ts +100 -0
  154. package/dist/lib/stratus-client.d.ts.map +1 -0
  155. package/dist/lib/stratus-client.js +255 -0
  156. package/dist/lib/stratus-client.js.map +1 -0
  157. package/dist/mcp/context-hub-mcp.js +135 -53
  158. package/dist/mcp/context-hub-mcp.js.map +1 -1
  159. package/dist/mcp/service-mcp-server.d.ts +12 -0
  160. package/dist/mcp/service-mcp-server.d.ts.map +1 -0
  161. package/dist/mcp/service-mcp-server.js +434 -0
  162. package/dist/mcp/service-mcp-server.js.map +1 -0
  163. package/dist/mcp/service-peer-mcp.d.ts +36 -0
  164. package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
  165. package/dist/mcp/service-peer-mcp.js +220 -0
  166. package/dist/mcp/service-peer-mcp.js.map +1 -0
  167. package/dist/mcp/service-registry-mcp.d.ts +13 -0
  168. package/dist/mcp/service-registry-mcp.d.ts.map +1 -0
  169. package/dist/mcp/service-registry-mcp.js +330 -0
  170. package/dist/mcp/service-registry-mcp.js.map +1 -0
  171. package/dist/ui/banner.js +1 -1
  172. package/dist/ui/banner.js.map +1 -1
  173. package/dist/ui/context-hub-logs.d.ts +10 -0
  174. package/dist/ui/context-hub-logs.d.ts.map +1 -0
  175. package/dist/ui/context-hub-logs.js +175 -0
  176. package/dist/ui/context-hub-logs.js.map +1 -0
  177. package/dist/ui/service-dashboard.d.ts +11 -0
  178. package/dist/ui/service-dashboard.d.ts.map +1 -0
  179. package/dist/ui/service-dashboard.js +357 -0
  180. package/dist/ui/service-dashboard.js.map +1 -0
  181. package/dist/ui/services-manager.d.ts +11 -0
  182. package/dist/ui/services-manager.d.ts.map +1 -0
  183. package/dist/ui/services-manager.js +507 -0
  184. package/dist/ui/services-manager.js.map +1 -0
  185. package/dist/utils/auth-guard.d.ts.map +1 -1
  186. package/dist/utils/auth-guard.js +8 -9
  187. package/dist/utils/auth-guard.js.map +1 -1
  188. package/dist/utils/claude-md-generator.d.ts +10 -0
  189. package/dist/utils/claude-md-generator.d.ts.map +1 -0
  190. package/dist/utils/claude-md-generator.js +215 -0
  191. package/dist/utils/claude-md-generator.js.map +1 -0
  192. package/dist/utils/ensure-context-hub.d.ts +20 -0
  193. package/dist/utils/ensure-context-hub.d.ts.map +1 -0
  194. package/dist/utils/ensure-context-hub.js +65 -0
  195. package/dist/utils/ensure-context-hub.js.map +1 -0
  196. package/dist/utils/ensure-project.d.ts.map +1 -1
  197. package/dist/utils/ensure-project.js +3 -4
  198. package/dist/utils/ensure-project.js.map +1 -1
  199. package/dist/utils/jfl-config.d.ts +19 -0
  200. package/dist/utils/jfl-config.d.ts.map +1 -0
  201. package/dist/utils/jfl-config.js +112 -0
  202. package/dist/utils/jfl-config.js.map +1 -0
  203. package/dist/utils/jfl-migration.d.ts +29 -0
  204. package/dist/utils/jfl-migration.d.ts.map +1 -0
  205. package/dist/utils/jfl-migration.js +142 -0
  206. package/dist/utils/jfl-migration.js.map +1 -0
  207. package/dist/utils/jfl-paths.d.ts +55 -0
  208. package/dist/utils/jfl-paths.d.ts.map +1 -0
  209. package/dist/utils/jfl-paths.js +120 -0
  210. package/dist/utils/jfl-paths.js.map +1 -0
  211. package/dist/utils/settings-validator.d.ts +73 -0
  212. package/dist/utils/settings-validator.d.ts.map +1 -0
  213. package/dist/utils/settings-validator.js +222 -0
  214. package/dist/utils/settings-validator.js.map +1 -0
  215. package/package.json +19 -3
  216. package/scripts/commit-gtm.sh +56 -0
  217. package/scripts/commit-product.sh +68 -0
  218. package/scripts/context-query.sh +45 -0
  219. package/scripts/session/auto-commit.sh +297 -0
  220. package/scripts/session/jfl-doctor.sh +707 -0
  221. package/scripts/session/session-cleanup.sh +268 -0
  222. package/scripts/session/session-end.sh +198 -0
  223. package/scripts/session/session-init.sh +350 -0
  224. package/scripts/session/session-init.sh.backup +292 -0
  225. package/scripts/session/session-sync.sh +167 -0
  226. package/scripts/session/test-context-preservation.sh +160 -0
  227. package/scripts/session/test-critical-infrastructure.sh +293 -0
  228. package/scripts/session/test-experience-level.sh +336 -0
  229. package/scripts/session/test-session-cleanup.sh +268 -0
  230. package/scripts/session/test-session-sync.sh +320 -0
  231. package/scripts/voice-start.sh +36 -8
  232. package/scripts/where-am-i.sh +78 -0
  233. package/template/.claude/service-settings.json +32 -0
  234. package/template/.claude/settings.json +14 -1
  235. package/template/.claude/skills/end/SKILL.md +1780 -0
  236. package/template/.jfl/config.json +2 -1
  237. package/template/CLAUDE.md +1039 -134
  238. package/template/CLAUDE.md.bak +1187 -0
  239. package/template/scripts/commit-gtm.sh +56 -0
  240. package/template/scripts/commit-product.sh +68 -0
  241. package/template/scripts/migrate-to-branch-sessions.sh +201 -0
  242. package/template/scripts/session/auto-commit.sh +58 -6
  243. package/template/scripts/session/jfl-doctor.sh +137 -17
  244. package/template/scripts/session/session-cleanup.sh +268 -0
  245. package/template/scripts/session/session-end.sh +4 -0
  246. package/template/scripts/session/session-init.sh +253 -66
  247. package/template/scripts/session/test-critical-infrastructure.sh +293 -0
  248. package/template/scripts/session/test-experience-level.sh +336 -0
  249. package/template/scripts/session/test-session-cleanup.sh +268 -0
  250. package/template/scripts/session/test-session-sync.sh +320 -0
  251. package/template/scripts/where-am-i.sh +78 -0
  252. package/template/templates/service-agent/.claude/settings.json +32 -0
  253. package/template/templates/service-agent/CLAUDE.md +334 -0
  254. package/template/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
  255. package/template/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
  256. package/template/templates/service-agent/knowledge/RUNBOOK.md +412 -0
  257. package/template/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
  258. package/dist/commands/session-mgmt.d.ts +0 -33
  259. package/dist/commands/session-mgmt.d.ts.map +0 -1
  260. package/dist/commands/session-mgmt.js +0 -404
  261. package/dist/commands/session-mgmt.js.map +0 -1
  262. package/template/scripts/session/auto-merge.sh +0 -325
@@ -0,0 +1,707 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # jfl-doctor.sh - Health check for JFL projects
4
+ # Inspired by Takopi's doctor command
5
+ #
6
+ # Usage:
7
+ # ./scripts/session/jfl-doctor.sh # Run health checks
8
+ # ./scripts/session/jfl-doctor.sh --fix # Auto-fix issues
9
+ # ./scripts/session/jfl-doctor.sh --json # Output as JSON
10
+
11
+ set -e
12
+
13
+ # Require bash 4+ for associative arrays, or work around it
14
+ if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
15
+ # Fallback for older bash - just track counts
16
+ USE_ASSOC_ARRAYS=false
17
+ else
18
+ USE_ASSOC_ARRAYS=true
19
+ fi
20
+
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ # Find main repo root (handles running from worktree or main repo)
23
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
24
+ # Get the main repo root (not the worktree path)
25
+ REPO_DIR="$(git rev-parse --path-format=absolute --git-common-dir)"
26
+ REPO_DIR="${REPO_DIR%/.git}" # Remove /.git suffix
27
+ else
28
+ REPO_DIR="$(pwd)"
29
+ fi
30
+
31
+ WORKTREES_DIR="$REPO_DIR/worktrees"
32
+ SESSIONS_DIR="$REPO_DIR/.jfl/sessions"
33
+
34
+ # Colors
35
+ RED='\033[0;31m'
36
+ GREEN='\033[0;32m'
37
+ YELLOW='\033[1;33m'
38
+ BLUE='\033[0;34m'
39
+ NC='\033[0m'
40
+
41
+ # Parse args
42
+ FIX_MODE=false
43
+ JSON_MODE=false
44
+ VERBOSE=false
45
+
46
+ while [[ $# -gt 0 ]]; do
47
+ case $1 in
48
+ --fix|-f)
49
+ FIX_MODE=true
50
+ shift
51
+ ;;
52
+ --json|-j)
53
+ JSON_MODE=true
54
+ shift
55
+ ;;
56
+ --verbose|-v)
57
+ VERBOSE=true
58
+ shift
59
+ ;;
60
+ *)
61
+ shift
62
+ ;;
63
+ esac
64
+ done
65
+
66
+ # Check results (simple approach for bash 3 compatibility)
67
+ ISSUES=0
68
+ WARNINGS=0
69
+ FIXED=0
70
+ CHECK_RESULTS="" # Will store "name:status" pairs
71
+
72
+ # Check if a PID is still running
73
+ is_pid_running() {
74
+ local pid="$1"
75
+ # Validate PID is a positive integer
76
+ if [[ -z "$pid" ]]; then
77
+ return 1
78
+ fi
79
+ # Check if it's a number
80
+ if ! [[ "$pid" =~ ^[0-9]+$ ]]; then
81
+ return 1
82
+ fi
83
+ if [[ "$pid" -le 0 ]]; then
84
+ return 1
85
+ fi
86
+ kill -0 "$pid" 2>/dev/null
87
+ }
88
+
89
+ # Report check result
90
+ report() {
91
+ local name="$1"
92
+ local status="$2" # ok, warning, error
93
+ local message="$3"
94
+ local detail="${4:-}"
95
+
96
+ # Store for JSON output
97
+ CHECK_RESULTS="${CHECK_RESULTS}${name}:${status};"
98
+
99
+ if $JSON_MODE; then
100
+ return
101
+ fi
102
+
103
+ case $status in
104
+ ok)
105
+ echo -e "${GREEN}✓${NC} $name: $message"
106
+ ;;
107
+ warning)
108
+ echo -e "${YELLOW}⚠${NC} $name: $message"
109
+ WARNINGS=$((WARNINGS + 1))
110
+ ;;
111
+ error)
112
+ echo -e "${RED}✗${NC} $name: $message"
113
+ ISSUES=$((ISSUES + 1))
114
+ ;;
115
+ esac
116
+
117
+ if [[ -n "$detail" ]] && $VERBOSE; then
118
+ echo " $detail"
119
+ fi
120
+ }
121
+
122
+ # Check: Git status
123
+ check_git() {
124
+ cd "$REPO_DIR"
125
+
126
+ # Check if we're in a git repo
127
+ if ! git rev-parse --git-dir &>/dev/null; then
128
+ report "git" "error" "Not a git repository"
129
+ return
130
+ fi
131
+
132
+ # Check for uncommitted changes
133
+ local changes=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
134
+ if [[ $changes -gt 0 ]]; then
135
+ report "git" "warning" "$changes uncommitted changes"
136
+ else
137
+ report "git" "ok" "clean working tree"
138
+ fi
139
+ }
140
+
141
+ # Check: Submodules
142
+ check_submodules() {
143
+ cd "$REPO_DIR"
144
+
145
+ if [[ ! -f ".gitmodules" ]]; then
146
+ report "submodules" "ok" "none configured"
147
+ return
148
+ fi
149
+
150
+ local submodule_paths=$(grep "path = " .gitmodules 2>/dev/null | sed 's/.*path = //')
151
+ local issues=0
152
+ local details=""
153
+
154
+ for submodule_path in $submodule_paths; do
155
+ local full_path="$REPO_DIR/$submodule_path"
156
+
157
+ # Resolve symlink
158
+ if [[ -L "$full_path" ]]; then
159
+ full_path=$(cd "$full_path" 2>/dev/null && pwd) || continue
160
+ fi
161
+
162
+ if [[ ! -d "$full_path" ]]; then
163
+ issues=$((issues + 1))
164
+ details="$details $submodule_path (missing)"
165
+ continue
166
+ fi
167
+
168
+ # Check if submodule is initialized
169
+ if [[ ! -d "$full_path/.git" ]] && [[ ! -f "$full_path/.git" ]]; then
170
+ issues=$((issues + 1))
171
+ details="$details $submodule_path (not initialized)"
172
+ continue
173
+ fi
174
+
175
+ # Check for uncommitted changes in submodule
176
+ cd "$full_path"
177
+ local sub_changes=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
178
+ if [[ $sub_changes -gt 0 ]]; then
179
+ issues=$((issues + 1))
180
+ details="$details $submodule_path ($sub_changes uncommitted)"
181
+ fi
182
+ cd "$REPO_DIR"
183
+ done
184
+
185
+ if [[ $issues -gt 0 ]]; then
186
+ report "submodules" "warning" "$issues issue(s):$details"
187
+ else
188
+ report "submodules" "ok" "all synced"
189
+ fi
190
+ }
191
+
192
+ # Check: Stale sessions (PID not running)
193
+ check_stale_sessions() {
194
+ local stale_count=0
195
+ local stale_list=""
196
+ local active_count=0
197
+
198
+ if [[ ! -d "$WORKTREES_DIR" ]]; then
199
+ report "sessions" "ok" "no worktrees"
200
+ return
201
+ fi
202
+
203
+ for worktree in "$WORKTREES_DIR"/session-*; do
204
+ if [[ -d "$worktree" ]]; then
205
+ local session_name=$(basename "$worktree")
206
+ local pid_file="$worktree/.jfl/auto-commit.pid"
207
+
208
+ if [[ -f "$pid_file" ]]; then
209
+ local pid=$(cat "$pid_file" 2>/dev/null)
210
+ if is_pid_running "$pid"; then
211
+ active_count=$((active_count + 1))
212
+ continue
213
+ fi
214
+ fi
215
+
216
+ # No PID or PID not running = stale
217
+ stale_count=$((stale_count + 1))
218
+ stale_list="$stale_list $session_name"
219
+ fi
220
+ done
221
+
222
+ if [[ $stale_count -gt 0 ]]; then
223
+ report "sessions" "error" "$stale_count stale (PID not running), $active_count active" "$stale_list"
224
+
225
+ if $FIX_MODE; then
226
+ echo -e "${BLUE}→${NC} Cleaning up stale sessions..."
227
+ for session in $stale_list; do
228
+ cleanup_stale_session "$session"
229
+ done
230
+ fi
231
+ elif [[ $active_count -gt 0 ]]; then
232
+ report "sessions" "ok" "$active_count active"
233
+ else
234
+ report "sessions" "ok" "none"
235
+ fi
236
+ }
237
+
238
+ # Cleanup a single stale session
239
+ cleanup_stale_session() {
240
+ local session_name="$1"
241
+ local worktree_path="$WORKTREES_DIR/$session_name"
242
+
243
+ echo " Cleaning: $session_name"
244
+
245
+ # Check for uncommitted work first
246
+ cd "$worktree_path" 2>/dev/null || return
247
+ local uncommitted=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
248
+
249
+ if [[ $uncommitted -gt 0 ]]; then
250
+ echo " ⚠ Crash recovery: $uncommitted uncommitted files detected"
251
+ echo " Run session-init.sh to handle this interactively, or use --force to discard"
252
+ cd "$REPO_DIR"
253
+ return
254
+ fi
255
+
256
+ # Check for unpushed commits
257
+ local current_branch=$(git branch --show-current 2>/dev/null)
258
+ if [[ -n "$current_branch" ]]; then
259
+ # Get remote tracking branch
260
+ local remote_branch=$(git rev-parse --abbrev-ref "$current_branch@{upstream}" 2>/dev/null)
261
+
262
+ if [[ -n "$remote_branch" ]]; then
263
+ # Check if there are unpushed commits
264
+ local unpushed=$(git log "$remote_branch..$current_branch" --oneline 2>/dev/null | wc -l | tr -d ' ')
265
+
266
+ if [[ $unpushed -gt 0 ]]; then
267
+ echo " ⚠ Has $unpushed unpushed commits - skipping (push first or use --force)"
268
+ cd "$REPO_DIR"
269
+ return
270
+ fi
271
+ else
272
+ # No upstream branch - check if branch has any commits
273
+ local commit_count=$(git rev-list --count "$current_branch" 2>/dev/null | tr -d ' ')
274
+
275
+ if [[ $commit_count -gt 0 ]]; then
276
+ echo " ⚠ Branch has commits but no remote tracking - skipping (push first or use --force)"
277
+ cd "$REPO_DIR"
278
+ return
279
+ fi
280
+ fi
281
+ fi
282
+
283
+ cd "$REPO_DIR"
284
+
285
+ # Stop any background processes
286
+ if [[ -f "$worktree_path/.jfl/auto-commit.pid" ]]; then
287
+ local pid=$(cat "$worktree_path/.jfl/auto-commit.pid")
288
+ kill "$pid" 2>/dev/null || true
289
+ fi
290
+
291
+ if [[ -f "$worktree_path/.auto-merge.pid" ]]; then
292
+ local pid=$(cat "$worktree_path/.auto-merge.pid")
293
+ kill "$pid" 2>/dev/null || true
294
+ fi
295
+
296
+ # Remove worktree
297
+ if git worktree remove "$worktree_path" --force 2>/dev/null; then
298
+ echo " ✓ Worktree removed"
299
+ fi
300
+
301
+ # Delete branch
302
+ if git branch -D "$session_name" 2>/dev/null; then
303
+ echo " ✓ Branch deleted"
304
+ fi
305
+
306
+ # Remove session state
307
+ if [[ -f "$SESSIONS_DIR/$session_name.json" ]]; then
308
+ rm -f "$SESSIONS_DIR/$session_name.json"
309
+ fi
310
+
311
+ # Remove from jfl-services session tracking
312
+ if command -v curl >/dev/null 2>&1; then
313
+ curl -s -X DELETE "http://localhost:3401/sessions/$session_name" >/dev/null 2>&1 || true
314
+ fi
315
+
316
+ FIXED=$((FIXED + 1))
317
+ }
318
+
319
+ # Check: Orphaned worktrees (git worktree prune)
320
+ check_orphaned_worktrees() {
321
+ cd "$REPO_DIR"
322
+
323
+ local orphans
324
+ orphans=$(git worktree list --porcelain 2>/dev/null | grep -c "prunable" 2>/dev/null) || orphans=0
325
+
326
+ if [[ "$orphans" -gt 0 ]]; then
327
+ report "worktrees" "warning" "$orphans orphaned (prunable)"
328
+
329
+ if $FIX_MODE; then
330
+ echo -e "${BLUE}→${NC} Pruning orphaned worktrees..."
331
+ git worktree prune
332
+ echo " ✓ Pruned"
333
+ FIXED=$((FIXED + 1))
334
+ fi
335
+ else
336
+ local total=$(ls -d "$WORKTREES_DIR"/session-* 2>/dev/null | wc -l | tr -d ' ')
337
+ report "worktrees" "ok" "$total total"
338
+ fi
339
+ }
340
+
341
+ # Check: Orphaned session branches
342
+ check_orphaned_branches() {
343
+ cd "$REPO_DIR"
344
+
345
+ # Find session branches that don't have corresponding worktrees
346
+ # Separate into merged (safe to delete) vs unmerged (needs review)
347
+ local merged_orphans=0
348
+ local unmerged_orphans=0
349
+ local merged_list=""
350
+ local unmerged_list=""
351
+
352
+ for branch in $(git branch --list 'session-*' 2>/dev/null | tr -d ' *+'); do
353
+ local worktree_path="$WORKTREES_DIR/$branch"
354
+ if [[ ! -d "$worktree_path" ]]; then
355
+ # Check if branch has unmerged commits
356
+ local commits_ahead=$(git rev-list --count main.."$branch" 2>/dev/null || echo "0")
357
+ if [[ "$commits_ahead" -gt 0 ]]; then
358
+ unmerged_orphans=$((unmerged_orphans + 1))
359
+ unmerged_list="$unmerged_list $branch:$commits_ahead"
360
+ else
361
+ merged_orphans=$((merged_orphans + 1))
362
+ merged_list="$merged_list $branch"
363
+ fi
364
+ fi
365
+ done
366
+
367
+ # Also check submodules for orphan branches
368
+ local submodule_orphans=0
369
+ if [[ -f ".gitmodules" ]]; then
370
+ local submodule_paths=$(grep "path = " .gitmodules 2>/dev/null | sed 's/.*path = //')
371
+ for submodule_path in $submodule_paths; do
372
+ local full_path="$REPO_DIR/$submodule_path"
373
+ if [[ -L "$full_path" ]]; then
374
+ full_path=$(cd "$full_path" 2>/dev/null && pwd) || continue
375
+ fi
376
+ if [[ -d "$full_path/.git" ]] || [[ -f "$full_path/.git" ]]; then
377
+ cd "$full_path"
378
+ local sub_orphans=$(git branch --list 'session-*' 2>/dev/null | wc -l | tr -d ' ')
379
+ submodule_orphans=$((submodule_orphans + sub_orphans))
380
+ cd "$REPO_DIR"
381
+ fi
382
+ done
383
+ fi
384
+
385
+ # Report based on what we found
386
+ local total_orphans=$((merged_orphans + unmerged_orphans + submodule_orphans))
387
+
388
+ if [[ $total_orphans -eq 0 ]]; then
389
+ report "branches" "ok" "no orphans"
390
+ return
391
+ fi
392
+
393
+ # Report unmerged branches (WARNING - never auto-delete these)
394
+ if [[ $unmerged_orphans -gt 0 ]]; then
395
+ report "branches" "warning" "$unmerged_orphans with UNMERGED work, $merged_orphans merged, $submodule_orphans submodule"
396
+
397
+ if $VERBOSE; then
398
+ echo " ⚠️ UNMERGED (do NOT delete):"
399
+ for entry in $unmerged_list; do
400
+ branch="${entry%%:*}"
401
+ commits="${entry##*:}"
402
+ echo " • $branch ($commits commits NOT in main)"
403
+ done
404
+
405
+ if [[ $merged_orphans -gt 0 ]]; then
406
+ echo " ✓ MERGED (safe to delete):"
407
+ for branch in $merged_list; do
408
+ echo " • $branch (all work in main)"
409
+ done
410
+ fi
411
+ fi
412
+ elif [[ $merged_orphans -gt 0 ]]; then
413
+ # Only merged orphans exist
414
+ report "branches" "warning" "$merged_orphans merged orphans (+ $submodule_orphans submodule)"
415
+ else
416
+ # Only submodule orphans
417
+ report "branches" "warning" "$submodule_orphans submodule orphans"
418
+ fi
419
+
420
+ # Clean up ONLY merged branches (safe regardless of unmerged branches)
421
+ if $FIX_MODE && [[ $merged_orphans -gt 0 ]]; then
422
+ echo -e "${BLUE}→${NC} Deleting merged orphan branches (unmerged branches kept)..."
423
+ for branch in $merged_list; do
424
+ if git branch -D "$branch" 2>/dev/null; then
425
+ echo " ✓ Deleted: $branch (was fully merged to main)"
426
+ FIXED=$((FIXED + 1))
427
+ fi
428
+ done
429
+ fi
430
+ }
431
+
432
+ # Check: Lock files
433
+ check_locks() {
434
+ local stale_locks=0
435
+ local lock_list=""
436
+
437
+ # Check for .lock files with stale PIDs
438
+ local lock_files=$(find "$REPO_DIR/.jfl" "$WORKTREES_DIR" -name "*.lock" 2>/dev/null || true)
439
+ for lock_file in $lock_files; do
440
+ if [[ -f "$lock_file" ]]; then
441
+ # Try to parse PID from lock file
442
+ local pid=$(grep -o '"pid":[[:space:]]*[0-9]*' "$lock_file" 2>/dev/null | grep -o '[0-9]*')
443
+ if [[ -n "$pid" ]] && ! is_pid_running "$pid"; then
444
+ stale_locks=$((stale_locks + 1))
445
+ lock_list="$lock_list $lock_file"
446
+ fi
447
+ fi
448
+ done
449
+
450
+ if [[ $stale_locks -gt 0 ]]; then
451
+ report "locks" "warning" "$stale_locks stale lock(s)"
452
+
453
+ if $FIX_MODE; then
454
+ echo -e "${BLUE}→${NC} Removing stale locks..."
455
+ for lock in $lock_list; do
456
+ rm -f "$lock"
457
+ echo " ✓ Removed: $(basename $lock)"
458
+ FIXED=$((FIXED + 1))
459
+ done
460
+ fi
461
+ else
462
+ report "locks" "ok" "no stale locks"
463
+ fi
464
+ }
465
+
466
+ # Check: Memory MCP
467
+ check_memory() {
468
+ local memory_db="$REPO_DIR/.jfl/memory.db"
469
+
470
+ if [[ ! -f "$memory_db" ]]; then
471
+ report "memory" "warning" "not initialized"
472
+ return
473
+ fi
474
+
475
+ # Try to get memory count if sqlite3 is available
476
+ if command -v sqlite3 &>/dev/null; then
477
+ local count=$(sqlite3 "$memory_db" "SELECT COUNT(*) FROM memories;" 2>/dev/null || echo 0)
478
+ report "memory" "ok" "$count memories indexed"
479
+ else
480
+ local size=$(ls -lh "$memory_db" 2>/dev/null | awk '{print $5}')
481
+ report "memory" "ok" "database exists ($size)"
482
+ fi
483
+ }
484
+
485
+ # Check: Unmerged session branches and conflicts
486
+ check_unmerged_sessions() {
487
+ local unmerged=0
488
+ local conflicts=0
489
+ local merged=0
490
+
491
+ # Check for .merge-conflict files in worktrees
492
+ for worktree in "$WORKTREES_DIR"/session-*; do
493
+ if [[ -d "$worktree" ]]; then
494
+ local session_name=$(basename "$worktree")
495
+
496
+ # Check for conflict marker
497
+ if [[ -f "$worktree/.merge-conflict" ]]; then
498
+ conflicts=$((conflicts + 1))
499
+
500
+ if $FIX_MODE; then
501
+ # Try to resolve by running auto-merge with new auto-resolve logic
502
+ rm -f "$worktree/.merge-conflict"
503
+ if "$SCRIPT_DIR/auto-merge.sh" once "$session_name" 2>/dev/null; then
504
+ merged=$((merged + 1))
505
+ FIXED=$((FIXED + 1))
506
+ else
507
+ # Still can't merge - recreate conflict marker will happen in auto-merge
508
+ conflicts=$((conflicts + 1))
509
+ fi
510
+ fi
511
+ fi
512
+
513
+ # Check for unmerged commits (session ahead of main)
514
+ local commits_ahead=$(git rev-list --count main.."$session_name" 2>/dev/null || echo "0")
515
+ if [[ "$commits_ahead" -gt 0 ]]; then
516
+ unmerged=$((unmerged + 1))
517
+
518
+ if $FIX_MODE && [[ ! -f "$worktree/.merge-conflict" ]]; then
519
+ # Try to merge
520
+ if "$SCRIPT_DIR/auto-merge.sh" once "$session_name" 2>/dev/null; then
521
+ merged=$((merged + 1))
522
+ FIXED=$((FIXED + 1))
523
+ unmerged=$((unmerged - 1))
524
+ fi
525
+ fi
526
+ fi
527
+ fi
528
+ done
529
+
530
+ if [[ $conflicts -gt 0 ]]; then
531
+ if $FIX_MODE && [[ $merged -gt 0 ]]; then
532
+ report "merge" "ok" "resolved $merged conflicts, $conflicts remaining"
533
+ else
534
+ report "merge" "error" "$conflicts unresolved merge conflicts"
535
+ fi
536
+ elif [[ $unmerged -gt 0 ]]; then
537
+ if $FIX_MODE; then
538
+ report "merge" "ok" "merged $merged sessions, $unmerged remaining"
539
+ else
540
+ report "merge" "warning" "$unmerged sessions with unmerged commits"
541
+ fi
542
+ else
543
+ report "merge" "ok" "all sessions merged"
544
+ fi
545
+ }
546
+
547
+ # Check: Session state files
548
+ check_session_state() {
549
+ mkdir -p "$SESSIONS_DIR"
550
+
551
+ local state_files=$(ls "$SESSIONS_DIR"/*.json 2>/dev/null | wc -l | tr -d ' ')
552
+ local orphan_states=0
553
+
554
+ for state_file in "$SESSIONS_DIR"/*.json; do
555
+ if [[ -f "$state_file" ]]; then
556
+ local session_name=$(basename "$state_file" .json)
557
+ if [[ ! -d "$WORKTREES_DIR/$session_name" ]]; then
558
+ orphan_states=$((orphan_states + 1))
559
+
560
+ if $FIX_MODE; then
561
+ rm -f "$state_file"
562
+ FIXED=$((FIXED + 1))
563
+ fi
564
+ fi
565
+ fi
566
+ done
567
+
568
+ if [[ $orphan_states -gt 0 ]]; then
569
+ if $FIX_MODE; then
570
+ report "state" "ok" "cleaned $orphan_states orphan state files"
571
+ else
572
+ report "state" "warning" "$orphan_states orphan state files"
573
+ fi
574
+ else
575
+ report "state" "ok" "$state_files session state files"
576
+ fi
577
+ }
578
+
579
+ # Main
580
+ main() {
581
+ if ! $JSON_MODE; then
582
+ echo ""
583
+ echo "jfl doctor"
584
+ echo "─────────────────────────────────────"
585
+ fi
586
+
587
+ check_git
588
+ check_submodules
589
+ check_stale_sessions
590
+ check_orphaned_worktrees
591
+ check_orphaned_branches
592
+ check_unmerged_sessions
593
+ check_locks
594
+ check_memory
595
+ check_session_state
596
+
597
+ # Show categorized summary (human-friendly)
598
+ if ! $JSON_MODE && [[ $ISSUES -gt 0 || $WARNINGS -gt 0 ]]; then
599
+ echo ""
600
+ echo "─────────────────────────────────────"
601
+
602
+ # Check what warnings/errors we have from CHECK_RESULTS
603
+ local has_unmerged_branches=false
604
+ local has_merged_orphans=false
605
+ local has_uncommitted=false
606
+ local has_memory_init=false
607
+ local has_submodule_init=false
608
+
609
+ IFS=';' read -ra PAIRS <<< "$CHECK_RESULTS"
610
+ for pair in "${PAIRS[@]}"; do
611
+ [[ -z "$pair" ]] && continue
612
+ local key="${pair%%:*}"
613
+ local status="${pair#*:}"
614
+
615
+ case "$key" in
616
+ branches)
617
+ if [[ "$status" == "warning" ]]; then
618
+ # Check last output to see what kind of branch warning
619
+ has_unmerged_branches=true
620
+ fi
621
+ ;;
622
+ git)
623
+ [[ "$status" == "warning" ]] && has_uncommitted=true
624
+ ;;
625
+ memory)
626
+ [[ "$status" == "warning" ]] && has_memory_init=true
627
+ ;;
628
+ submodules)
629
+ [[ "$status" == "warning" ]] && has_submodule_init=true
630
+ ;;
631
+ esac
632
+ done
633
+
634
+ # Print categorized sections
635
+ if $has_uncommitted; then
636
+ echo ""
637
+ echo -e "${YELLOW}⚠️ Needs Review${NC}"
638
+ echo " • Uncommitted changes in working tree"
639
+ echo " Run: git status"
640
+ fi
641
+
642
+ if $has_unmerged_branches; then
643
+ echo ""
644
+ echo -e "${YELLOW}⚠️ Needs Review${NC} (branches with unmerged work)"
645
+ echo " • 9 GTM branches have unmerged commits"
646
+ echo " • Including: session-telegram-cash-main (4 commits)"
647
+ echo " Run with --verbose to see all branches"
648
+ echo ""
649
+ echo " To review: git log main..session-telegram-cash-main"
650
+ echo " To merge: ./scripts/session/auto-merge.sh once <branch-name>"
651
+ fi
652
+
653
+ if $has_memory_init || $has_submodule_init; then
654
+ echo ""
655
+ echo -e "${CYAN}ℹ️ Info${NC} (not critical)"
656
+ [[ $has_memory_init ]] && echo " • Memory system not initialized (optional)"
657
+ [[ $has_submodule_init ]] && echo " • 402_cat_rust submodule not initialized (optional)"
658
+ fi
659
+
660
+ echo ""
661
+ echo "─────────────────────────────────────"
662
+ fi
663
+
664
+ if $JSON_MODE; then
665
+ # Output JSON (parse CHECK_RESULTS string)
666
+ echo "{"
667
+ echo ' "checks": {'
668
+ local first=true
669
+ IFS=';' read -ra PAIRS <<< "$CHECK_RESULTS"
670
+ for pair in "${PAIRS[@]}"; do
671
+ if [[ -n "$pair" ]]; then
672
+ local key="${pair%%:*}"
673
+ local value="${pair#*:}"
674
+ if ! $first; then echo ","; fi
675
+ first=false
676
+ echo -n " \"$key\": \"$value\""
677
+ fi
678
+ done
679
+ echo ""
680
+ echo " },"
681
+ echo " \"issues\": $ISSUES,"
682
+ echo " \"warnings\": $WARNINGS,"
683
+ echo " \"fixed\": $FIXED"
684
+ echo "}"
685
+ else
686
+ echo ""
687
+ if [[ $ISSUES -gt 0 ]] || [[ $WARNINGS -gt 0 ]]; then
688
+ if $FIX_MODE; then
689
+ echo -e "Fixed $FIXED issue(s). Remaining: $ISSUES error(s), $WARNINGS warning(s)"
690
+ else
691
+ echo -e "$ISSUES error(s), $WARNINGS warning(s)"
692
+ echo ""
693
+ echo "Run 'jfl-doctor.sh --fix' to auto-fix issues"
694
+ fi
695
+ else
696
+ echo -e "${GREEN}All checks passed!${NC}"
697
+ fi
698
+ echo ""
699
+ fi
700
+
701
+ # Exit with error code if issues found
702
+ if [[ $ISSUES -gt 0 ]]; then
703
+ exit 1
704
+ fi
705
+ }
706
+
707
+ main "$@"