tlc-claude-code 2.9.1 → 2.10.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.
@@ -106,14 +106,14 @@ process.stdout.write(JSON.stringify(result));" 2>/dev/null
106
106
  After `resolveRouting` returns, immediately write `.tlc/.build-routing-active` with the active provider name from `models[0]` before doing any other build work. Remove `.tlc/.build-routing-active` on completion, cancellation, or failure cleanup.
107
107
 
108
108
  **Routing decision:**
109
- - If routing says external provider (`models[0] !== 'claude'`), follow **ONLY Orchestrator Mode** below.
110
- - If routing says Claude (`models[0] === 'claude'`), follow **Inline Mode** below.
109
+ - **ALWAYS use Orchestrator Mode.** Claude never writes code inline. The routing config determines which provider the orchestrator dispatches TO (codex, claude session, gemini), but Claude in this conversation is always the PM.
110
+ - If the orchestrator is unreachable, auto-recover it (restart container, deploy latest code). If recovery fails, STOP do not fall back to inline code.
111
111
 
112
- ## ORCHESTRATOR MODE
112
+ ## ORCHESTRATOR MODE (ALWAYS)
113
113
 
114
- Use this mode only when `models[0]` is **NOT** `claude`.
114
+ Claude is the **project manager**. Claude reads the plan, breaks tasks into small prompts, and dispatches each to the tlc-core orchestrator. Claude does NOT write code. This is not conditional on routing — it is always the mode.
115
115
 
116
- Claude is the **project manager**. Claude reads the plan, breaks tasks into small prompts, and dispatches each to the tlc-core orchestrator. Claude does NOT write code.
116
+ The `models[0]` value from routing determines which provider the orchestrator uses (`codex`, `claude`, `gemini`), not whether Claude writes code inline.
117
117
 
118
118
  ### Step O1: Read the Plan
119
119
 
@@ -254,11 +254,9 @@ After PR is created (or phase completes):
254
254
  - Mark completed tasks `[x]` in PLAN.md
255
255
  - Report final summary
256
256
 
257
- ## INLINE MODE
257
+ ## AGENT INSTRUCTIONS (Reference for Dispatched Sessions)
258
258
 
259
- Use this mode only when `models[0]` **IS** `claude`.
260
-
261
- The existing build instructions below are the Inline Mode instructions and should be followed unchanged.
259
+ **These instructions are NOT for Claude in the main conversation.** They are the reference for what the dispatched orchestrator agent (Codex, Claude session, or Gemini) should do inside its tmux session. Claude packages these into the dispatch prompt — Claude does not execute them inline.
262
260
 
263
261
  ## Engineering Standards
264
262
 
@@ -19,9 +19,62 @@ For brand new projects with no code, use `/tlc:new-project` instead.
19
19
 
20
20
  ## Process
21
21
 
22
- ### 0. Upgrade Check (runs first, always)
22
+ ### 0. Orchestrator Enforcement (runs first, always)
23
23
 
24
- **Before anything else**, check if this is already a TLC project:
24
+ **Before anything else**, verify the orchestrator is available. All code execution goes through it — no exceptions.
25
+
26
+ ```bash
27
+ # Check orchestrator health
28
+ if curl -sf --max-time 2 http://localhost:3100/health > /dev/null 2>&1; then
29
+ echo "Orchestrator: ✅ healthy"
30
+ else
31
+ echo "Orchestrator: ❌ not running — attempting recovery..."
32
+ # Try to find and restart the orchestrator container
33
+ ORCH_CONTAINER=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep "orchestrator" | head -1)
34
+ if [ -n "$ORCH_CONTAINER" ]; then
35
+ docker start "$ORCH_CONTAINER" 2>/dev/null
36
+ sleep 3
37
+ fi
38
+ # If still down and tlc-core source exists, deploy and start
39
+ if ! curl -sf --max-time 2 http://localhost:3100/health > /dev/null 2>&1; then
40
+ if command -v tlc-core >/dev/null 2>&1; then
41
+ tlc-core start &
42
+ sleep 5
43
+ fi
44
+ fi
45
+ # Final check
46
+ if curl -sf --max-time 2 http://localhost:3100/health > /dev/null 2>&1; then
47
+ echo "Orchestrator: ✅ recovered"
48
+ else
49
+ echo "Orchestrator: ❌ FAILED — code execution will not work"
50
+ echo "Install tlc-core: git clone git@github.com:TwentyTwoLabs22/tlc-core.git && cd cli && npm i && npm link"
51
+ echo "Then run: tlc-core start"
52
+ fi
53
+ fi
54
+ ```
55
+
56
+ Also verify CodeDB and agent runner:
57
+
58
+ ```bash
59
+ # CodeDB
60
+ if ! curl -sf --max-time 1 http://localhost:7719/health > /dev/null 2>&1; then
61
+ if command -v codedb >/dev/null 2>&1; then
62
+ codedb serve . &
63
+ echo "CodeDB: ✅ started"
64
+ else
65
+ echo "CodeDB: ❌ not installed — run: npx tlc-claude-code"
66
+ fi
67
+ fi
68
+
69
+ # Agent runner container
70
+ if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q "tlc-agent-runner"; then
71
+ docker start tlc-agent-runner 2>/dev/null && echo "Agent Runner: ✅ started" || echo "Agent Runner: ❌ not available"
72
+ fi
73
+ ```
74
+
75
+ ### 0b. Upgrade Check
76
+
77
+ **After orchestrator is verified**, check if this is already a TLC project:
25
78
 
26
79
  ```bash
27
80
  # Check for existing TLC markers
@@ -100,14 +100,12 @@ process.stdout.write(JSON.stringify(result));" 2>/dev/null
100
100
  After `resolveRouting` returns, immediately write `.tlc/.quick-routing-active` with the active provider name from `models[0]` before doing any other quick work. Remove `.tlc/.quick-routing-active` on completion, cancellation, or failure cleanup.
101
101
 
102
102
  **Routing decision:**
103
- - If routing says external provider (`models[0] !== 'claude'`), follow **ONLY ORCHESTRATOR MODE** below.
104
- - If routing says Claude (`models[0] === 'claude'`), follow **INLINE MODE** below.
103
+ - **ALWAYS use Orchestrator Mode.** Claude never writes code inline. Routing determines which provider the orchestrator dispatches to.
104
+ - If orchestrator is down, auto-recover. If recovery fails, STOP do not fall back to inline.
105
105
 
106
- ## ORCHESTRATOR MODE
106
+ ## ORCHESTRATOR MODE (ALWAYS)
107
107
 
108
- Use this mode only when `models[0]` is **NOT** `claude`.
109
-
110
- Claude does not execute the quick-task instructions inline in this path. Claude acts as the orchestrator for the routed provider run:
108
+ Claude acts as the orchestrator dispatches the quick task to the orchestrator, monitors completion, verifies result. Claude does NOT write code.
111
109
 
112
110
  1. Treat Claude as the orchestrator for a small, test-first routed task rather than executing the work inline.
113
111
  2. Gather the minimal project context needed for the quick task, then package a focused prompt with the task request, expected failing test, implementation scope, and verification target.
@@ -123,12 +121,9 @@ Claude does not execute the quick-task instructions inline in this path. Claude
123
121
  - If the provider returns incomplete code or missing tests, issue a targeted retry for the missing part.
124
122
  7. When finished, display the routed provider result, persist any captured memory, remove `.tlc/.quick-routing-active`, and stop. Do **not** execute Inline Mode afterward.
125
123
 
126
- ## INLINE MODE
127
-
128
- Use this mode only when `models[0]` **IS** `claude`.
129
-
130
- The existing quick-task instructions below are the Inline Mode instructions and should be followed unchanged.
124
+ ## AGENT INSTRUCTIONS (Reference for Dispatched Sessions)
131
125
 
126
+ **These instructions are NOT for Claude in the main conversation.** They are packaged into the dispatch prompt for the orchestrator agent.
132
127
 
133
128
  ## Engineering Standards
134
129
 
@@ -181,6 +181,31 @@ Only mention upgrade details in `--verbose` mode.
181
181
 
182
182
  ## One-Pass Detection
183
183
 
184
+ ### Step 0: Quick Health Check
185
+
186
+ Before phase detection, run a fast health check (no auto-recovery — that's the SessionStart hook's job). Just report status in one line:
187
+
188
+ ```bash
189
+ # Check key systems in parallel (each with 1s timeout)
190
+ CODEDB=$(curl -sf --max-time 1 http://localhost:7719/health > /dev/null 2>&1 && echo "✅" || echo "❌")
191
+ ORCH=$(curl -sf --max-time 1 http://localhost:3100/health > /dev/null 2>&1 && echo "✅" || echo "❌")
192
+ RUNNER=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -q "tlc-agent-runner" && echo "✅" || echo "❌")
193
+ ```
194
+
195
+ Print one compact line:
196
+
197
+ ```
198
+ Systems: CodeDB ${CODEDB} | Orchestrator ${ORCH} | Agent Runner ${RUNNER}
199
+ ```
200
+
201
+ If any system is ❌, **auto-recover immediately** before continuing:
202
+
203
+ 1. CodeDB ❌ → run `codedb serve . &` (or `npx tlc-claude-code` if binary missing), wait 2s, re-check
204
+ 2. Orchestrator ❌ → find orchestrator container (`docker ps | grep orchestrator`), deploy latest code from `../tlc-core/orchestrator/`, restart container, wait 3s, re-check
205
+ 3. Agent Runner ❌ → `docker start tlc-agent-runner` if stopped, report if container doesn't exist
206
+
207
+ After recovery attempts, print updated status. Only continue to phase detection after all recoverable systems are up. If recovery fails, print the failure and continue anyway — don't block indefinitely.
208
+
184
209
  ### Step 1: Load Core Context
185
210
 
186
211
  Read, in this order:
@@ -256,11 +281,22 @@ Backward compatibility:
256
281
 
257
282
  ## Output Format
258
283
 
259
- Normal output must stay within 3-5 lines.
284
+ Normal output must stay compact health line + phase status + action.
260
285
 
261
286
  Use this pattern:
262
287
 
263
288
  ```text
289
+ Systems: CodeDB ✅ | Orchestrator ✅ | Agent Runner ✅
290
+ TLC v{version} | Phase {PHASE_ID}: {Name} | {done}/{total} tasks done
291
+ Next: {action summary}
292
+ Running /tlc:{command} {PHASE_ID}...
293
+ ```
294
+
295
+ If any system is down:
296
+
297
+ ```text
298
+ Systems: CodeDB ✅ | Orchestrator ❌ | Agent Runner ✅
299
+ ⚠️ Orchestrator down — run /tlc:init to recover
264
300
  TLC v{version} | Phase {PHASE_ID}: {Name} | {done}/{total} tasks done
265
301
  Next: {action summary}
266
302
  Running /tlc:{command} {PHASE_ID}...
@@ -269,6 +305,7 @@ Running /tlc:{command} {PHASE_ID}...
269
305
  If there is nothing safe to auto-run:
270
306
 
271
307
  ```text
308
+ Systems: CodeDB ✅ | Orchestrator ✅ | Agent Runner ✅
272
309
  TLC v{version} | All phases complete
273
310
  Next: release or start the next milestone
274
311
  Run /tlc:complete or /tlc:new-milestone
@@ -338,7 +375,8 @@ Run /tlc:complete or /tlc:new-milestone
338
375
  ## Example
339
376
 
340
377
  ```text
341
- TLC v2.4.2 | Phase TLC-8: Auth System | 3/5 tasks done
378
+ Systems: CodeDB | Orchestrator | Agent Runner
379
+ TLC v2.9.2 | Phase TLC-8: Auth System | 3/5 tasks done
342
380
  Next: build task 4 (JWT middleware)
343
381
  Running /tlc:build TLC-8...
344
382
  ```
@@ -1,86 +1,98 @@
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 ──────────────────────────────"
19
+
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)
12
24
 
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}"
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_VER} auto-updating..."
30
+ npm i -g tlc-claude-code@latest > /dev/null 2>&1
31
+ NEW_VER=$(node -e "try{console.log(require('tlc-claude-code/package.json').version)}catch{console.log('?')}" 2>/dev/null)
32
+ if [ "$NEW_VER" = "$LATEST_VER" ]; then
33
+ echo " TLC Package: ✅ updated to v${NEW_VER}"
34
+ else
35
+ echo " TLC Package: ⚠️ update failed — still on v${INSTALLED_VER}"
36
+ WARNINGS=$((WARNINGS + 1))
21
37
  fi
38
+ else
39
+ echo " TLC Package: ✅ v${INSTALLED_VER} (latest)"
22
40
  fi
23
41
 
24
- PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
25
-
26
- # ─── TLC Server ───────────────────────────────────────────
42
+ # ─── 2. TLC Memory System ────────────────────────────────
43
+ mkdir -p "$PROJECT_DIR/.tlc/memory/team/decisions" \
44
+ "$PROJECT_DIR/.tlc/memory/team/gotchas" \
45
+ "$PROJECT_DIR/.tlc/memory/.local/sessions" 2>/dev/null
27
46
 
28
- TLC_PORT="${TLC_PORT:-3147}"
29
47
  if curl -sf --max-time 1 "http://localhost:${TLC_PORT}/api/health" > /dev/null 2>&1; then
30
- : # Server is running
48
+ echo " TLC Server: ✅ running (:${TLC_PORT})"
49
+ # Drain spool
50
+ SPOOL_DRAIN=""
51
+ if node -e "require('tlc-claude-code/server/lib/capture/spool-processor')" 2>/dev/null; then
52
+ SPOOL_DRAIN="require('tlc-claude-code/server/lib/capture/spool-processor').processSpool('$PROJECT_DIR')"
53
+ elif [ -f "$PROJECT_DIR/server/lib/capture/spool-processor.js" ]; then
54
+ SPOOL_DRAIN="require('$PROJECT_DIR/server/lib/capture/spool-processor').processSpool('$PROJECT_DIR')"
55
+ fi
56
+ [ -n "$SPOOL_DRAIN" ] && node -e "$SPOOL_DRAIN" 2>/dev/null
31
57
  else
32
58
  PLIST="$HOME/Library/LaunchAgents/com.tlc.server.plist"
33
-
34
59
  if [ -f "$PLIST" ]; then
35
60
  launchctl kickstart -k "gui/$(id -u)/com.tlc.server" 2>/dev/null
36
61
  elif [ -f "$PROJECT_DIR/server/index.js" ]; then
37
62
  nohup node "$PROJECT_DIR/server/index.js" > "$HOME/.tlc/logs/server.log" 2>&1 &
38
63
  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
64
+ sleep 2
65
+ if curl -sf --max-time 1 "http://localhost:${TLC_PORT}/api/health" > /dev/null 2>&1; then
66
+ echo " TLC Server: ✅ started (:${TLC_PORT})"
67
+ else
68
+ echo " TLC Server: ❌ failed to start"
69
+ ERRORS=$((ERRORS + 1))
70
+ fi
44
71
  fi
45
72
 
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)"
73
+ SPOOL_FILE="$PROJECT_DIR/.tlc/memory/.spool.jsonl"
74
+ SPOOL_COUNT=0
75
+ if [ -f "$SPOOL_FILE" ]; then
76
+ SPOOL_COUNT=$(wc -l < "$SPOOL_FILE" 2>/dev/null || echo "0")
77
+ fi
78
+ SPOOL_COUNT=$(echo "$SPOOL_COUNT" | tr -d ' ')
79
+ if [ "$SPOOL_COUNT" -gt 0 ] 2>/dev/null; then
80
+ echo " Memory Spool: ⚠️ ${SPOOL_COUNT} entries pending drain"
81
+ WARNINGS=$((WARNINGS + 1))
54
82
  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
83
+ echo " Memory: ✅ ready (spool empty)"
73
84
  fi
74
85
 
75
- # ─── CodeDB (codebase awareness) ─────────────────────
86
+ # ─── 3. CodeDB ───────────────────────────────────────────
76
87
  if command -v codedb >/dev/null 2>&1; then
77
88
  if curl -sf --max-time 1 "http://localhost:7719/health" > /dev/null 2>&1; then
78
- echo "codedb: running"
89
+ 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 "?")
90
+ echo " CodeDB: ✅ running (${CODEDB_FILES} files indexed)"
79
91
  else
80
92
  codedb serve "$PROJECT_DIR" > /dev/null 2>&1 &
81
- echo "codedb: started indexing $PROJECT_DIR"
93
+ echo " CodeDB: started indexing"
82
94
  fi
83
- # Ensure .mcp.json exists for Claude Code MCP discovery
95
+ # Ensure .mcp.json
84
96
  if [ ! -f "$PROJECT_DIR/.mcp.json" ]; then
85
97
  cat > "$PROJECT_DIR/.mcp.json" <<'MCPEOF'
86
98
  {
@@ -92,126 +104,269 @@ if command -v codedb >/dev/null 2>&1; then
92
104
  }
93
105
  }
94
106
  MCPEOF
95
- echo "codedb: created .mcp.json"
107
+ echo " CodeDB MCP: created .mcp.json"
108
+ fi
109
+ else
110
+ # Auto-install CodeDB binary
111
+ echo " CodeDB: ⏳ not installed — auto-installing..."
112
+ if npx -y tlc-claude-code 2>/dev/null | tail -1; then
113
+ if command -v codedb >/dev/null 2>&1; then
114
+ codedb serve "$PROJECT_DIR" > /dev/null 2>&1 &
115
+ echo " CodeDB: ✅ installed and started"
116
+ else
117
+ echo " CodeDB: ❌ install failed"
118
+ ERRORS=$((ERRORS + 1))
119
+ fi
120
+ else
121
+ echo " CodeDB: ❌ install failed"
122
+ ERRORS=$((ERRORS + 1))
96
123
  fi
97
124
  fi
98
125
 
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')"
126
+ # ─── 4. tlc-core Orchestrator + Containers ────────────────
127
+ if [ "${TLC_CORE_AUTOSTART}" = "false" ]; then
128
+ echo " Orchestrator: ⏸ disabled (TLC_CORE_AUTOSTART=false)"
129
+ elif curl -sf --max-time 2 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
130
+ ORCH_HEALTH=$(curl -sf --max-time 2 "http://localhost:${TLC_CORE_PORT}/health" 2>/dev/null)
131
+ 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)
132
+ 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)
133
+ 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)
134
+ echo " Orchestrator: ✅ healthy (pg:${PG_STATUS}, docker:${DOCKER_STATUS}, sessions:${RUNNING_SESSIONS})"
135
+
136
+ # Check agent-runner container
137
+ if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "tlc-agent-runner"; then
138
+ TMUX_COUNT=$(docker exec tlc-agent-runner tmux list-sessions 2>/dev/null | wc -l | tr -d ' ')
139
+ echo " Agent Runner: ✅ running (${TMUX_COUNT} tmux sessions)"
140
+ else
141
+ echo " Agent Runner: ❌ container not running"
142
+ ERRORS=$((ERRORS + 1))
143
+ fi
144
+
145
+ # Check session monitor + watchdog
146
+ ORCH_LOGS=$(docker logs tlc-core-orchestrator-1 --tail 50 2>&1)
147
+ MONITOR_OK=false
148
+ WATCHDOG_OK=false
149
+ if echo "$ORCH_LOGS" | grep -q "Session monitor started"; then
150
+ MONITOR_OK=true
151
+ echo " Session Monitor: ✅ active (10s interval)"
152
+ fi
153
+ if echo "$ORCH_LOGS" | grep -q "Watchdog started\|watchdog.*Started"; then
154
+ WATCHDOG_OK=true
155
+ echo " Watchdog: ✅ active (60s interval)"
156
+ fi
157
+
158
+ # Auto-recover: if monitor or watchdog missing, deploy latest code and restart
159
+ if [ "$MONITOR_OK" = false ] || [ "$WATCHDOG_OK" = false ]; then
160
+ echo " Monitor/Watchdog: ⏳ missing — deploying latest code and restarting..."
161
+ # Find orchestrator source
162
+ ORCH_SRC=""
163
+ if [ -d "$PROJECT_DIR/../tlc-core/orchestrator" ]; then
164
+ ORCH_SRC="$PROJECT_DIR/../tlc-core/orchestrator"
165
+ elif [ -d "$(dirname "$PROJECT_DIR")/tlc-core/orchestrator" ]; then
166
+ ORCH_SRC="$(dirname "$PROJECT_DIR")/tlc-core/orchestrator"
167
+ fi
168
+ if [ -n "$ORCH_SRC" ]; then
169
+ ORCH_CONTAINER=$(docker ps --format '{{.Names}}' 2>/dev/null | grep "orchestrator" | head -1)
170
+ if [ -n "$ORCH_CONTAINER" ]; then
171
+ docker cp "$ORCH_SRC/lib/." "$ORCH_CONTAINER:/app/lib/" 2>/dev/null
172
+ docker cp "$ORCH_SRC/index.js" "$ORCH_CONTAINER:/app/index.js" 2>/dev/null
173
+ docker cp "$ORCH_SRC/migrations/." "$ORCH_CONTAINER:/app/migrations/" 2>/dev/null
174
+ docker restart "$ORCH_CONTAINER" > /dev/null 2>&1
175
+ sleep 3
176
+ if curl -sf --max-time 2 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
177
+ echo " Monitor/Watchdog: ✅ restarted with latest code"
178
+ else
179
+ echo " Monitor/Watchdog: ❌ restart failed"
180
+ ERRORS=$((ERRORS + 1))
181
+ fi
182
+ else
183
+ echo " Monitor/Watchdog: ❌ orchestrator container not found"
184
+ ERRORS=$((ERRORS + 1))
185
+ fi
186
+ else
187
+ [ "$MONITOR_OK" = false ] && echo " Session Monitor: ❌ not running (tlc-core source not found)" && ERRORS=$((ERRORS + 1))
188
+ [ "$WATCHDOG_OK" = false ] && echo " Watchdog: ⚠️ not detected" && WARNINGS=$((WARNINGS + 1))
189
+ fi
190
+ fi
191
+ else
192
+ if command -v tlc-core >/dev/null 2>&1; then
193
+ echo " Orchestrator: ❌ not running — starting..."
194
+ tlc-core start > /dev/null 2>&1 &
195
+ for i in 1 2 3 4 5; do
196
+ sleep 3
197
+ curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1 && break
198
+ done
199
+ if curl -sf --max-time 1 "http://localhost:${TLC_CORE_PORT}/health" > /dev/null 2>&1; then
200
+ echo " Orchestrator: ✅ started"
201
+ else
202
+ echo " Orchestrator: ❌ failed to start. Run 'tlc-core start' manually."
203
+ ERRORS=$((ERRORS + 1))
204
+ fi
205
+ else
206
+ echo " Orchestrator: ❌ not installed"
207
+ ERRORS=$((ERRORS + 1))
208
+ fi
209
+ fi
210
+
211
+ # ─── 5. LLM CLI Versions ─────────────────────────────────
212
+ CLAUDE_VER=$(claude --version 2>/dev/null | head -1 || echo "not installed")
213
+ CODEX_VER=$(codex --version 2>/dev/null | head -1 || echo "not installed")
214
+ GEMINI_VER=$(gemini --version 2>/dev/null | head -1 || echo "not installed")
215
+
216
+ PROVIDER_COUNT=0
217
+ PROVIDER_LIST=""
218
+ if [ "$CLAUDE_VER" != "not installed" ]; then
219
+ PROVIDER_COUNT=$((PROVIDER_COUNT + 1))
220
+ PROVIDER_LIST="${PROVIDER_LIST}claude(${CLAUDE_VER}), "
111
221
  fi
112
- if [ -n "$SPOOL_DRAIN" ]; then
113
- node -e "$SPOOL_DRAIN" 2>/dev/null
222
+ if [ "$CODEX_VER" != "not installed" ]; then
223
+ PROVIDER_COUNT=$((PROVIDER_COUNT + 1))
224
+ PROVIDER_LIST="${PROVIDER_LIST}codex(${CODEX_VER}), "
114
225
  fi
226
+ if [ "$GEMINI_VER" != "not installed" ]; then
227
+ PROVIDER_COUNT=$((PROVIDER_COUNT + 1))
228
+ PROVIDER_LIST="${PROVIDER_LIST}gemini(${GEMINI_VER}), "
229
+ fi
230
+ PROVIDER_LIST=$(echo "$PROVIDER_LIST" | sed 's/, $//')
115
231
 
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.
232
+ if [ "$PROVIDER_COUNT" -gt 0 ]; then
233
+ echo " LLM Providers: ✅ ${PROVIDER_COUNT} available — ${PROVIDER_LIST}"
234
+ # Check for updates (non-blocking, just warn)
235
+ if [ "$CODEX_VER" != "not installed" ]; then
236
+ CODEX_LATEST=$(npm view @openai/codex version 2>/dev/null || echo "")
237
+ if [ -n "$CODEX_LATEST" ] && [ "$CODEX_VER" != *"$CODEX_LATEST"* ] 2>/dev/null; then
238
+ echo " Codex Update: ⚠️ latest: ${CODEX_LATEST} — run: npm i -g @openai/codex"
239
+ WARNINGS=$((WARNINGS + 1))
240
+ fi
241
+ fi
242
+ else
243
+ echo " LLM Providers: ❌ none found"
244
+ ERRORS=$((ERRORS + 1))
245
+ fi
121
246
 
247
+ # Write router state
122
248
  STATE_DIR="$PROJECT_DIR/.tlc"
123
249
  STATE_FILE="$STATE_DIR/.router-state.json"
124
250
  mkdir -p "$STATE_DIR"
125
251
 
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
252
+ CLAUDE_OK="false"; CODEX_OK="false"; GEMINI_OK="false"
253
+ [ "$CLAUDE_VER" != "not installed" ] && CLAUDE_OK="true"
254
+ [ "$CODEX_VER" != "not installed" ] && CODEX_OK="true"
255
+ [ "$GEMINI_VER" != "not installed" ] && GEMINI_OK="true"
160
256
 
161
- # Write state file
162
- cat > "$STATE_FILE" <<STATEEOF
257
+ cat > "$STATE_FILE" <<STATEEOF
163
258
  {
164
259
  "probed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
165
260
  "ttl_seconds": 3600,
166
261
  "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
- }
262
+ "claude": { "available": $CLAUDE_OK, "version": "$CLAUDE_VER" },
263
+ "codex": { "available": $CODEX_OK, "version": "$CODEX_VER" },
264
+ "gemini": { "available": $GEMINI_OK, "version": "$GEMINI_VER" }
179
265
  },
180
- "summary": {
181
- "available_count": $AVAILABLE,
182
- "configured": "$CONFIGURED_PROVIDERS"
183
- }
266
+ "summary": { "available_count": $PROVIDER_COUNT }
184
267
  }
185
268
  STATEEOF
186
269
 
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
270
+ # ─── 6. Git + Remote Sync ────────────────────────────────
271
+ # Fetch remote to get accurate ahead/behind counts
272
+ git fetch --quiet 2>/dev/null
273
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null)
274
+ BEHIND=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo "?")
275
+ AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo "?")
276
+
277
+ if [ "$BEHIND" = "0" ] && [ "$AHEAD" = "0" ]; then
278
+ echo " Git (${CURRENT_BRANCH}): ✅ in sync with remote"
279
+ elif [ "$BEHIND" != "?" ] && [ "$BEHIND" -gt 0 ] 2>/dev/null; then
280
+ echo " Git (${CURRENT_BRANCH}): ⚠️ ${BEHIND} commits behind remote — run: git pull"
281
+ WARNINGS=$((WARNINGS + 1))
282
+ elif [ "$AHEAD" != "?" ] && [ "$AHEAD" -gt 0 ] 2>/dev/null; then
283
+ echo " Git (${CURRENT_BRANCH}): ⚠️ ${AHEAD} commits ahead — push pending"
284
+ WARNINGS=$((WARNINGS + 1))
198
285
  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."
286
+ echo " Git (${CURRENT_BRANCH}): ⚠️ remote tracking unknown"
287
+ WARNINGS=$((WARNINGS + 1))
288
+ fi
289
+
290
+ # ─── 7. Workspace Awareness ──────────────────────────────
291
+ WORKSPACE_FILE=""
292
+ CHECK_DIR="$PROJECT_DIR"
293
+ while [ "$CHECK_DIR" != "/" ]; do
294
+ if [ -f "$CHECK_DIR/.tlc-workspace.json" ]; then
295
+ WORKSPACE_FILE="$CHECK_DIR/.tlc-workspace.json"
296
+ break
203
297
  fi
298
+ CHECK_DIR=$(dirname "$CHECK_DIR")
299
+ done
300
+
301
+ if [ -n "$WORKSPACE_FILE" ]; then
302
+ WORKSPACE_NAME=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$WORKSPACE_FILE','utf8')).workspace)}catch{console.log('?')}" 2>/dev/null)
303
+ 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)
304
+ echo " Workspace: ✅ ${WORKSPACE_NAME} (${REPO_COUNT} repos)"
305
+
306
+ # Check each repo's sync status
307
+ WORKSPACE_ROOT=$(dirname "$WORKSPACE_FILE")
308
+ node -e "
309
+ const fs = require('fs');
310
+ const path = require('path');
311
+ const { execSync } = require('child_process');
312
+ const wsRoot = process.argv[1];
313
+ const ws = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
314
+ for (const [name, repo] of Object.entries(ws.repos)) {
315
+ const rp = path.resolve(wsRoot, repo.path);
316
+ try {
317
+ fs.accessSync(rp);
318
+ // Fetch remote for accurate state
319
+ try { execSync('git -C ' + JSON.stringify(rp) + ' fetch --quiet', { stdio: ['pipe','pipe','pipe'], timeout: 10000 }); } catch {}
320
+ const branch = execSync('git -C ' + JSON.stringify(rp) + ' branch --show-current', { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
321
+ let status = 'unknown';
322
+ try {
323
+ const behind = execSync('git -C ' + JSON.stringify(rp) + ' rev-list --count HEAD..@{u}', { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
324
+ const ahead = execSync('git -C ' + JSON.stringify(rp) + ' rev-list --count @{u}..HEAD', { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
325
+ if (behind === '0' && ahead === '0') status = 'in sync';
326
+ else if (behind !== '0') status = behind + ' behind — needs pull';
327
+ else status = ahead + ' ahead — push pending';
328
+ } catch { status = 'no remote tracking'; }
329
+ console.log(' [' + repo.prefix + '] ' + branch + ': ' + status);
330
+ } catch {
331
+ console.log(' [' + repo.prefix + '] not cloned');
332
+ }
333
+ }
334
+ " "$WORKSPACE_ROOT" "$WORKSPACE_FILE" 2>/dev/null
335
+ else
336
+ echo " Workspace: ⚠️ no .tlc-workspace.json found (single-repo mode)"
337
+ WARNINGS=$((WARNINGS + 1))
204
338
  fi
205
339
 
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"
340
+ # ─── 8. CI/CD Status ─────────────────────────────────────
341
+ if command -v gh >/dev/null 2>&1; then
342
+ LAST_RUN=$(gh run list --limit 1 --json conclusion,status,name -q '.[0]' 2>/dev/null)
343
+ if [ -n "$LAST_RUN" ]; then
344
+ 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)
345
+ 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)
346
+ if [ "$RUN_STATUS" = "success" ]; then
347
+ echo " CI/CD: ✅ last run passed (${RUN_NAME})"
348
+ elif [ "$RUN_STATUS" = "failure" ]; then
349
+ echo " CI/CD: ❌ last run FAILED (${RUN_NAME})"
350
+ ERRORS=$((ERRORS + 1))
351
+ else
352
+ echo " CI/CD: ⏳ ${RUN_STATUS} (${RUN_NAME})"
353
+ fi
354
+ else
355
+ echo " CI/CD: ⚠️ no recent runs"
356
+ fi
357
+ else
358
+ echo " CI/CD: ⚠️ gh CLI not installed"
359
+ WARNINGS=$((WARNINGS + 1))
212
360
  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
361
+
362
+ # ─── Summary ─────────────────────────────────────────────
363
+ echo "────────────────────────────────────────────────────"
364
+ if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
365
+ echo "All systems healthy. ✅"
366
+ elif [ "$ERRORS" -eq 0 ]; then
367
+ echo "${WARNINGS} warning(s), 0 errors. Systems operational."
368
+ else
369
+ echo "⚠️ ${ERRORS} error(s), ${WARNINGS} warning(s). Check above."
215
370
  fi
216
371
 
217
372
  exit 0
package/CLAUDE.md CHANGED
@@ -10,6 +10,7 @@
10
10
  4. **No direct implementation.** User says "build X" → run `/tlc:progress` then `/tlc:build`.
11
11
  5. **No Co-Authored-By in commits.** The user is the author. Claude is a tool.
12
12
  6. **Ask before `git push`.** Never push without explicit approval.
13
+ 7. **Claude NEVER writes code.** All code execution goes through the orchestrator (tlc-core :3100). Claude is PM — read, plan, dispatch, review. Sub-agents writing code is a bug. If the orchestrator is down, recover it first — never fall back to inline code.
13
14
 
14
15
  ## CodeDB
15
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlc-claude-code",
3
- "version": "2.9.1",
3
+ "version": "2.10.0",
4
4
  "description": "TLC - Test Led Coding for Claude Code",
5
5
  "bin": {
6
6
  "tlc-claude-code": "./bin/install.js",