tlc-claude-code 2.9.0 → 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.
@@ -174,50 +174,85 @@ If orchestrator is down, dispatch directly via tmux on the host:
174
174
 
175
175
  ```bash
176
176
  tmux new-session -d -s <task-id> -c $(pwd)
177
- tmux send-keys -t <task-id> "/opt/homebrew/bin/codex --full-auto '<prompt>'" Enter
177
+ tmux send-keys -t <task-id> "/opt/homebrew/bin/codex --dangerously-bypass-approvals-and-sandbox '<prompt>'" Enter
178
178
  ```
179
179
 
180
- ### Step O4: Report and Free
180
+ ### Step O4: Report and Monitor
181
181
 
182
- After dispatching all tasks, report:
182
+ After dispatching all tasks, report the initial state:
183
183
 
184
184
  ```
185
185
  Dispatched N tasks to orchestrator:
186
- ses_abc123 Task 1: Create schema codex running monitor=running
187
- ses_def456 Task 2: Add validation codex running monitor=running
186
+ ses_abc123 Task 1: Create schema codex running
187
+ ses_def456 Task 2: Add validation codex running
188
188
 
189
- Run /tlc:status to check progress.
190
- I'm free — what would you like to work on?
189
+ Monitoring for completion...
191
190
  ```
192
191
 
193
- Include each session's current monitor state in the status display. When available, prefer the same session-status formatting used by orchestration status views so monitor values stay consistent across commands.
192
+ **Do NOT stop here. Do NOT say "I'm free". Enter the monitoring loop immediately.**
194
193
 
195
- **STOP HERE.** Do not write code. Do not execute inline mode. Claude is the PM, not the coder.
194
+ ### Step O5: Auto-Monitor Loop (Mandatory)
196
195
 
197
- ### Step O5: Monitor (When User Asks)
196
+ After dispatching, poll all sessions every 30 seconds until all reach terminal states. This loop runs automatically — do not wait for the user to ask.
198
197
 
199
- When the user asks for status or agents complete, check:
198
+ ```
199
+ Poll cycle:
200
+ 1. For each dispatched session, check: GET /sessions/:id/status (or tmux capture-pane)
201
+ 2. Classify each session:
202
+ - completed → ready for merge-back
203
+ - stuck → send Enter once, re-check next cycle. After 3 attempts, ask user: "Session stuck. Retry or cancel?"
204
+ - budget_exhausted → ask user: "Budget exhausted. Re-dispatch or skip?"
205
+ - crashed / errored → log error, mark task as failed, continue with others
206
+ - timed_out → log timeout, mark task as failed
207
+ - running → continue monitoring
208
+ 3. When ANY session completes, report immediately: "✅ ses_abc123 Task 1 complete"
209
+ 4. When ALL dispatched sessions reach terminal states, exit the loop
210
+ ```
200
211
 
201
- ```bash
202
- # Check all sessions
203
- curl -s http://localhost:3100/sessions | node -e "
204
- const d=require('fs').readFileSync('/dev/stdin','utf8');
205
- JSON.parse(d).forEach(s=>console.log(s.id, s.status, s.project));
206
- "
212
+ **Use Bash `sleep 30` between poll cycles. Do not use background tasks for this — poll sequentially in the main conversation so results are visible.**
207
213
 
208
- # Check specific session output (inside container)
209
- docker exec tlc-agent-runner tmux capture-pane -t <session_id> -p -S -20
210
- ```
214
+ Surface progress at natural milestones:
215
+ - When a session completes: one-line status update
216
+ - When all sessions in a parallel batch complete: summary + trigger next batch
217
+ - When a session fails: immediate report with error details
211
218
 
212
- Auto-approve any prompts found in tmux sessions:
219
+ ### Step O5a: Dispatch Dependent Tasks
220
+
221
+ When a batch of tasks completes, check the dependency graph for newly unblocked tasks. Dispatch them immediately without asking.
213
222
 
214
- ```bash
215
- docker exec tlc-agent-runner tmux send-keys -t <session_id> Enter
216
223
  ```
224
+ Example:
225
+ Tasks 1+2 dispatched (parallel, no deps)
226
+ Task 1 completes → check if any task depends only on Task 1 → dispatch it
227
+ Task 2 completes → check if remaining tasks are unblocked → dispatch them
228
+ Tasks 3+4 now unblocked → dispatch immediately
229
+ ...continue until all tasks dispatched and completed
230
+ ```
231
+
232
+ ### Step O5b: Auto Merge-Back
233
+
234
+ When all tasks in the phase complete (or all non-failed tasks complete):
235
+
236
+ 1. Pull all commits from container worktrees to the host phase branch
237
+ 2. Run the full test suite: `cd server && npx vitest run` (or project's test command)
238
+ 3. If tests pass → continue to Step O5c
239
+ 4. If tests fail → report failures, attempt auto-fix (one round), re-run tests
240
+
241
+ ### Step O5c: Auto Review + PR
242
+
243
+ After tests pass:
244
+
245
+ 1. Run auto-review (same checks as Step 10 in Inline Mode)
246
+ 2. If review passes → push branch and create PR (ask user before pushing per project rules)
247
+ 3. If review fails → fix issues, re-run tests, re-review (max 3 iterations)
217
248
 
218
249
  ### Step O6: Cleanup
219
250
 
220
- When all tasks complete, remove `.tlc/.build-routing-active` and report results.
251
+ After PR is created (or phase completes):
252
+ - Remove `.tlc/.build-routing-active`
253
+ - Remove `.tlc/.active-sessions.json`
254
+ - Mark completed tasks `[x]` in PLAN.md
255
+ - Report final summary
221
256
 
222
257
  ## INLINE MODE
223
258
 
@@ -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.0",
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",