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
@@ -0,0 +1,77 @@
1
+ # /tlc:bootstrap - Workspace Bootstrap
2
+
3
+ Clone all repos and set up a workspace on a new machine.
4
+
5
+ ## Usage
6
+
7
+ ```
8
+ /tlc:bootstrap
9
+ /tlc:bootstrap --dry-run
10
+ /tlc:bootstrap --skip-install
11
+ /tlc:bootstrap --parallel 5
12
+ ```
13
+
14
+ ## What This Does
15
+
16
+ 1. Reads `projects.json` from the workspace root
17
+ 2. Clones all repos that aren't already present
18
+ 3. Checks out the correct branch per repo
19
+ 4. Installs dependencies (npm install, pip install, etc.)
20
+ 5. Rebuilds vector indexes from memory text files
21
+ 6. Reports summary
22
+
23
+ ## Options
24
+
25
+ | Flag | Default | Description |
26
+ |------|---------|-------------|
27
+ | `--dry-run` | false | Show what would be cloned without doing it |
28
+ | `--skip-install` | false | Skip dependency installation |
29
+ | `--parallel N` | 3 | Number of concurrent clones |
30
+
31
+ ## Process
32
+
33
+ ### Step 1: Read Registry
34
+
35
+ Load `projects.json` from workspace root. If it doesn't exist:
36
+ ```
37
+ No projects.json found.
38
+
39
+ Run /tlc:init to initialize this workspace, or create projects.json manually.
40
+ ```
41
+
42
+ ### Step 2: Clone Repos
43
+
44
+ For each project in the registry:
45
+ - If directory already exists with `.git/` → skip
46
+ - Otherwise → `git clone <url> <path>`
47
+ - Checkout the `defaultBranch`
48
+
49
+ ### Step 3: Install Dependencies
50
+
51
+ For each cloned repo (unless `--skip-install`):
52
+ - Node.js (`package.json`) → `npm install`
53
+ - Python (`requirements.txt`) → `pip install -r requirements.txt`
54
+ - Go (`go.mod`) → `go mod download`
55
+
56
+ ### Step 4: Rebuild Vectors
57
+
58
+ If Ollama is available:
59
+ - Pull embedding model: `ollama pull mxbai-embed-large`
60
+ - Rebuild vector indexes from memory text files
61
+
62
+ ### Step 5: Summary
63
+
64
+ ```
65
+ Workspace bootstrap complete:
66
+ Cloned: 3 repos
67
+ Skipped: 1 (already present)
68
+ Failed: 0
69
+
70
+ All repos ready. Run /tlc:progress to see project status.
71
+ ```
72
+
73
+ ## Related Commands
74
+
75
+ - `/tlc:init` — Initialize TLC in an existing project
76
+ - `/tlc:recall` — Search workspace memory
77
+ - `/tlc:progress` — Check project status
@@ -636,21 +636,35 @@ git add src/auth/login.ts tests/auth/login.test.ts
636
636
  git commit -m "feat: {task-title} - phase {N}"
637
637
  ```
638
638
 
639
- #### 7e. Mark Task Complete (Multi-User)
639
+ #### 7e. Mark Task Complete in PLAN.md (MANDATORY)
640
640
 
641
- If using multi-user mode (task had `[>@user]` marker):
641
+ **Always update the task marker in PLAN.md after tests pass.** This is not optional.
642
642
 
643
- 1. Update marker: `[>@{user}]` → `[x@{user}]`
644
- 2. Commit: `git commit -m "complete: task {N} - {title} (@{user})"`
645
- 3. Push to share progress with team
643
+ 1. Open `.planning/phases/{phase}-PLAN.md`
644
+ 2. Find the task heading: `### Task {N}: {title} [ ]` (or `[>@{user}]`)
645
+ 3. Update the marker:
646
+ - Single-user: `[ ]` → `[x]`
647
+ - Multi-user: `[>@{user}]` → `[x@{user}]`
648
+ 4. Include the plan update in the same commit (step 7d) or commit separately
649
+
650
+ **Example:**
651
+ ```
652
+ ### Task 3: Tabbed Project Detail Page [ ] ← before
653
+ ### Task 3: Tabbed Project Detail Page [x] ← after
654
+ ```
655
+
656
+ This keeps the plan file as the single source of truth for task status. Do NOT wait until the end of the phase — mark each task done immediately after its tests pass.
657
+
658
+ If in multi-user mode, also push to share progress with team.
646
659
 
647
660
  #### 7f. Move to next task
648
- Repeat 7a-7d for each task in the phase.
661
+ Repeat 7a-7e for each task in the phase.
649
662
 
650
663
  **Critical Rules:**
651
664
  - Implement **one task at a time**
652
665
  - Run tests **after each task**
653
666
  - Commit **after each passing task**
667
+ - **Mark task `[x]` in PLAN.md after each passing task** — never defer this
654
668
  - Do NOT batch — sequential execution catches issues early
655
669
 
656
670
  ### Step 8: Verify All Tests Pass (Green)
@@ -648,9 +648,199 @@ Add to GitHub deploy keys (read-only):
648
648
  https://github.com/org/repo/settings/keys
649
649
  ```
650
650
 
651
- ### Environment Variables
651
+ ### Secrets Management with HashiCorp Vault
652
652
 
653
- Secrets stored on dev server:
653
+ **Never store secrets in `.env` files, code, or git.** All secrets go through HashiCorp Vault.
654
+
655
+ #### Setup Vault
656
+
657
+ ```
658
+ > /tlc:deploy vault setup
659
+
660
+ HashiCorp Vault Setup
661
+ ════════════════════════════════════════════════
662
+
663
+ Vault address: https://vault.example.com
664
+ Auth method: [token/approle/github] approle
665
+
666
+ Configuring AppRole auth for TLC...
667
+ ✓ Created policy: tlc-deploy-policy
668
+ ✓ Created AppRole: tlc-deploy
669
+ ✓ Role ID: 7c8a9b2d-...
670
+ ✓ Secret ID: (stored in CI secrets)
671
+
672
+ Secrets engine: kv-v2 at secret/tlc/{project}
673
+
674
+ Configuration saved to .tlc.json (no secrets stored)
675
+ ```
676
+
677
+ #### Secret Paths
678
+
679
+ Secrets are organized by environment in Vault:
680
+
681
+ ```
682
+ secret/tlc/{project}/
683
+ ├── dev/
684
+ │ ├── database_url
685
+ │ ├── jwt_secret
686
+ │ ├── slack_webhook
687
+ │ └── github_webhook_secret
688
+ ├── staging/
689
+ │ ├── database_url
690
+ │ ├── jwt_secret
691
+ │ └── api_keys
692
+ └── production/
693
+ ├── database_url
694
+ ├── jwt_secret
695
+ ├── api_keys
696
+ └── ssl_certs
697
+ ```
698
+
699
+ #### Vault Commands
700
+
701
+ ```
702
+ > /tlc:deploy vault set dev/jwt_secret "new-secret-value"
703
+ ✓ Secret stored at secret/tlc/my-app/dev/jwt_secret
704
+
705
+ > /tlc:deploy vault list dev
706
+ Secrets at secret/tlc/my-app/dev/:
707
+ database_url
708
+ jwt_secret
709
+ slack_webhook
710
+ github_webhook_secret
711
+
712
+ > /tlc:deploy vault get dev/jwt_secret
713
+ ✓ Value copied to clipboard (not displayed)
714
+
715
+ > /tlc:deploy vault rotate dev/jwt_secret
716
+ ✓ New secret generated and stored
717
+ ✓ Containers using this secret will be restarted
718
+ ```
719
+
720
+ #### How Containers Get Secrets
721
+
722
+ Secrets are injected at deploy time, never baked into images:
723
+
724
+ ```yaml
725
+ # docker-compose.yml (generated per deployment)
726
+ services:
727
+ app:
728
+ build: ./deployments/${BRANCH}
729
+ environment:
730
+ - VAULT_ADDR=${VAULT_ADDR}
731
+ - VAULT_ROLE_ID=${VAULT_ROLE_ID}
732
+ # Secrets fetched at startup via vault-agent sidecar
733
+ # OR injected via envconsul/consul-template
734
+ ```
735
+
736
+ **Option A: Vault Agent Sidecar (recommended)**
737
+
738
+ ```yaml
739
+ services:
740
+ vault-agent:
741
+ image: hashicorp/vault
742
+ command: vault agent -config=/etc/vault/agent.hcl
743
+ volumes:
744
+ - shared-secrets:/secrets
745
+
746
+ app:
747
+ depends_on: [vault-agent]
748
+ volumes:
749
+ - shared-secrets:/secrets:ro
750
+ environment:
751
+ - ENV_FILE=/secrets/.env
752
+ ```
753
+
754
+ **Option B: envconsul (simpler)**
755
+
756
+ ```dockerfile
757
+ # In app Dockerfile
758
+ RUN curl -fsSL https://releases.hashicorp.com/envconsul/0.13.2/envconsul_0.13.2_linux_amd64.tgz | tar xz
759
+ ENTRYPOINT ["envconsul", "-vault-addr", "$VAULT_ADDR", "-secret", "secret/tlc/${PROJECT}/${ENV}", "npm", "start"]
760
+ ```
761
+
762
+ **Option C: CI-time injection (simplest, for non-production)**
763
+
764
+ ```yaml
765
+ # GitHub Actions
766
+ - name: Fetch secrets from Vault
767
+ uses: hashicorp/vault-action@v3
768
+ with:
769
+ url: ${{ secrets.VAULT_ADDR }}
770
+ method: approle
771
+ roleId: ${{ secrets.VAULT_ROLE_ID }}
772
+ secretId: ${{ secrets.VAULT_SECRET_ID }}
773
+ secrets: |
774
+ secret/data/tlc/${{ env.PROJECT }}/dev database_url | DATABASE_URL;
775
+ secret/data/tlc/${{ env.PROJECT }}/dev jwt_secret | JWT_SECRET;
776
+ ```
777
+
778
+ #### Vault Configuration in .tlc.json
779
+
780
+ ```json
781
+ {
782
+ "deploy": {
783
+ "domain": "project.example.com",
784
+ "vault": {
785
+ "enabled": true,
786
+ "addr": "https://vault.example.com",
787
+ "auth": "approle",
788
+ "secretPath": "secret/tlc/my-app",
789
+ "injection": "vault-agent",
790
+ "environments": ["dev", "staging", "production"]
791
+ }
792
+ }
793
+ }
794
+ ```
795
+
796
+ **No secrets in `.tlc.json`.** Only the Vault address and paths. Role IDs and Secret IDs live in CI secrets or the deploy server's own Vault agent.
797
+
798
+ #### Secret Rotation
799
+
800
+ ```
801
+ > /tlc:deploy vault rotate-all dev
802
+
803
+ Rotating all secrets for dev environment...
804
+ ✓ database_url — rotated (new password generated)
805
+ ✓ jwt_secret — rotated (32-byte random)
806
+ ✓ slack_webhook — skipped (external, rotate manually)
807
+ ✓ github_webhook_secret — rotated
808
+
809
+ Restarting affected containers...
810
+ ✓ dev containers restarted with new secrets
811
+
812
+ Rotation complete. Old secrets invalidated.
813
+ ```
814
+
815
+ #### Migration from .env Files
816
+
817
+ If you have existing `.env` files:
818
+
819
+ ```
820
+ > /tlc:deploy vault migrate
821
+
822
+ Found .env files:
823
+ /opt/tlc-deploy/.env (4 secrets)
824
+ .env.local (2 secrets)
825
+
826
+ Migrating to Vault...
827
+ ✓ JWT_SECRET → secret/tlc/my-app/dev/jwt_secret
828
+ ✓ DATABASE_URL → secret/tlc/my-app/dev/database_url
829
+ ✓ GITHUB_WEBHOOK_SECRET → secret/tlc/my-app/dev/github_webhook_secret
830
+ ✓ SLACK_WEBHOOK_URL → secret/tlc/my-app/dev/slack_webhook
831
+
832
+ Securely deleting .env files...
833
+ ✓ /opt/tlc-deploy/.env shredded
834
+ ✓ .env.local shredded
835
+
836
+ Check .gitignore includes: .env* ✓
837
+
838
+ Migration complete. All secrets now in Vault.
839
+ ```
840
+
841
+ ### Environment Variables (Legacy / Non-Vault)
842
+
843
+ If Vault is not configured, secrets fall back to `.env` files on the deploy server. **This is not recommended for production.**
654
844
 
655
845
  ```bash
656
846
  # /opt/tlc-deploy/.env
@@ -660,6 +850,8 @@ GITHUB_WEBHOOK_SECRET=webhook-secret
660
850
  SLACK_WEBHOOK_URL=https://hooks.slack.com/...
661
851
  ```
662
852
 
853
+ **Warning:** TLC will flag `.env` files containing secrets during `/tlc:security` scans.
854
+
663
855
  ## Cleanup
664
856
 
665
857
  ### Remove old deployments
@@ -0,0 +1,214 @@
1
+ # /tlc:e2e-verify - E2E Visual Verification
2
+
3
+ When code is "done", verify it actually works by running e2e tests, taking screenshots, and checking logs. Trust but verify.
4
+
5
+ ## What This Does
6
+
7
+ 1. **Starts the dev server** (if not already running)
8
+ 2. **Runs Playwright e2e tests** to exercise the application
9
+ 3. **Takes screenshots** of key pages/states
10
+ 4. **Reads screenshots visually** to verify UI correctness
11
+ 5. **Checks server and browser console logs** for errors
12
+ 6. **Reports findings** and fixes issues
13
+
14
+ This replaces the manual "let me check if it actually works" step.
15
+
16
+ ## Usage
17
+
18
+ ```
19
+ /tlc:e2e-verify
20
+ /tlc:e2e-verify dashboard # verify specific area
21
+ /tlc:e2e-verify --screenshots # screenshot-heavy mode
22
+ ```
23
+
24
+ ## Process
25
+
26
+ ### Step 1: Ensure Dev Server is Running
27
+
28
+ Check if the server is already up:
29
+
30
+ ```bash
31
+ curl -s http://localhost:3148/health || curl -s http://localhost:3148/ 2>/dev/null
32
+ curl -s http://localhost:4000/ 2>/dev/null
33
+ ```
34
+
35
+ If not running:
36
+ - Start the TLC server: `node server/index.js &` (port from `.tlc.json`)
37
+ - Start the dashboard dev server if needed: `cd dashboard-web && npm run dev &`
38
+ - Wait for both to be ready (poll health endpoints)
39
+
40
+ ### Step 2: Run Existing E2E Tests
41
+
42
+ Check which Playwright config applies:
43
+ - Root: `playwright.config.ts` (server e2e, baseURL `localhost:3147`)
44
+ - Dashboard: `dashboard-web/playwright.config.ts` (dashboard e2e, baseURL `localhost:4000`)
45
+
46
+ Run the relevant tests:
47
+
48
+ ```bash
49
+ npx playwright test --reporter=list
50
+ ```
51
+
52
+ - Capture stdout/stderr
53
+ - Note any failures
54
+
55
+ ### Step 3: Take Screenshots
56
+
57
+ Use Playwright to screenshot key pages. Create a temporary test script if no existing screenshots cover the area:
58
+
59
+ ```bash
60
+ npx playwright test --project=chromium --reporter=list
61
+ ```
62
+
63
+ For targeted screenshots, use Playwright's screenshot API via a quick script:
64
+
65
+ ```javascript
66
+ // Save to /tmp/tlc-screenshots/
67
+ const { chromium } = require('playwright');
68
+ const browser = await chromium.launch();
69
+ const page = await browser.newPage();
70
+
71
+ // Screenshot key pages
72
+ await page.goto('http://localhost:4000');
73
+ await page.screenshot({ path: '/tmp/tlc-screenshots/dashboard-home.png', fullPage: true });
74
+
75
+ // Login flow
76
+ await page.goto('http://localhost:4000/login');
77
+ await page.screenshot({ path: '/tmp/tlc-screenshots/login.png', fullPage: true });
78
+
79
+ // After login
80
+ await page.fill('[data-testid="email"]', 'user@local.com');
81
+ await page.fill('[data-testid="password"]', '2026rocks');
82
+ await page.click('[data-testid="login-btn"]');
83
+ await page.waitForURL('**/dashboard**');
84
+ await page.screenshot({ path: '/tmp/tlc-screenshots/after-login.png', fullPage: true });
85
+ ```
86
+
87
+ Adapt the script based on what was changed — screenshot the areas affected by recent work.
88
+
89
+ ### Step 4: Visual Inspection
90
+
91
+ **Read each screenshot using the Read tool** (Claude is multimodal and can see images).
92
+
93
+ For each screenshot, check:
94
+ - **Layout**: Is the page rendering correctly? No overlapping elements, broken grids?
95
+ - **Content**: Is text readable? Are labels correct? No "undefined" or "[object Object]"?
96
+ - **State**: Are loading states resolved? No spinners stuck? No empty states where data should be?
97
+ - **Errors**: Any visible error messages, red banners, or console error overlays?
98
+ - **Styling**: Does it look intentional? No broken CSS, missing icons, or unstyled elements?
99
+
100
+ ### Step 5: Check Logs
101
+
102
+ **Server logs:**
103
+ ```bash
104
+ # Check for errors in server output
105
+ # If server was started with output capture, read the log file
106
+ cat /tmp/tlc-server.log 2>/dev/null | grep -i "error\|warn\|fail" | tail -20
107
+ ```
108
+
109
+ **Browser console:**
110
+ - Playwright captures console messages — check test output for console errors
111
+ - Look for: uncaught exceptions, failed network requests, React/Vue warnings
112
+
113
+ **Network requests:**
114
+ - Check for failed API calls (4xx, 5xx responses)
115
+ - Check for CORS errors
116
+ - Check for slow responses (>2s)
117
+
118
+ ### Step 6: Report and Fix
119
+
120
+ **If everything looks good:**
121
+ ```
122
+ ✅ E2E Verification Complete
123
+
124
+ Screenshots reviewed:
125
+ - Dashboard home: ✅ Renders correctly
126
+ - Login page: ✅ Form visible, styled correctly
127
+ - After login: ✅ Dashboard loads with data
128
+
129
+ Logs: No errors found
130
+ Tests: 12/12 passing
131
+
132
+ Code is verified.
133
+ ```
134
+
135
+ **If issues found:**
136
+ ```
137
+ ⚠️ E2E Verification Found Issues
138
+
139
+ 1. Dashboard home: Table header misaligned on narrow viewport
140
+ 2. Server log: "Warning: deprecated API endpoint /api/v1/stats called"
141
+ 3. Console: "Failed to load resource: 404 /api/fonts/inter.woff2"
142
+
143
+ Fixing...
144
+ ```
145
+
146
+ Then fix each issue:
147
+ - Read the relevant source files
148
+ - Apply fixes
149
+ - Re-run the verification for the fixed areas
150
+ - Loop until clean
151
+
152
+ ### Step 7: Final Confirmation
153
+
154
+ After all fixes:
155
+ - Re-run full e2e suite
156
+ - Take fresh screenshots of fixed areas
157
+ - Confirm visually
158
+ - Report final status
159
+
160
+ ## Screenshot Locations
161
+
162
+ Screenshots are saved to `/tmp/tlc-screenshots/` for the session. Clean up after verification.
163
+
164
+ ## Guard Rails
165
+
166
+ - **Don't skip failures.** Every visual anomaly and log error gets investigated.
167
+ - **Don't change tests to match broken UI.** Fix the UI.
168
+ - **Clean up temp files.** Remove screenshot scripts and temp screenshots when done.
169
+ - **Respect server state.** Don't kill servers that were already running before verification.
170
+
171
+ ## Example
172
+
173
+ ```
174
+ > /tlc:e2e-verify
175
+
176
+ Checking servers...
177
+ Server (3148): ✅ running
178
+ Dashboard (4000): ✅ running
179
+
180
+ Running e2e tests...
181
+ 12/12 passing ✅
182
+
183
+ Taking screenshots...
184
+ 📸 /dashboard — captured
185
+ 📸 /login — captured
186
+ 📸 /settings — captured
187
+ 📸 /phases — captured
188
+
189
+ Reviewing screenshots...
190
+ /dashboard: ✅ Layout correct, data loading, no errors
191
+ /login: ✅ Form renders, labels correct
192
+ /settings: ⚠️ Theme toggle shows "undefined" label
193
+ /phases: ✅ Phase list renders correctly
194
+
195
+ Checking logs...
196
+ Server: ✅ No errors
197
+ Console: ⚠️ "Cannot read property 'label' of undefined" on /settings
198
+
199
+ Found 1 issue: Theme toggle label is undefined.
200
+ Reading settings component...
201
+
202
+ Fixed: Added fallback label for theme toggle.
203
+ Re-verifying /settings... ✅ Label now shows "Dark Mode"
204
+
205
+ ✅ E2E Verification Complete — all clear.
206
+ ```
207
+
208
+ ## When to Use
209
+
210
+ - After `/tlc:build` completes a phase
211
+ - After `/tlc:watchci` reports green
212
+ - Before marking a phase as verified (`/tlc:verify`)
213
+ - Any time you want visual proof that the app works
214
+ - Combine with `/tlc:watchci`: build → push → watchci → e2e-verify