tlc-claude-code 2.9.1 → 2.9.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.
@@ -1,86 +1,91 @@
1
1
  #!/bin/bash
2
- # TLC Enforcement Layer 2b: Session initialization
3
- # 1. Inject TLC awareness
4
- # 2. Ensure TLC server is running
5
- # 3. Probe LLM providers and write persistent router state
2
+ # TLC Session Init Comprehensive Health Check
3
+ # Verifies ALL systems and reports status clearly.
4
+ # No silent failures. No partial checks.
6
5
 
7
6
  if [ ! -f ".tlc.json" ]; then
8
7
  exit 0
9
8
  fi
10
9
 
10
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
11
+ TLC_PORT="${TLC_PORT:-3147}"
12
+ TLC_CORE_PORT="${TLC_CORE_PORT:-3100}"
13
+ ERRORS=0
14
+ WARNINGS=0
15
+
11
16
  echo "TLC project detected. All work goes through /tlc commands. Run /tlc for current status and next action."
17
+ echo ""
18
+ echo "── System Health Check ──────────────────────────────"
12
19
 
13
- # ─── TLC Update Notification ─────────────────────────────
14
- UPDATE_FILE="$HOME/.tlc/.update-available"
15
- if [ -f "$UPDATE_FILE" ]; then
16
- CURRENT=$(grep '^current=' "$UPDATE_FILE" | cut -d= -f2)
17
- LATEST=$(grep '^latest=' "$UPDATE_FILE" | cut -d= -f2)
18
- CMD=$(grep '^command=' "$UPDATE_FILE" | cut -d= -f2-)
19
- if [ -n "$CURRENT" ] && [ -n "$LATEST" ]; then
20
- echo "TLC update available: ${CURRENT} → ${LATEST}. Run: ${CMD}"
21
- fi
22
- fi
20
+ # ─── 1. TLC Package Version ──────────────────────────────
21
+ INSTALLED_VER=$(node -e "try{console.log(require('tlc-claude-code/package.json').version)}catch{try{console.log(require('$PROJECT_DIR/package.json').version)}catch{console.log('not-installed')}}" 2>/dev/null)
22
+ LATEST_VER=$(npm view tlc-claude-code version 2>/dev/null || echo "unknown")
23
+ PROJECT_VER=$(node -e "try{console.log(require('./.tlc.json').tlcVersion||'unknown')}catch{console.log('unknown')}" 2>/dev/null)
23
24
 
24
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
25
+ if [ "$INSTALLED_VER" = "not-installed" ]; then
26
+ echo " TLC Package: ❌ not installed (run: npm i -g tlc-claude-code)"
27
+ ERRORS=$((ERRORS + 1))
28
+ elif [ "$INSTALLED_VER" != "$LATEST_VER" ] && [ "$LATEST_VER" != "unknown" ]; then
29
+ echo " TLC Package: ⚠️ ${INSTALLED_VER} (latest: ${LATEST_VER}) — run: npm i -g tlc-claude-code"
30
+ WARNINGS=$((WARNINGS + 1))
31
+ else
32
+ echo " TLC Package: ✅ v${INSTALLED_VER} (latest)"
33
+ fi
25
34
 
26
- # ─── TLC Server ───────────────────────────────────────────
35
+ # ─── 2. TLC Memory System ────────────────────────────────
36
+ mkdir -p "$PROJECT_DIR/.tlc/memory/team/decisions" \
37
+ "$PROJECT_DIR/.tlc/memory/team/gotchas" \
38
+ "$PROJECT_DIR/.tlc/memory/.local/sessions" 2>/dev/null
27
39
 
28
- TLC_PORT="${TLC_PORT:-3147}"
29
40
  if curl -sf --max-time 1 "http://localhost:${TLC_PORT}/api/health" > /dev/null 2>&1; then
30
- : # Server is running
41
+ echo " TLC Server: ✅ running (:${TLC_PORT})"
42
+ # Drain spool
43
+ SPOOL_DRAIN=""
44
+ if node -e "require('tlc-claude-code/server/lib/capture/spool-processor')" 2>/dev/null; then
45
+ SPOOL_DRAIN="require('tlc-claude-code/server/lib/capture/spool-processor').processSpool('$PROJECT_DIR')"
46
+ elif [ -f "$PROJECT_DIR/server/lib/capture/spool-processor.js" ]; then
47
+ SPOOL_DRAIN="require('$PROJECT_DIR/server/lib/capture/spool-processor').processSpool('$PROJECT_DIR')"
48
+ fi
49
+ [ -n "$SPOOL_DRAIN" ] && node -e "$SPOOL_DRAIN" 2>/dev/null
31
50
  else
32
51
  PLIST="$HOME/Library/LaunchAgents/com.tlc.server.plist"
33
-
34
52
  if [ -f "$PLIST" ]; then
35
53
  launchctl kickstart -k "gui/$(id -u)/com.tlc.server" 2>/dev/null
36
54
  elif [ -f "$PROJECT_DIR/server/index.js" ]; then
37
55
  nohup node "$PROJECT_DIR/server/index.js" > "$HOME/.tlc/logs/server.log" 2>&1 &
38
56
  fi
39
-
40
- for i in 1 2 3; do
41
- sleep 1
42
- curl -sf --max-time 1 "http://localhost:${TLC_PORT}/api/health" > /dev/null 2>&1 && break
43
- done
57
+ sleep 2
58
+ if curl -sf --max-time 1 "http://localhost:${TLC_PORT}/api/health" > /dev/null 2>&1; then
59
+ echo " TLC Server: ✅ started (:${TLC_PORT})"
60
+ else
61
+ echo " TLC Server: ❌ failed to start"
62
+ ERRORS=$((ERRORS + 1))
63
+ fi
44
64
  fi
45
65
 
46
- # --- tlc-core Orchestrator ---
47
- # Kill switch: export TLC_CORE_AUTOSTART=false to disable auto-start
48
- TLC_CORE_PORT="${TLC_CORE_PORT:-3100}"
49
- if [ "${TLC_CORE_AUTOSTART}" = "false" ]; then
50
- echo "tlc-core orchestrator: auto-start disabled (TLC_CORE_AUTOSTART=false)"
51
- elif curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
52
- SESSIONS=$(curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/sessions" 2>/dev/null | jq "length" 2>/dev/null || echo "0")
53
- echo "tlc-core orchestrator: healthy (${SESSIONS} sessions)"
66
+ SPOOL_FILE="$PROJECT_DIR/.tlc/memory/.spool.jsonl"
67
+ SPOOL_COUNT=0
68
+ if [ -f "$SPOOL_FILE" ]; then
69
+ SPOOL_COUNT=$(wc -l < "$SPOOL_FILE" 2>/dev/null || echo "0")
70
+ fi
71
+ SPOOL_COUNT=$(echo "$SPOOL_COUNT" | tr -d ' ')
72
+ if [ "$SPOOL_COUNT" -gt 0 ] 2>/dev/null; then
73
+ echo " Memory Spool: ⚠️ ${SPOOL_COUNT} entries pending drain"
74
+ WARNINGS=$((WARNINGS + 1))
54
75
  else
55
- # Try to auto-start if tlc-core CLI is available
56
- if command -v tlc-core >/dev/null 2>&1; then
57
- echo "tlc-core orchestrator: not running — starting..."
58
- tlc-core start > /dev/null 2>&1 &
59
- # Wait up to 15s for health
60
- for i in 1 2 3 4 5; do
61
- sleep 3
62
- if curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
63
- echo "tlc-core orchestrator: started successfully"
64
- break
65
- fi
66
- done
67
- if ! curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
68
- echo "tlc-core orchestrator: failed to start. Run 'tlc-core start' manually."
69
- fi
70
- else
71
- echo "tlc-core orchestrator: not installed. Run: git clone git@github.com:TwentyTwoLabs22/tlc-core.git && cd cli && npm install && npm link"
72
- fi
76
+ echo " Memory: ✅ ready (spool empty)"
73
77
  fi
74
78
 
75
- # ─── CodeDB (codebase awareness) ─────────────────────
79
+ # ─── 3. CodeDB ───────────────────────────────────────────
76
80
  if command -v codedb >/dev/null 2>&1; then
77
81
  if curl -sf --max-time 1 "http://localhost:7719/health" > /dev/null 2>&1; then
78
- echo "codedb: running"
82
+ CODEDB_FILES=$(curl -sf --max-time 1 "http://localhost:7719/status" 2>/dev/null | node -e "try{const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.files||'?')}catch{console.log('?')}" 2>/dev/null || echo "?")
83
+ echo " CodeDB: ✅ running (${CODEDB_FILES} files indexed)"
79
84
  else
80
85
  codedb serve "$PROJECT_DIR" > /dev/null 2>&1 &
81
- echo "codedb: started indexing $PROJECT_DIR"
86
+ echo " CodeDB: started indexing"
82
87
  fi
83
- # Ensure .mcp.json exists for Claude Code MCP discovery
88
+ # Ensure .mcp.json
84
89
  if [ ! -f "$PROJECT_DIR/.mcp.json" ]; then
85
90
  cat > "$PROJECT_DIR/.mcp.json" <<'MCPEOF'
86
91
  {
@@ -92,126 +97,210 @@ if command -v codedb >/dev/null 2>&1; then
92
97
  }
93
98
  }
94
99
  MCPEOF
95
- echo "codedb: created .mcp.json"
100
+ echo " CodeDB MCP: created .mcp.json"
96
101
  fi
102
+ else
103
+ echo " CodeDB: ❌ not installed (run: npx tlc-claude-code)"
104
+ ERRORS=$((ERRORS + 1))
97
105
  fi
98
106
 
99
- # ─── Memory System Init ─────────────────────────────
100
- mkdir -p "$PROJECT_DIR/.tlc/memory/team/decisions" \
101
- "$PROJECT_DIR/.tlc/memory/team/gotchas" \
102
- "$PROJECT_DIR/.tlc/memory/.local/sessions"
103
-
104
- # ─── Spool Drain ─────────────────────────────────────
105
- # Try to drain any pending spool entries from previous sessions
106
- SPOOL_DRAIN=""
107
- if node -e "require('tlc-claude-code/server/lib/capture/spool-processor')" 2>/dev/null; then
108
- SPOOL_DRAIN="require('tlc-claude-code/server/lib/capture/spool-processor').processSpool('$PROJECT_DIR')"
109
- elif [ -f "$PROJECT_DIR/server/lib/capture/spool-processor.js" ]; then
110
- SPOOL_DRAIN="require('$PROJECT_DIR/server/lib/capture/spool-processor').processSpool('$PROJECT_DIR')"
107
+ # ─── 4. tlc-core Orchestrator + Containers ────────────────
108
+ if [ "${TLC_CORE_AUTOSTART}" = "false" ]; then
109
+ echo " Orchestrator: ⏸ disabled (TLC_CORE_AUTOSTART=false)"
110
+ elif curl -sf --max-time 2 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
111
+ ORCH_HEALTH=$(curl -sf --max-time 2 "http://localhost:${TLC_CORE_PORT}/health" 2>/dev/null)
112
+ PG_STATUS=$(echo "$ORCH_HEALTH" | node -e "try{console.log(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).postgres)}catch{console.log('?')}" 2>/dev/null)
113
+ DOCKER_STATUS=$(echo "$ORCH_HEALTH" | node -e "try{console.log(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).docker)}catch{console.log('?')}" 2>/dev/null)
114
+ RUNNING_SESSIONS=$(curl -sf --max-time 2 "http://localhost:${TLC_CORE_PORT}/sessions" 2>/dev/null | node -e "try{const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));const r=d.filter(s=>s.status==='running');console.log(r.length+'/'+d.length)}catch{console.log('?')}" 2>/dev/null)
115
+ echo " Orchestrator: ✅ healthy (pg:${PG_STATUS}, docker:${DOCKER_STATUS}, sessions:${RUNNING_SESSIONS})"
116
+
117
+ # Check agent-runner container
118
+ if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "tlc-agent-runner"; then
119
+ TMUX_COUNT=$(docker exec tlc-agent-runner tmux list-sessions 2>/dev/null | wc -l | tr -d ' ')
120
+ echo " Agent Runner: ✅ running (${TMUX_COUNT} tmux sessions)"
121
+ else
122
+ echo " Agent Runner: ❌ container not running"
123
+ ERRORS=$((ERRORS + 1))
124
+ fi
125
+
126
+ # Check session monitor + watchdog
127
+ ORCH_LOGS=$(docker logs tlc-core-orchestrator-1 --tail 50 2>&1)
128
+ if echo "$ORCH_LOGS" | grep -q "Session monitor started"; then
129
+ echo " Session Monitor: ✅ active (10s interval)"
130
+ else
131
+ echo " Session Monitor: ❌ not running — orchestrator needs restart"
132
+ ERRORS=$((ERRORS + 1))
133
+ fi
134
+ if echo "$ORCH_LOGS" | grep -q "Watchdog started\|watchdog.*Started"; then
135
+ echo " Watchdog: ✅ active (60s interval)"
136
+ else
137
+ echo " Watchdog: ⚠️ not detected in logs"
138
+ WARNINGS=$((WARNINGS + 1))
139
+ fi
140
+ else
141
+ if command -v tlc-core >/dev/null 2>&1; then
142
+ echo " Orchestrator: ❌ not running — starting..."
143
+ tlc-core start > /dev/null 2>&1 &
144
+ for i in 1 2 3 4 5; do
145
+ sleep 3
146
+ curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1 && break
147
+ done
148
+ if curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
149
+ echo " Orchestrator: ✅ started"
150
+ else
151
+ echo " Orchestrator: ❌ failed to start. Run 'tlc-core start' manually."
152
+ ERRORS=$((ERRORS + 1))
153
+ fi
154
+ else
155
+ echo " Orchestrator: ❌ not installed"
156
+ ERRORS=$((ERRORS + 1))
157
+ fi
158
+ fi
159
+
160
+ # ─── 5. LLM CLI Versions ─────────────────────────────────
161
+ CLAUDE_VER=$(claude --version 2>/dev/null | head -1 || echo "not installed")
162
+ CODEX_VER=$(codex --version 2>/dev/null | head -1 || echo "not installed")
163
+ GEMINI_VER=$(gemini --version 2>/dev/null | head -1 || echo "not installed")
164
+
165
+ PROVIDER_COUNT=0
166
+ PROVIDER_LIST=""
167
+ if [ "$CLAUDE_VER" != "not installed" ]; then
168
+ PROVIDER_COUNT=$((PROVIDER_COUNT + 1))
169
+ PROVIDER_LIST="${PROVIDER_LIST}claude(${CLAUDE_VER}), "
170
+ fi
171
+ if [ "$CODEX_VER" != "not installed" ]; then
172
+ PROVIDER_COUNT=$((PROVIDER_COUNT + 1))
173
+ PROVIDER_LIST="${PROVIDER_LIST}codex(${CODEX_VER}), "
111
174
  fi
112
- if [ -n "$SPOOL_DRAIN" ]; then
113
- node -e "$SPOOL_DRAIN" 2>/dev/null
175
+ if [ "$GEMINI_VER" != "not installed" ]; then
176
+ PROVIDER_COUNT=$((PROVIDER_COUNT + 1))
177
+ PROVIDER_LIST="${PROVIDER_LIST}gemini(${GEMINI_VER}), "
114
178
  fi
179
+ PROVIDER_LIST=$(echo "$PROVIDER_LIST" | sed 's/, $//')
115
180
 
116
- # ─── LLM Router: Probe Providers ─────────────────────────
117
- #
118
- # Writes .tlc/.router-state.json with provider availability.
119
- # Skills read this file instead of probing from scratch.
120
- # State has a TTL — re-probed if older than 1 hour.
181
+ if [ "$PROVIDER_COUNT" -gt 0 ]; then
182
+ echo " LLM Providers: ✅ ${PROVIDER_COUNT} available — ${PROVIDER_LIST}"
183
+ else
184
+ echo " LLM Providers: ❌ none found"
185
+ ERRORS=$((ERRORS + 1))
186
+ fi
121
187
 
188
+ # Write router state
122
189
  STATE_DIR="$PROJECT_DIR/.tlc"
123
190
  STATE_FILE="$STATE_DIR/.router-state.json"
124
191
  mkdir -p "$STATE_DIR"
125
192
 
126
- # Check if state is fresh (less than 1 hour old)
127
- STALE=true
128
- if [ -f "$STATE_FILE" ]; then
129
- STATE_AGE=$(( $(date +%s) - $(stat -f %m "$STATE_FILE" 2>/dev/null || stat -c %Y "$STATE_FILE" 2>/dev/null || echo 0) ))
130
- if [ "$STATE_AGE" -lt 3600 ]; then
131
- STALE=false
132
- fi
133
- fi
134
-
135
- if [ "$STALE" = true ]; then
136
- # Probe each provider
137
- CLAUDE_PATH=$(command -v claude 2>/dev/null || echo "")
138
- CODEX_PATH=$(command -v codex 2>/dev/null || echo "")
139
- GEMINI_PATH=$(command -v gemini 2>/dev/null || echo "")
140
-
141
- CLAUDE_OK="false"
142
- CODEX_OK="false"
143
- GEMINI_OK="false"
144
-
145
- [ -n "$CLAUDE_PATH" ] && CLAUDE_OK="true"
146
- [ -n "$CODEX_PATH" ] && CODEX_OK="true"
147
- [ -n "$GEMINI_PATH" ] && GEMINI_OK="true"
148
-
149
- # Count available
150
- AVAILABLE=0
151
- [ "$CLAUDE_OK" = "true" ] && AVAILABLE=$((AVAILABLE + 1))
152
- [ "$CODEX_OK" = "true" ] && AVAILABLE=$((AVAILABLE + 1))
153
- [ "$GEMINI_OK" = "true" ] && AVAILABLE=$((AVAILABLE + 1))
154
-
155
- # Read configured providers from .tlc.json
156
- CONFIGURED_PROVIDERS=""
157
- if command -v jq >/dev/null 2>&1; then
158
- CONFIGURED_PROVIDERS=$(jq -r '.router.providers // {} | keys[]' .tlc.json 2>/dev/null | tr '\n' ',' | sed 's/,$//')
159
- fi
193
+ CLAUDE_OK="false"; CODEX_OK="false"; GEMINI_OK="false"
194
+ [ "$CLAUDE_VER" != "not installed" ] && CLAUDE_OK="true"
195
+ [ "$CODEX_VER" != "not installed" ] && CODEX_OK="true"
196
+ [ "$GEMINI_VER" != "not installed" ] && GEMINI_OK="true"
160
197
 
161
- # Write state file
162
- cat > "$STATE_FILE" <<STATEEOF
198
+ cat > "$STATE_FILE" <<STATEEOF
163
199
  {
164
200
  "probed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
165
201
  "ttl_seconds": 3600,
166
202
  "providers": {
167
- "claude": {
168
- "available": $CLAUDE_OK,
169
- "path": "$CLAUDE_PATH"
170
- },
171
- "codex": {
172
- "available": $CODEX_OK,
173
- "path": "$CODEX_PATH"
174
- },
175
- "gemini": {
176
- "available": $GEMINI_OK,
177
- "path": "$GEMINI_PATH"
178
- }
203
+ "claude": { "available": $CLAUDE_OK, "version": "$CLAUDE_VER" },
204
+ "codex": { "available": $CODEX_OK, "version": "$CODEX_VER" },
205
+ "gemini": { "available": $GEMINI_OK, "version": "$GEMINI_VER" }
179
206
  },
180
- "summary": {
181
- "available_count": $AVAILABLE,
182
- "configured": "$CONFIGURED_PROVIDERS"
183
- }
207
+ "summary": { "available_count": $PROVIDER_COUNT }
184
208
  }
185
209
  STATEEOF
186
210
 
187
- # Report to Claude
188
- if [ "$AVAILABLE" -gt 0 ]; then
189
- PROVIDERS_LIST=""
190
- [ "$CLAUDE_OK" = "true" ] && PROVIDERS_LIST="${PROVIDERS_LIST}claude, "
191
- [ "$CODEX_OK" = "true" ] && PROVIDERS_LIST="${PROVIDERS_LIST}codex, "
192
- [ "$GEMINI_OK" = "true" ] && PROVIDERS_LIST="${PROVIDERS_LIST}gemini, "
193
- PROVIDERS_LIST=$(echo "$PROVIDERS_LIST" | sed 's/, $//')
194
- echo "LLM Router: ${AVAILABLE} providers available (${PROVIDERS_LIST}). State written to .tlc/.router-state.json. All routing skills MUST read this file for provider availability — do not probe manually."
195
- else
196
- echo "LLM Router: No external providers detected. Running Claude-only mode. Install codex or gemini for multi-LLM reviews."
197
- fi
211
+ # ─── 6. Git + Remote Sync ────────────────────────────────
212
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null)
213
+ BEHIND=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo "?")
214
+ AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo "?")
215
+
216
+ if [ "$BEHIND" = "0" ] && [ "$AHEAD" = "0" ]; then
217
+ echo " Git (${CURRENT_BRANCH}): in sync with remote"
218
+ elif [ "$BEHIND" != "?" ] && [ "$BEHIND" -gt 0 ] 2>/dev/null; then
219
+ echo " Git (${CURRENT_BRANCH}): ⚠️ ${BEHIND} commits behind remote — run: git pull"
220
+ WARNINGS=$((WARNINGS + 1))
221
+ elif [ "$AHEAD" != "?" ] && [ "$AHEAD" -gt 0 ] 2>/dev/null; then
222
+ echo " Git (${CURRENT_BRANCH}): ⚠️ ${AHEAD} commits ahead — push pending"
223
+ WARNINGS=$((WARNINGS + 1))
198
224
  else
199
- # State is fresh just report it
200
- if command -v jq >/dev/null 2>&1 && [ -f "$STATE_FILE" ]; then
201
- COUNT=$(jq -r '.summary.available_count' "$STATE_FILE" 2>/dev/null)
202
- echo "LLM Router: ${COUNT} providers available (cached). State at .tlc/.router-state.json."
225
+ echo " Git (${CURRENT_BRANCH}): ⚠️ remote tracking unknown"
226
+ WARNINGS=$((WARNINGS + 1))
227
+ fi
228
+
229
+ # ─── 7. Workspace Awareness ──────────────────────────────
230
+ WORKSPACE_FILE=""
231
+ CHECK_DIR="$PROJECT_DIR"
232
+ while [ "$CHECK_DIR" != "/" ]; do
233
+ if [ -f "$CHECK_DIR/.tlc-workspace.json" ]; then
234
+ WORKSPACE_FILE="$CHECK_DIR/.tlc-workspace.json"
235
+ break
203
236
  fi
237
+ CHECK_DIR=$(dirname "$CHECK_DIR")
238
+ done
239
+
240
+ if [ -n "$WORKSPACE_FILE" ]; then
241
+ WORKSPACE_NAME=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$WORKSPACE_FILE','utf8')).workspace)}catch{console.log('?')}" 2>/dev/null)
242
+ REPO_COUNT=$(node -e "try{console.log(Object.keys(JSON.parse(require('fs').readFileSync('$WORKSPACE_FILE','utf8')).repos).length)}catch{console.log('?')}" 2>/dev/null)
243
+ echo " Workspace: ✅ ${WORKSPACE_NAME} (${REPO_COUNT} repos)"
244
+
245
+ # Check each repo's sync status
246
+ WORKSPACE_ROOT=$(dirname "$WORKSPACE_FILE")
247
+ node -e "
248
+ const fs = require('fs');
249
+ const path = require('path');
250
+ const { execSync } = require('child_process');
251
+ const wsRoot = process.argv[1];
252
+ const ws = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
253
+ for (const [name, repo] of Object.entries(ws.repos)) {
254
+ const rp = path.resolve(wsRoot, repo.path);
255
+ try {
256
+ fs.accessSync(rp);
257
+ const branch = execSync('git -C ' + JSON.stringify(rp) + ' branch --show-current', { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
258
+ let status = 'unknown';
259
+ try {
260
+ const behind = execSync('git -C ' + JSON.stringify(rp) + ' rev-list --count HEAD..@{u}', { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
261
+ status = behind === '0' ? 'in sync' : behind + ' behind';
262
+ } catch { status = 'no remote tracking'; }
263
+ console.log(' [' + repo.prefix + '] ' + branch + ': ' + status);
264
+ } catch {
265
+ console.log(' [' + repo.prefix + '] not cloned');
266
+ }
267
+ }
268
+ " "$WORKSPACE_ROOT" "$WORKSPACE_FILE" 2>/dev/null
269
+ else
270
+ echo " Workspace: ⚠️ no .tlc-workspace.json found (single-repo mode)"
271
+ WARNINGS=$((WARNINGS + 1))
204
272
  fi
205
273
 
206
- # ─── Health Check (runs AFTER router probe so provider state is fresh) ───
207
- HEALTH_MOD=""
208
- if node -e "require('tlc-claude-code/server/lib/health-check')" 2>/dev/null; then
209
- HEALTH_MOD="tlc-claude-code/server/lib/health-check"
210
- elif [ -f "$PROJECT_DIR/server/lib/health-check.js" ]; then
211
- HEALTH_MOD="$PROJECT_DIR/server/lib/health-check"
274
+ # ─── 8. CI/CD Status ─────────────────────────────────────
275
+ if command -v gh >/dev/null 2>&1; then
276
+ LAST_RUN=$(gh run list --limit 1 --json conclusion,status,name -q '.[0]' 2>/dev/null)
277
+ if [ -n "$LAST_RUN" ]; then
278
+ RUN_STATUS=$(echo "$LAST_RUN" | node -e "try{const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.conclusion||d.status)}catch{console.log('?')}" 2>/dev/null)
279
+ RUN_NAME=$(echo "$LAST_RUN" | node -e "try{const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(d.name)}catch{console.log('?')}" 2>/dev/null)
280
+ if [ "$RUN_STATUS" = "success" ]; then
281
+ echo " CI/CD: ✅ last run passed (${RUN_NAME})"
282
+ elif [ "$RUN_STATUS" = "failure" ]; then
283
+ echo " CI/CD: ❌ last run FAILED (${RUN_NAME})"
284
+ ERRORS=$((ERRORS + 1))
285
+ else
286
+ echo " CI/CD: ⏳ ${RUN_STATUS} (${RUN_NAME})"
287
+ fi
288
+ else
289
+ echo " CI/CD: ⚠️ no recent runs"
290
+ fi
291
+ else
292
+ echo " CI/CD: ⚠️ gh CLI not installed"
293
+ WARNINGS=$((WARNINGS + 1))
212
294
  fi
213
- if [ -n "$HEALTH_MOD" ]; then
214
- node -e "require('$HEALTH_MOD').runHealthChecks('$PROJECT_DIR').then(r => r.warnings.forEach(w => console.error(w)))" 2>/dev/null
295
+
296
+ # ─── Summary ─────────────────────────────────────────────
297
+ echo "────────────────────────────────────────────────────"
298
+ if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
299
+ echo "All systems healthy. ✅"
300
+ elif [ "$ERRORS" -eq 0 ]; then
301
+ echo "${WARNINGS} warning(s), 0 errors. Systems operational."
302
+ else
303
+ echo "⚠️ ${ERRORS} error(s), ${WARNINGS} warning(s). Check above."
215
304
  fi
216
305
 
217
306
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlc-claude-code",
3
- "version": "2.9.1",
3
+ "version": "2.9.2",
4
4
  "description": "TLC - Test Led Coding for Claude Code",
5
5
  "bin": {
6
6
  "tlc-claude-code": "./bin/install.js",