tlc-claude-code 1.8.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.claude/commands/tlc/bootstrap.md +77 -0
  2. package/.claude/commands/tlc/build.md +20 -6
  3. package/.claude/commands/tlc/deploy.md +194 -2
  4. package/.claude/commands/tlc/e2e-verify.md +214 -0
  5. package/.claude/commands/tlc/guard.md +191 -0
  6. package/.claude/commands/tlc/help.md +32 -0
  7. package/.claude/commands/tlc/init.md +73 -37
  8. package/.claude/commands/tlc/llm.md +19 -4
  9. package/.claude/commands/tlc/preflight.md +134 -0
  10. package/.claude/commands/tlc/recall.md +87 -0
  11. package/.claude/commands/tlc/remember.md +71 -0
  12. package/.claude/commands/tlc/review.md +17 -4
  13. package/.claude/commands/tlc/watchci.md +159 -0
  14. package/.claude/hooks/tlc-block-tools.sh +41 -0
  15. package/.claude/hooks/tlc-capture-exchange.sh +50 -0
  16. package/.claude/hooks/tlc-post-build.sh +38 -0
  17. package/.claude/hooks/tlc-post-push.sh +22 -0
  18. package/.claude/hooks/tlc-prompt-guard.sh +69 -0
  19. package/.claude/hooks/tlc-session-init.sh +123 -0
  20. package/CLAUDE.md +96 -201
  21. package/bin/install.js +171 -2
  22. package/bin/postinstall.js +45 -26
  23. package/dashboard-web/dist/assets/index-CdS5CHqu.css +1 -0
  24. package/dashboard-web/dist/assets/index-CwNPPVpg.js +483 -0
  25. package/dashboard-web/dist/assets/index-CwNPPVpg.js.map +1 -0
  26. package/dashboard-web/dist/index.html +2 -2
  27. package/docker-compose.dev.yml +18 -12
  28. package/package.json +3 -1
  29. package/server/index.js +240 -1
  30. package/server/lib/bug-writer.js +204 -0
  31. package/server/lib/bug-writer.test.js +279 -0
  32. package/server/lib/capture-bridge.js +242 -0
  33. package/server/lib/capture-bridge.test.js +363 -0
  34. package/server/lib/capture-guard.js +140 -0
  35. package/server/lib/capture-guard.test.js +182 -0
  36. package/server/lib/claude-cascade.js +247 -0
  37. package/server/lib/claude-cascade.test.js +245 -0
  38. package/server/lib/command-runner.js +159 -0
  39. package/server/lib/command-runner.test.js +92 -0
  40. package/server/lib/context-injection.js +121 -0
  41. package/server/lib/context-injection.test.js +340 -0
  42. package/server/lib/conversation-chunker.js +320 -0
  43. package/server/lib/conversation-chunker.test.js +573 -0
  44. package/server/lib/deploy/runners/dependency-runner.js +106 -0
  45. package/server/lib/deploy/runners/dependency-runner.test.js +148 -0
  46. package/server/lib/deploy/runners/secrets-runner.js +174 -0
  47. package/server/lib/deploy/runners/secrets-runner.test.js +127 -0
  48. package/server/lib/deploy/security-gates.js +11 -24
  49. package/server/lib/deploy/security-gates.test.js +9 -2
  50. package/server/lib/deploy-engine.js +182 -0
  51. package/server/lib/deploy-engine.test.js +147 -0
  52. package/server/lib/docker-api.js +137 -0
  53. package/server/lib/docker-api.test.js +202 -0
  54. package/server/lib/docker-client.js +297 -0
  55. package/server/lib/docker-client.test.js +308 -0
  56. package/server/lib/embedding-client.js +160 -0
  57. package/server/lib/embedding-client.test.js +243 -0
  58. package/server/lib/global-config.js +198 -0
  59. package/server/lib/global-config.test.js +288 -0
  60. package/server/lib/inherited-search.js +184 -0
  61. package/server/lib/inherited-search.test.js +343 -0
  62. package/server/lib/input-sanitizer.js +86 -0
  63. package/server/lib/input-sanitizer.test.js +117 -0
  64. package/server/lib/launchd-agent.js +225 -0
  65. package/server/lib/launchd-agent.test.js +185 -0
  66. package/server/lib/memory-api.js +182 -0
  67. package/server/lib/memory-api.test.js +320 -0
  68. package/server/lib/memory-bridge-e2e.test.js +160 -0
  69. package/server/lib/memory-committer.js +18 -4
  70. package/server/lib/memory-committer.test.js +21 -0
  71. package/server/lib/memory-hooks-capture.test.js +415 -0
  72. package/server/lib/memory-hooks-integration.test.js +98 -0
  73. package/server/lib/memory-hooks.js +139 -0
  74. package/server/lib/memory-inheritance.js +179 -0
  75. package/server/lib/memory-inheritance.test.js +360 -0
  76. package/server/lib/memory-store-adapter.js +105 -0
  77. package/server/lib/memory-store-adapter.test.js +141 -0
  78. package/server/lib/memory-wiring-e2e.test.js +93 -0
  79. package/server/lib/nginx-config.js +114 -0
  80. package/server/lib/nginx-config.test.js +82 -0
  81. package/server/lib/ollama-health.js +91 -0
  82. package/server/lib/ollama-health.test.js +74 -0
  83. package/server/lib/plan-writer.js +196 -0
  84. package/server/lib/plan-writer.test.js +298 -0
  85. package/server/lib/port-guard.js +44 -0
  86. package/server/lib/port-guard.test.js +65 -0
  87. package/server/lib/project-scanner.js +302 -0
  88. package/server/lib/project-scanner.test.js +541 -0
  89. package/server/lib/project-status.js +302 -0
  90. package/server/lib/project-status.test.js +470 -0
  91. package/server/lib/projects-registry.js +237 -0
  92. package/server/lib/projects-registry.test.js +275 -0
  93. package/server/lib/recall-command.js +207 -0
  94. package/server/lib/recall-command.test.js +306 -0
  95. package/server/lib/remember-command.js +98 -0
  96. package/server/lib/remember-command.test.js +288 -0
  97. package/server/lib/rich-capture.js +221 -0
  98. package/server/lib/rich-capture.test.js +312 -0
  99. package/server/lib/roadmap-api.js +200 -0
  100. package/server/lib/roadmap-api.test.js +318 -0
  101. package/server/lib/security/crypto-utils.test.js +2 -2
  102. package/server/lib/semantic-recall.js +242 -0
  103. package/server/lib/semantic-recall.test.js +463 -0
  104. package/server/lib/setup-generator.js +315 -0
  105. package/server/lib/setup-generator.test.js +303 -0
  106. package/server/lib/ssh-client.js +184 -0
  107. package/server/lib/ssh-client.test.js +127 -0
  108. package/server/lib/test-inventory.js +112 -0
  109. package/server/lib/test-inventory.test.js +360 -0
  110. package/server/lib/vector-indexer.js +246 -0
  111. package/server/lib/vector-indexer.test.js +459 -0
  112. package/server/lib/vector-store.js +260 -0
  113. package/server/lib/vector-store.test.js +706 -0
  114. package/server/lib/vps-api.js +184 -0
  115. package/server/lib/vps-api.test.js +208 -0
  116. package/server/lib/vps-bootstrap.js +124 -0
  117. package/server/lib/vps-bootstrap.test.js +79 -0
  118. package/server/lib/vps-monitor.js +126 -0
  119. package/server/lib/vps-monitor.test.js +98 -0
  120. package/server/lib/workspace-api.js +992 -0
  121. package/server/lib/workspace-api.test.js +1217 -0
  122. package/server/lib/workspace-bootstrap.js +164 -0
  123. package/server/lib/workspace-bootstrap.test.js +503 -0
  124. package/server/lib/workspace-context.js +129 -0
  125. package/server/lib/workspace-context.test.js +214 -0
  126. package/server/lib/workspace-detector.js +162 -0
  127. package/server/lib/workspace-detector.test.js +193 -0
  128. package/server/lib/workspace-init.js +307 -0
  129. package/server/lib/workspace-init.test.js +244 -0
  130. package/server/lib/workspace-snapshot.js +236 -0
  131. package/server/lib/workspace-snapshot.test.js +444 -0
  132. package/server/lib/workspace-watcher.js +162 -0
  133. package/server/lib/workspace-watcher.test.js +257 -0
  134. package/server/package-lock.json +1306 -17
  135. package/server/package.json +7 -0
  136. package/dashboard-web/dist/assets/index-B1I_joSL.js +0 -393
  137. package/dashboard-web/dist/assets/index-B1I_joSL.js.map +0 -1
  138. package/dashboard-web/dist/assets/index-Trhg1C1Y.css +0 -1
@@ -41,19 +41,32 @@ TLC automatically uses ALL providers configured for the `review` capability in `
41
41
 
42
42
  ## Process
43
43
 
44
- ### Step 1: Load Router Configuration
44
+ ### Step 1: Load Router State (Persistent)
45
45
 
46
- Read `.tlc.json` to get configured review providers:
46
+ **Always read from the router state file first**, then fall back to config:
47
47
 
48
48
  ```javascript
49
+ // 1. Read persistent router state (written by session-init hook)
50
+ const routerState = JSON.parse(fs.readFileSync('.tlc/.router-state.json', 'utf-8'));
51
+
52
+ // 2. Read config for capability mappings
49
53
  const config = JSON.parse(fs.readFileSync('.tlc.json', 'utf-8'));
50
54
  const reviewProviders = config.router?.capabilities?.review?.providers || ['claude'];
51
55
  const providers = config.router?.providers || {};
56
+
57
+ // 3. Filter to only AVAILABLE providers (from state file)
58
+ const availableReviewers = reviewProviders.filter(p =>
59
+ routerState.providers[p]?.available === true
60
+ );
52
61
  ```
53
62
 
54
- **Default providers for review:** `['claude', 'codex']`
63
+ **The state file (`.tlc/.router-state.json`) is authoritative for availability.** It's written by the `SessionStart` hook, re-probed every hour, and persists across skill invocations. Never run `which codex` yourself — read the state file.
64
+
65
+ If the state file is missing or stale (>1 hour), probe manually and write a fresh one.
66
+
67
+ **Default providers for review:** `['claude', 'codex']` — but only invoked if the state file confirms they're available.
55
68
 
56
- If Codex is configured and available, it WILL be invoked automatically.
69
+ If Codex is configured AND available in state, it WILL be invoked automatically.
57
70
 
58
71
  ### Step 2: Identify Changes
59
72
 
@@ -0,0 +1,159 @@
1
+ # /tlc:watchci - Watch CI and Fix Until Green
2
+
3
+ After pushing code, monitor the GitHub Actions run and fix failures in a loop until CI passes.
4
+
5
+ ## What This Does
6
+
7
+ 1. **Identifies the GH Actions run** triggered by the latest push
8
+ 2. **Polls until complete** (or fails)
9
+ 3. **On failure**: reads the logs, identifies the issue, fixes it, commits, pushes again
10
+ 4. **Repeats** until green or max attempts reached
11
+ 5. **Reports** final status
12
+
13
+ This is the "I pushed, now babysit CI for me" command.
14
+
15
+ ## Usage
16
+
17
+ ```
18
+ /tlc:watchci
19
+ /tlc:watchci 3 # max 3 fix attempts (default: 5)
20
+ ```
21
+
22
+ ## Process
23
+
24
+ ### Step 1: Find the Active Run
25
+
26
+ ```bash
27
+ gh run list --limit 5 --json databaseId,status,conclusion,headBranch,event,name,createdAt
28
+ ```
29
+
30
+ - Find the most recent run on the current branch
31
+ - If no run is in progress or recently completed, tell the user and exit
32
+ - Show: workflow name, run ID, status
33
+
34
+ ### Step 2: Wait for Completion
35
+
36
+ Poll the run status every 30 seconds:
37
+
38
+ ```bash
39
+ gh run view <run_id> --json status,conclusion
40
+ ```
41
+
42
+ While status is `in_progress` or `queued`:
43
+ - Print a brief status update every 60 seconds (not every poll)
44
+ - Continue waiting
45
+
46
+ ### Step 3: Check Result
47
+
48
+ If `conclusion` is `success`:
49
+ - Report green and exit
50
+
51
+ If `conclusion` is `failure`:
52
+ - Move to Step 4
53
+
54
+ ### Step 4: Read Failure Logs
55
+
56
+ ```bash
57
+ gh run view <run_id> --log-failed
58
+ ```
59
+
60
+ - Parse the failed step(s) and their log output
61
+ - Identify the root cause:
62
+ - **Test failure**: Which test, what assertion, what file
63
+ - **Build failure**: Which compilation error, what file/line
64
+ - **Lint failure**: Which rule, what file/line
65
+ - **Dependency issue**: Which package, what version conflict
66
+ - **Other**: Extract the relevant error message
67
+
68
+ ### Step 5: Fix the Issue
69
+
70
+ Based on the failure type:
71
+
72
+ **Test failure:**
73
+ - Read the failing test file and the source file it tests
74
+ - Understand what broke
75
+ - Fix the source code (not the test, unless the test itself is wrong)
76
+ - Run the test locally first to verify: `npm test` or `npx vitest run <file>`
77
+
78
+ **Build failure:**
79
+ - Read the file with the compilation error
80
+ - Fix the type error, missing import, etc.
81
+ - Verify locally: `npm run build` or `npx tsc --noEmit`
82
+
83
+ **Lint failure:**
84
+ - Read the file and fix the lint issue
85
+ - Verify locally: `npm run lint`
86
+
87
+ **Dependency issue:**
88
+ - Fix package.json or lock file as needed
89
+ - Verify locally: `npm ci`
90
+
91
+ ### Step 6: Commit and Push
92
+
93
+ - Stage only the files you changed
94
+ - Commit with message: `fix: resolve CI failure - <brief description>`
95
+ - Push to the same branch
96
+ - **Ask user before pushing** (per TLC rules)
97
+
98
+ ### Step 7: Loop Back
99
+
100
+ - Return to Step 1 to watch the new run
101
+ - Track attempt count
102
+ - If max attempts (default 5) reached without green, stop and report:
103
+ - What was tried
104
+ - What's still failing
105
+ - Suggestion for manual investigation
106
+
107
+ ## Guard Rails
108
+
109
+ - **Never push without asking.** Even in a fix loop, confirm before each push.
110
+ - **Never modify tests to make CI pass** unless the test itself has a genuine bug (not a "make the assertion match the wrong output" fix).
111
+ - **Local verification first.** Always run the fix locally before pushing.
112
+ - **Max attempts.** Default 5. Don't loop forever.
113
+ - **Don't mask failures.** If a test is legitimately catching a bug, fix the bug. Don't skip the test.
114
+
115
+ ## Example
116
+
117
+ ```
118
+ > /tlc:watchci
119
+
120
+ Watching CI for branch: feature/auth-module
121
+ Run #4521 (CI) — in progress...
122
+
123
+ ⏳ Waiting... (2m elapsed)
124
+ ⏳ Waiting... (3m elapsed)
125
+
126
+ ❌ Run #4521 failed
127
+
128
+ Failed step: "Run tests"
129
+ Error: FAIL server/lib/auth/auth.test.js
130
+ ● validateToken › should reject expired tokens
131
+ Expected: "TOKEN_EXPIRED"
132
+ Received: "INVALID_TOKEN"
133
+
134
+ Reading auth.test.js and auth.service.js...
135
+
136
+ Found: validateToken returns generic "INVALID_TOKEN" for expired tokens
137
+ instead of the specific "TOKEN_EXPIRED" error code.
138
+
139
+ Fix: Update the expiry check in auth.service.js to return "TOKEN_EXPIRED"
140
+
141
+ Running locally... ✅ Test passes
142
+
143
+ Ready to commit and push fix? (Y/n)
144
+ > y
145
+
146
+ Pushed. Watching new run...
147
+
148
+ Run #4522 (CI) — in progress...
149
+ ⏳ Waiting... (2m elapsed)
150
+
151
+ ✅ Run #4522 passed! CI is green.
152
+ ```
153
+
154
+ ## When to Use
155
+
156
+ - After pushing and you want CI monitored automatically
157
+ - After `/tlc:build` when you've pushed your work
158
+ - When CI keeps failing and you want the fix loop automated
159
+ - Pair with `/tlc:e2e-verify` for full verification after CI is green
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+ # TLC Enforcement Layer 1: Hard-block non-TLC planning tools
3
+ # Fires on PreToolUse for banned tools and DENIES them.
4
+ # Claude cannot bypass this - the tool call never executes.
5
+
6
+ INPUT=$(cat)
7
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
8
+
9
+ case "$TOOL" in
10
+ EnterPlanMode)
11
+ REASON="BLOCKED by TLC. Use /tlc:plan instead. Plans go in .planning/phases/ files."
12
+ ;;
13
+ TaskCreate)
14
+ REASON="BLOCKED by TLC. Tasks live in .planning/phases/{N}-PLAN.md with [ ] markers."
15
+ ;;
16
+ TaskUpdate)
17
+ REASON="BLOCKED by TLC. Update task markers in .planning/phases/{N}-PLAN.md files."
18
+ ;;
19
+ TaskGet)
20
+ REASON="BLOCKED by TLC. Read tasks from .planning/phases/{N}-PLAN.md files."
21
+ ;;
22
+ TaskList)
23
+ REASON="BLOCKED by TLC. Use /tlc:progress to check task status."
24
+ ;;
25
+ ExitPlanMode)
26
+ REASON="BLOCKED by TLC. Plans are approved via /tlc:build, not ExitPlanMode."
27
+ ;;
28
+ *)
29
+ exit 0
30
+ ;;
31
+ esac
32
+
33
+ cat <<EOF
34
+ {
35
+ "hookSpecificOutput": {
36
+ "hookEventName": "PreToolUse",
37
+ "permissionDecision": "deny",
38
+ "permissionDecisionReason": "${REASON}"
39
+ }
40
+ }
41
+ EOF
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # TLC Memory Capture - Claude Code Stop Hook
3
+ #
4
+ # Reads Stop hook JSON from stdin, extracts the assistant response,
5
+ # and sends it to the TLC server for memory processing.
6
+ # Falls back to local spool when server is unreachable.
7
+ #
8
+ # This script MUST exit 0 always - capture failures never block Claude.
9
+
10
+ set -o pipefail
11
+
12
+ # Read stdin (Stop hook provides JSON)
13
+ INPUT=$(cat)
14
+
15
+ # Quick exit if no input
16
+ [ -z "$INPUT" ] && exit 0
17
+
18
+ # Use the capture-bridge Node.js module for reliable processing
19
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
20
+ BRIDGE_SCRIPT="$PROJECT_DIR/server/lib/capture-bridge.js"
21
+
22
+ if [ -f "$BRIDGE_SCRIPT" ]; then
23
+ echo "$INPUT" | node -e "
24
+ const bridge = require('$BRIDGE_SCRIPT');
25
+ let input = '';
26
+ process.stdin.on('data', d => input += d);
27
+ process.stdin.on('end', async () => {
28
+ const parsed = bridge.parseStopHookInput(input);
29
+ if (!parsed || !parsed.assistantMessage) process.exit(0);
30
+
31
+ const userMessage = parsed.transcriptPath
32
+ ? bridge.extractLastUserMessage(parsed.transcriptPath)
33
+ : null;
34
+
35
+ await bridge.captureExchange({
36
+ cwd: parsed.cwd || '$PROJECT_DIR',
37
+ assistantMessage: parsed.assistantMessage,
38
+ userMessage,
39
+ sessionId: parsed.sessionId,
40
+ });
41
+
42
+ const path = require('path');
43
+ const spoolDir = path.join(parsed.cwd || '$PROJECT_DIR', '.tlc', 'memory');
44
+ await bridge.drainSpool(spoolDir);
45
+ });
46
+ " 2>/dev/null
47
+ fi
48
+
49
+ # Always exit 0 - never block Claude
50
+ exit 0
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+ # TLC Plugin: Post-build and post-plan automation chain
3
+ # Fires on PostToolUse for Skill.
4
+ #
5
+ # After /tlc:build (code written):
6
+ # guard → preflight → review (Claude + Codex) → if APPROVED → offer push + PR
7
+ #
8
+ # After /tlc:plan:
9
+ # guard plan → validate structure
10
+ #
11
+ # After /tlc:quick:
12
+ # preflight → review → offer push
13
+
14
+ INPUT=$(cat)
15
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
16
+
17
+ # Only care about Skill tool
18
+ [ "$TOOL" != "Skill" ] && exit 0
19
+
20
+ SKILL=$(echo "$INPUT" | jq -r '.tool_input.skill // empty')
21
+
22
+ case "$SKILL" in
23
+ tlc:build)
24
+ echo "[TLC-PLUGIN] Build phase complete. Mandatory post-build chain:
25
+ (1) Run /tlc:guard to validate TLC process compliance (test-first, coverage, no skipped tests).
26
+ (2) Run /tlc:preflight to check for gaps (references, registries, configs, distribution).
27
+ (3) Run /tlc:review to invoke multi-LLM review — Claude reviews in-session, THEN invoke Codex CLI for second opinion. BOTH must APPROVE.
28
+ (4) If review APPROVED: ask user for permission to push to remote and create a PR for main.
29
+ (5) If review CHANGES_REQUESTED: fix the issues and re-run the review. Do NOT push.
30
+ Do NOT skip any step. Do NOT push without review approval."
31
+ ;;
32
+ tlc:plan)
33
+ echo "[TLC-PLUGIN] Plan created. Now automatically run /tlc:guard plan to validate the plan has proper task breakdown, acceptance criteria, and dependency ordering. Do NOT skip."
34
+ ;;
35
+ tlc:quick)
36
+ echo "[TLC-PLUGIN] Quick task complete. Run /tlc:preflight then /tlc:review (Claude + Codex). If APPROVED, ask user for permission to push and create PR."
37
+ ;;
38
+ esac
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # TLC Plugin: Post-push automation chain
3
+ # Fires on PostToolUse for Bash. Detects git push.
4
+ #
5
+ # After git push:
6
+ # watchci (monitor GH Actions) → if green → e2e-verify (screenshots + logs)
7
+
8
+ INPUT=$(cat)
9
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
10
+
11
+ # Only care about Bash tool
12
+ [ "$TOOL" != "Bash" ] && exit 0
13
+
14
+ # Check if the command was a git push
15
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
16
+ if echo "$COMMAND" | grep -qE '^git push'; then
17
+ echo "[TLC-PLUGIN] git push detected. Mandatory post-push chain:
18
+ (1) Run /tlc:watchci — monitor the GitHub Actions run, read failure logs, fix issues, and push again until CI is green.
19
+ (2) Once CI is green, run /tlc:e2e-verify — start the app, take screenshots, read them visually, check server and console logs for errors.
20
+ (3) If e2e-verify finds issues, fix them and push again (which restarts this chain).
21
+ Do NOT skip any step."
22
+ fi
@@ -0,0 +1,69 @@
1
+ #!/bin/bash
2
+ # TLC Enforcement Layer 2: Smart intent detection + routing
3
+ # Fires on every UserPromptSubmit. Parses user message for intent and
4
+ # injects specific TLC command routing — not a generic reminder.
5
+ #
6
+ # Output appears as <user-prompt-submit-hook> and Claude treats it as user instruction.
7
+ # Survives context compaction because it is re-injected every turn.
8
+
9
+ # Only active in TLC projects
10
+ [ ! -f ".tlc.json" ] && exit 0
11
+
12
+ # Read user's message from stdin
13
+ INPUT=$(cat)
14
+ MSG=$(echo "$INPUT" | jq -r '.user_prompt // empty' 2>/dev/null)
15
+
16
+ # If we can't parse the message, fall back to generic reminder
17
+ if [ -z "$MSG" ]; then
18
+ echo "[TLC PROJECT] All work uses /tlc commands. Never write code without tests. Run /tlc if unsure."
19
+ exit 0
20
+ fi
21
+
22
+ # Lowercase for matching
23
+ MSG_LOWER=$(echo "$MSG" | tr '[:upper:]' '[:lower:]')
24
+
25
+ # Detect intent and inject specific routing
26
+ # Priority order: most specific first
27
+
28
+ # Plan intent
29
+ if echo "$MSG_LOWER" | grep -qE '\b(plan|break.*(down|into)|design|architect|roadmap)\b'; then
30
+ if ! echo "$MSG_LOWER" | grep -qE '/tlc'; then
31
+ echo "[TLC-ENFORCE] Planning detected. You MUST use /tlc:plan for this. Plans go in .planning/phases/ files, not in chat. Invoke Skill(skill=\"tlc:plan\") now."
32
+ exit 0
33
+ fi
34
+ fi
35
+
36
+ # Build/implement intent
37
+ if echo "$MSG_LOWER" | grep -qE '\b(build|implement|create|add|code|write|develop|make)\b.*(feature|function|module|component|endpoint|api|page|service|handler|route|model)'; then
38
+ if ! echo "$MSG_LOWER" | grep -qE '/tlc'; then
39
+ echo "[TLC-ENFORCE] Implementation detected. You MUST use /tlc:build for this. Tests before code — Red, Green, Refactor. Run /tlc:progress first, then invoke Skill(skill=\"tlc:build\"). Do NOT write code directly."
40
+ exit 0
41
+ fi
42
+ fi
43
+
44
+ # Fix/bug intent
45
+ if echo "$MSG_LOWER" | grep -qE '\b(fix|bug|broken|not working|failing|crash|error)\b'; then
46
+ if ! echo "$MSG_LOWER" | grep -qE '/tlc'; then
47
+ echo "[TLC-ENFORCE] Bug/fix detected. Use /tlc:quick for small fixes (still test-first) or /tlc:autofix if tests are failing. Do NOT write code directly without tests."
48
+ exit 0
49
+ fi
50
+ fi
51
+
52
+ # Refactor intent
53
+ if echo "$MSG_LOWER" | grep -qE '\b(refactor|clean.?up|restructure|reorganize|simplify)\b'; then
54
+ if ! echo "$MSG_LOWER" | grep -qE '/tlc'; then
55
+ echo "[TLC-ENFORCE] Refactoring detected. Use /tlc:refactor for step-by-step standards refactoring with tests. Invoke Skill(skill=\"tlc:refactor\")."
56
+ exit 0
57
+ fi
58
+ fi
59
+
60
+ # Deploy intent
61
+ if echo "$MSG_LOWER" | grep -qE '\b(deploy|ship|release|publish|push to prod|staging)\b'; then
62
+ if ! echo "$MSG_LOWER" | grep -qE '/tlc'; then
63
+ echo "[TLC-ENFORCE] Deployment detected. Use /tlc:deploy for deployment. Secrets must come from HashiCorp Vault or environment — never hardcode. Invoke Skill(skill=\"tlc:deploy\")."
64
+ exit 0
65
+ fi
66
+ fi
67
+
68
+ # Generic fallback for TLC projects
69
+ echo "[TLC PROJECT] All work uses /tlc commands. Never write code without tests. Run /tlc if unsure."
@@ -0,0 +1,123 @@
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
6
+
7
+ if [ ! -f ".tlc.json" ]; then
8
+ exit 0
9
+ fi
10
+
11
+ echo "TLC project detected. All work goes through /tlc commands. Run /tlc for current status and next action."
12
+
13
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
14
+
15
+ # ─── TLC Server ───────────────────────────────────────────
16
+
17
+ TLC_PORT="${TLC_PORT:-3147}"
18
+ if curl -sf --max-time 1 "http://localhost:${TLC_PORT}/api/health" > /dev/null 2>&1; then
19
+ : # Server is running
20
+ else
21
+ PLIST="$HOME/Library/LaunchAgents/com.tlc.server.plist"
22
+
23
+ if [ -f "$PLIST" ]; then
24
+ launchctl kickstart -k "gui/$(id -u)/com.tlc.server" 2>/dev/null
25
+ elif [ -f "$PROJECT_DIR/server/index.js" ]; then
26
+ nohup node "$PROJECT_DIR/server/index.js" > "$HOME/.tlc/logs/server.log" 2>&1 &
27
+ fi
28
+
29
+ for i in 1 2 3; do
30
+ sleep 1
31
+ curl -sf --max-time 1 "http://localhost:${TLC_PORT}/api/health" > /dev/null 2>&1 && break
32
+ done
33
+ fi
34
+
35
+ # ─── LLM Router: Probe Providers ─────────────────────────
36
+ #
37
+ # Writes .tlc/.router-state.json with provider availability.
38
+ # Skills read this file instead of probing from scratch.
39
+ # State has a TTL — re-probed if older than 1 hour.
40
+
41
+ STATE_DIR="$PROJECT_DIR/.tlc"
42
+ STATE_FILE="$STATE_DIR/.router-state.json"
43
+ mkdir -p "$STATE_DIR"
44
+
45
+ # Check if state is fresh (less than 1 hour old)
46
+ STALE=true
47
+ if [ -f "$STATE_FILE" ]; then
48
+ STATE_AGE=$(( $(date +%s) - $(stat -f %m "$STATE_FILE" 2>/dev/null || stat -c %Y "$STATE_FILE" 2>/dev/null || echo 0) ))
49
+ if [ "$STATE_AGE" -lt 3600 ]; then
50
+ STALE=false
51
+ fi
52
+ fi
53
+
54
+ if [ "$STALE" = true ]; then
55
+ # Probe each provider
56
+ CLAUDE_PATH=$(which claude 2>/dev/null || echo "")
57
+ CODEX_PATH=$(which codex 2>/dev/null || echo "")
58
+ GEMINI_PATH=$(which gemini 2>/dev/null || echo "")
59
+
60
+ CLAUDE_OK="false"
61
+ CODEX_OK="false"
62
+ GEMINI_OK="false"
63
+
64
+ [ -n "$CLAUDE_PATH" ] && CLAUDE_OK="true"
65
+ [ -n "$CODEX_PATH" ] && CODEX_OK="true"
66
+ [ -n "$GEMINI_PATH" ] && GEMINI_OK="true"
67
+
68
+ # Count available
69
+ AVAILABLE=0
70
+ [ "$CLAUDE_OK" = "true" ] && AVAILABLE=$((AVAILABLE + 1))
71
+ [ "$CODEX_OK" = "true" ] && AVAILABLE=$((AVAILABLE + 1))
72
+ [ "$GEMINI_OK" = "true" ] && AVAILABLE=$((AVAILABLE + 1))
73
+
74
+ # Read configured providers from .tlc.json
75
+ CONFIGURED_PROVIDERS=""
76
+ if command -v jq >/dev/null 2>&1; then
77
+ CONFIGURED_PROVIDERS=$(jq -r '.router.providers // {} | keys[]' .tlc.json 2>/dev/null | tr '\n' ',' | sed 's/,$//')
78
+ fi
79
+
80
+ # Write state file
81
+ cat > "$STATE_FILE" <<STATEEOF
82
+ {
83
+ "probed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
84
+ "ttl_seconds": 3600,
85
+ "providers": {
86
+ "claude": {
87
+ "available": $CLAUDE_OK,
88
+ "path": "$CLAUDE_PATH"
89
+ },
90
+ "codex": {
91
+ "available": $CODEX_OK,
92
+ "path": "$CODEX_PATH"
93
+ },
94
+ "gemini": {
95
+ "available": $GEMINI_OK,
96
+ "path": "$GEMINI_PATH"
97
+ }
98
+ },
99
+ "summary": {
100
+ "available_count": $AVAILABLE,
101
+ "configured": "$CONFIGURED_PROVIDERS"
102
+ }
103
+ }
104
+ STATEEOF
105
+
106
+ # Report to Claude
107
+ if [ "$AVAILABLE" -gt 0 ]; then
108
+ PROVIDERS_LIST=""
109
+ [ "$CLAUDE_OK" = "true" ] && PROVIDERS_LIST="${PROVIDERS_LIST}claude, "
110
+ [ "$CODEX_OK" = "true" ] && PROVIDERS_LIST="${PROVIDERS_LIST}codex, "
111
+ [ "$GEMINI_OK" = "true" ] && PROVIDERS_LIST="${PROVIDERS_LIST}gemini, "
112
+ PROVIDERS_LIST=$(echo "$PROVIDERS_LIST" | sed 's/, $//')
113
+ 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."
114
+ else
115
+ echo "LLM Router: No external providers detected. Running Claude-only mode. Install codex or gemini for multi-LLM reviews."
116
+ fi
117
+ else
118
+ # State is fresh — just report it
119
+ if command -v jq >/dev/null 2>&1 && [ -f "$STATE_FILE" ]; then
120
+ COUNT=$(jq -r '.summary.available_count' "$STATE_FILE" 2>/dev/null)
121
+ echo "LLM Router: ${COUNT} providers available (cached). State at .tlc/.router-state.json."
122
+ fi
123
+ fi