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.
- package/.claude/hooks/tlc-session-init.sh +238 -149
- package/package.json +1 -1
|
@@ -1,86 +1,91 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# TLC
|
|
3
|
-
#
|
|
4
|
-
#
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
echo "
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 "
|
|
86
|
+
echo " CodeDB: ✅ started indexing"
|
|
82
87
|
fi
|
|
83
|
-
# Ensure .mcp.json
|
|
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 "
|
|
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
|
-
# ───
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 [
|
|
113
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
# ───
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|