tlc-claude-code 0.6.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.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/bin/install.js +165 -0
- package/build.md +347 -0
- package/complete.md +160 -0
- package/coverage.md +222 -0
- package/discuss.md +185 -0
- package/help.md +115 -0
- package/init.md +219 -0
- package/install.sh +65 -0
- package/new-milestone.md +172 -0
- package/new-project.md +259 -0
- package/package.json +27 -0
- package/plan.md +210 -0
- package/progress.md +136 -0
- package/quick.md +52 -0
- package/status.md +65 -0
- package/tlc.md +217 -0
- package/verify.md +159 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jurgen Calleja
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# TLC
|
|
2
|
+
|
|
3
|
+
**Test Led Coding. Tests before code. Automatically.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx tlc-claude-code
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="assets/terminal.svg" alt="TLC" width="700">
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The Problem
|
|
16
|
+
|
|
17
|
+
You tell Claude to build something. It builds it. You test it manually. It's broken. You debug. Repeat.
|
|
18
|
+
|
|
19
|
+
**That's backwards.**
|
|
20
|
+
|
|
21
|
+
## The Solution
|
|
22
|
+
|
|
23
|
+
TLC writes tests *before* code exists. Every feature has a spec. Every spec is executable. When the code works, you know — because the tests pass.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
You describe → Tests are written → Code is implemented → Tests pass → Done
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
No manual testing. No "does this work?" No vibes.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx tlc-claude-code # Install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then in Claude Code:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
/tlc
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
That's it. One command. It knows what to do next.
|
|
46
|
+
|
|
47
|
+
Starting fresh? It asks what you're building.
|
|
48
|
+
Have existing code? It finds untested files.
|
|
49
|
+
Mid-project? It picks up where you left off.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
| Command | What |
|
|
56
|
+
|---------|------|
|
|
57
|
+
| `/tlc` | **Smart entry point. Knows what's next.** |
|
|
58
|
+
| `/tlc:new-project` | Start fresh. Discuss stack, scaffold. |
|
|
59
|
+
| `/tlc:init` | Add TLC to existing code. |
|
|
60
|
+
| `/tlc:coverage` | Find untested → write tests |
|
|
61
|
+
| `/tlc:quick` | One-off task with tests |
|
|
62
|
+
| `/tlc:status` | Pass/fail counts |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## What Makes This Different
|
|
67
|
+
|
|
68
|
+
### 1. Tests First, Always
|
|
69
|
+
|
|
70
|
+
Other workflows: plan → build → "hope it works"
|
|
71
|
+
|
|
72
|
+
TLC: plan → **write failing tests** → build until tests pass
|
|
73
|
+
|
|
74
|
+
The tests *are* the spec. No ambiguity.
|
|
75
|
+
|
|
76
|
+
### 2. Smart Stack Selection
|
|
77
|
+
|
|
78
|
+
Don't pick tech in a vacuum. TLC asks what you're building, who uses it, what scale — then suggests the right stack.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Building: Internal dashboard
|
|
82
|
+
Scale: Small team
|
|
83
|
+
Data: Simple CRUD
|
|
84
|
+
|
|
85
|
+
→ Suggested: Next.js + SQLite + Vercel
|
|
86
|
+
→ Why: Fast to build, cheap to host, fits your needs
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Parallel Agents
|
|
90
|
+
|
|
91
|
+
Up to 3 Claude instances working simultaneously. GitHub issues as task queue. Watch them go.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
┌──────────────────────────────────────────────────────┐
|
|
95
|
+
│ Agents │
|
|
96
|
+
│ [1] ● Working on #42: Auth flow │
|
|
97
|
+
│ [2] ● Working on #43: User CRUD │
|
|
98
|
+
│ [3] ○ Idle │
|
|
99
|
+
└──────────────────────────────────────────────────────┘
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 4. GitHub Integration
|
|
103
|
+
|
|
104
|
+
Plans approved → issues created automatically. Tasks complete → issues closed. Full audit trail.
|
|
105
|
+
|
|
106
|
+
### 5. Live Preview
|
|
107
|
+
|
|
108
|
+
Docker container spins up. See your app as it's built. Not after.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Dashboard (Coming Soon)
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
┌─────────────────────────────────┬──────────────────────┐
|
|
116
|
+
│ Chat │ GitHub Issues │
|
|
117
|
+
│ │ #42 Auth flow WIP │
|
|
118
|
+
│ Building login endpoint... │ #43 User CRUD │
|
|
119
|
+
│ ✓ Created tests/auth.test.ts │ #44 Dashboard │
|
|
120
|
+
│ ✓ Tests failing (expected) ├──────────────────────┤
|
|
121
|
+
│ Implementing... │ Agents (2/3) │
|
|
122
|
+
│ │ [1] ● #42 │
|
|
123
|
+
│ │ [2] ● #43 │
|
|
124
|
+
│ │ [3] ○ Idle │
|
|
125
|
+
├─────────────────────────────────┼──────────────────────┤
|
|
126
|
+
│ > add password reset flow │ Tests: 23/23 ✓ │
|
|
127
|
+
└─────────────────────────────────┴──────────────────────┘
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
TUI dashboard. Multiple panes. Real-time updates.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Philosophy
|
|
135
|
+
|
|
136
|
+
**Tests define behavior. Code makes tests pass.**
|
|
137
|
+
|
|
138
|
+
- Tests written BEFORE code
|
|
139
|
+
- Tests are the spec, not an afterthought
|
|
140
|
+
- If it's not tested, it doesn't exist
|
|
141
|
+
- Human verification still happens — tests catch logic errors, you catch "not what I meant"
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## vs Other Approaches
|
|
146
|
+
|
|
147
|
+
| Approach | Process | Result |
|
|
148
|
+
|----------|---------|--------|
|
|
149
|
+
| Vibe coding | Build → hope | Works until it doesn't |
|
|
150
|
+
| Manual TDD | Write tests yourself | Slow, easy to skip |
|
|
151
|
+
| **TLC** | Tests auto-generated first | Fast, guaranteed coverage |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Install
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx tlc-claude-code
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Options:
|
|
162
|
+
- `--global` — Available everywhere
|
|
163
|
+
- `--local` — This project only
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
|
|
7
|
+
// ANSI color codes
|
|
8
|
+
const c = {
|
|
9
|
+
reset: '\x1b[0m',
|
|
10
|
+
bold: '\x1b[1m',
|
|
11
|
+
dim: '\x1b[2m',
|
|
12
|
+
cyan: '\x1b[36m',
|
|
13
|
+
green: '\x1b[32m',
|
|
14
|
+
yellow: '\x1b[33m',
|
|
15
|
+
magenta: '\x1b[35m',
|
|
16
|
+
white: '\x1b[37m',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const LOGO = `
|
|
20
|
+
${c.cyan} ████████╗██╗ ██████╗
|
|
21
|
+
╚══██╔══╝██║ ██╔════╝
|
|
22
|
+
██║ ██║ ██║
|
|
23
|
+
██║ ██║ ██║
|
|
24
|
+
██║ ███████╗╚██████╗
|
|
25
|
+
╚═╝ ╚══════╝ ╚═════╝${c.reset}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const VERSION = '0.6.0';
|
|
29
|
+
|
|
30
|
+
const COMMANDS = [
|
|
31
|
+
'tlc.md',
|
|
32
|
+
'new-project.md',
|
|
33
|
+
'init.md',
|
|
34
|
+
'coverage.md',
|
|
35
|
+
'discuss.md',
|
|
36
|
+
'plan.md',
|
|
37
|
+
'build.md',
|
|
38
|
+
'verify.md',
|
|
39
|
+
'status.md',
|
|
40
|
+
'progress.md',
|
|
41
|
+
'complete.md',
|
|
42
|
+
'new-milestone.md',
|
|
43
|
+
'quick.md',
|
|
44
|
+
'help.md'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function getGlobalDir() {
|
|
48
|
+
const claudeConfig = process.env.CLAUDE_CONFIG_DIR || path.join(require('os').homedir(), '.claude');
|
|
49
|
+
return path.join(claudeConfig, 'commands');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getLocalDir() {
|
|
53
|
+
return path.join(process.cwd(), '.claude', 'commands');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printBanner() {
|
|
57
|
+
console.log(LOGO);
|
|
58
|
+
console.log(` ${c.bold}TLC${c.reset} ${c.dim}v${VERSION}${c.reset}`);
|
|
59
|
+
console.log(` ${c.white}Test Led Coding for Claude Code${c.reset}`);
|
|
60
|
+
console.log(` ${c.dim}Tests before code, automatically${c.reset}`);
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function log(msg) {
|
|
65
|
+
console.log(` ${msg}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function success(msg) {
|
|
69
|
+
console.log(` ${c.green}✓${c.reset} ${msg}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function info(msg) {
|
|
73
|
+
console.log(` ${c.cyan}→${c.reset} ${msg}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Standalone - no external dependencies needed
|
|
77
|
+
|
|
78
|
+
function install(targetDir, installType) {
|
|
79
|
+
const commandsDir = path.join(targetDir, 'tlc');
|
|
80
|
+
|
|
81
|
+
// Create directory
|
|
82
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
83
|
+
|
|
84
|
+
// Copy command files
|
|
85
|
+
const sourceDir = path.join(__dirname, '..');
|
|
86
|
+
let installed = 0;
|
|
87
|
+
for (const file of COMMANDS) {
|
|
88
|
+
const src = path.join(sourceDir, file);
|
|
89
|
+
const dest = path.join(commandsDir, file);
|
|
90
|
+
if (fs.existsSync(src)) {
|
|
91
|
+
fs.copyFileSync(src, dest);
|
|
92
|
+
installed++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
success(`Installed ${installed} commands to ${c.cyan}${commandsDir}${c.reset}`);
|
|
97
|
+
log('');
|
|
98
|
+
log(`${c.green}Done!${c.reset} Restart Claude Code to load commands.`);
|
|
99
|
+
log('');
|
|
100
|
+
log(`${c.bold}Quick Start:${c.reset}`);
|
|
101
|
+
log(` ${c.cyan}/tlc${c.reset} Smart entry point - knows what to do next`);
|
|
102
|
+
log('');
|
|
103
|
+
log(`${c.dim}Or use specific commands:${c.reset}`);
|
|
104
|
+
log(` ${c.cyan}/tlc:new-project${c.reset} Start new project`);
|
|
105
|
+
log(` ${c.cyan}/tlc:init${c.reset} Add TLC to existing code`);
|
|
106
|
+
log(` ${c.cyan}/tlc:coverage${c.reset} Find and fix test gaps`);
|
|
107
|
+
log('');
|
|
108
|
+
log(`Run ${c.cyan}/tlc:help${c.reset} for all commands.`);
|
|
109
|
+
log('');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main() {
|
|
113
|
+
const args = process.argv.slice(2);
|
|
114
|
+
|
|
115
|
+
printBanner();
|
|
116
|
+
|
|
117
|
+
if (args.includes('--global') || args.includes('-g')) {
|
|
118
|
+
info(`Installing ${c.bold}globally${c.reset} to ~/.claude/commands/tlc`);
|
|
119
|
+
log('');
|
|
120
|
+
install(getGlobalDir(), 'global');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (args.includes('--local') || args.includes('-l')) {
|
|
125
|
+
info(`Installing ${c.bold}locally${c.reset} to ./.claude/commands/tlc`);
|
|
126
|
+
log('');
|
|
127
|
+
install(getLocalDir(), 'local');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if non-interactive
|
|
132
|
+
if (!process.stdin.isTTY) {
|
|
133
|
+
log(`${c.yellow}Non-interactive terminal detected, defaulting to global install${c.reset}`);
|
|
134
|
+
log('');
|
|
135
|
+
install(getGlobalDir(), 'global');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Interactive prompt
|
|
140
|
+
const rl = readline.createInterface({
|
|
141
|
+
input: process.stdin,
|
|
142
|
+
output: process.stdout
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
log('Where would you like to install?');
|
|
146
|
+
log(` ${c.bold}1)${c.reset} Global ${c.dim}(~/.claude/commands/tlc)${c.reset} - available in all projects`);
|
|
147
|
+
log(` ${c.bold}2)${c.reset} Local ${c.dim}(./.claude/commands/tlc)${c.reset} - this project only`);
|
|
148
|
+
log('');
|
|
149
|
+
|
|
150
|
+
rl.question(' Choice [1/2]: ', (answer) => {
|
|
151
|
+
rl.close();
|
|
152
|
+
console.log('');
|
|
153
|
+
if (answer === '2') {
|
|
154
|
+
info(`Installing ${c.bold}locally${c.reset}`);
|
|
155
|
+
log('');
|
|
156
|
+
install(getLocalDir(), 'local');
|
|
157
|
+
} else {
|
|
158
|
+
info(`Installing ${c.bold}globally${c.reset}`);
|
|
159
|
+
log('');
|
|
160
|
+
install(getGlobalDir(), 'global');
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
main();
|
package/build.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# /tlc:build - Build a Phase (Test-First)
|
|
2
|
+
|
|
3
|
+
Write failing tests, then implement to make them pass.
|
|
4
|
+
|
|
5
|
+
## What This Does
|
|
6
|
+
|
|
7
|
+
1. **Write failing tests** for all tasks in the phase
|
|
8
|
+
2. **Verify tests fail** (Red)
|
|
9
|
+
3. **Implement code** one task at a time (Green)
|
|
10
|
+
4. **Verify tests pass** after each task
|
|
11
|
+
5. **Commit** after each passing task
|
|
12
|
+
|
|
13
|
+
This is the core TLC command. Tests before code, one task at a time.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/tlc:build <phase_number>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Process
|
|
22
|
+
|
|
23
|
+
### Step 1: Load Plans
|
|
24
|
+
|
|
25
|
+
Read all `.planning/phases/{phase}-*-PLAN.md` files for this phase.
|
|
26
|
+
|
|
27
|
+
### Step 2: Detect Test Framework
|
|
28
|
+
|
|
29
|
+
Check what's already set up:
|
|
30
|
+
- `vitest.config.*` → Vitest
|
|
31
|
+
- `jest.config.*` → Jest
|
|
32
|
+
- `pytest.ini` or `pyproject.toml` with pytest → pytest
|
|
33
|
+
- `spec/` directory → RSpec
|
|
34
|
+
- None found → Set up based on PROJECT.md stack (see framework defaults below)
|
|
35
|
+
|
|
36
|
+
### Step 3: Plan Tests for Each Task
|
|
37
|
+
|
|
38
|
+
Before writing any tests, create a test plan for each task in the phase.
|
|
39
|
+
|
|
40
|
+
For each task in the plan:
|
|
41
|
+
|
|
42
|
+
1. **Read the task** — understand what behavior is being specified
|
|
43
|
+
2. **Identify test cases:**
|
|
44
|
+
- Happy path (expected inputs → expected outputs)
|
|
45
|
+
- Edge cases mentioned in `<action>`
|
|
46
|
+
- Error conditions from `<verify>`
|
|
47
|
+
3. **Create test plan entry**
|
|
48
|
+
|
|
49
|
+
Create `.planning/phases/{phase}-TEST-PLAN.md`:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
# Phase {N} Test Plan
|
|
53
|
+
|
|
54
|
+
## Task: {task-id} - {task-title}
|
|
55
|
+
|
|
56
|
+
### File: tests/{feature}.test.ts
|
|
57
|
+
|
|
58
|
+
| Test | Type | Expected Result |
|
|
59
|
+
|------|------|-----------------|
|
|
60
|
+
| user can log in with valid credentials | happy path | returns user object |
|
|
61
|
+
| login rejects invalid password | error | throws AuthError |
|
|
62
|
+
| login rejects empty email | edge case | throws ValidationError |
|
|
63
|
+
|
|
64
|
+
### Dependencies to mock:
|
|
65
|
+
- database connection
|
|
66
|
+
- email service
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Task: {task-id-2} - {task-title-2}
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Step 4: Write Tests One Task at a Time
|
|
75
|
+
|
|
76
|
+
**For each task in the test plan, sequentially:**
|
|
77
|
+
|
|
78
|
+
#### 4a. Write test file for this task
|
|
79
|
+
|
|
80
|
+
Follow the project's test patterns. Test names should describe expected behavior:
|
|
81
|
+
```
|
|
82
|
+
✓ "user can log in with valid credentials"
|
|
83
|
+
✓ "login rejects invalid password with 401"
|
|
84
|
+
✗ "test login" (too vague)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Vitest/Jest (TypeScript):**
|
|
88
|
+
```typescript
|
|
89
|
+
import { describe, it, expect } from 'vitest'
|
|
90
|
+
import { login } from '../src/auth/login'
|
|
91
|
+
|
|
92
|
+
describe('login', () => {
|
|
93
|
+
it('returns user object for valid credentials', async () => {
|
|
94
|
+
const result = await login('user@test.com', 'password123')
|
|
95
|
+
expect(result.user).toBeDefined()
|
|
96
|
+
expect(result.user.email).toBe('user@test.com')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('throws AuthError for invalid password', async () => {
|
|
100
|
+
await expect(login('user@test.com', 'wrong'))
|
|
101
|
+
.rejects.toThrow('Invalid credentials')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**pytest (Python):**
|
|
107
|
+
```python
|
|
108
|
+
import pytest
|
|
109
|
+
from src.auth import login
|
|
110
|
+
|
|
111
|
+
def test_login_returns_user_for_valid_credentials():
|
|
112
|
+
result = login("user@test.com", "password123")
|
|
113
|
+
assert result["user"]["email"] == "user@test.com"
|
|
114
|
+
|
|
115
|
+
def test_login_raises_for_invalid_password():
|
|
116
|
+
with pytest.raises(AuthError, match="Invalid credentials"):
|
|
117
|
+
login("user@test.com", "wrong")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### 4b. Run this test file
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm test -- tests/auth/login.test.ts # vitest
|
|
124
|
+
pytest tests/test_login.py # pytest
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Verify:
|
|
128
|
+
- ✅ Tests execute (no syntax errors)
|
|
129
|
+
- ✅ Tests FAIL (not pass, not skip)
|
|
130
|
+
- ❌ If import errors, add mocks/stubs and retry
|
|
131
|
+
|
|
132
|
+
#### 4c. Commit this test file
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
git add tests/auth/login.test.ts
|
|
136
|
+
git commit -m "test: add login tests (red) - phase {N}"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### 4d. Move to next task
|
|
140
|
+
|
|
141
|
+
Repeat 4a-4c for each task in the test plan.
|
|
142
|
+
|
|
143
|
+
**Critical Rules:**
|
|
144
|
+
- Tests must be **syntactically valid** and **runnable**
|
|
145
|
+
- Tests must **FAIL** because code doesn't exist yet
|
|
146
|
+
- Tests must NOT **ERROR** from import issues — mock if needed
|
|
147
|
+
- Do NOT write any implementation code
|
|
148
|
+
- Do NOT skip or stub out the actual assertions
|
|
149
|
+
- **One task at a time, verify, commit, then next**
|
|
150
|
+
|
|
151
|
+
### Step 5: Verify All Tests Fail (Red)
|
|
152
|
+
|
|
153
|
+
Run the full test suite:
|
|
154
|
+
```bash
|
|
155
|
+
npm test # or vitest run, pytest, etc.
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Check output:
|
|
159
|
+
- ✅ All new tests executed (no syntax errors)
|
|
160
|
+
- ✅ All new tests FAILED (not passed, not skipped)
|
|
161
|
+
- ❌ If tests error on imports, add mocks and retry
|
|
162
|
+
- ❌ If tests pass, something's wrong — investigate
|
|
163
|
+
|
|
164
|
+
### Step 6: Create Test Summary
|
|
165
|
+
|
|
166
|
+
Create `.planning/phases/{phase}-TESTS.md`:
|
|
167
|
+
|
|
168
|
+
```markdown
|
|
169
|
+
# Phase {N} Tests
|
|
170
|
+
|
|
171
|
+
Generated: {timestamp}
|
|
172
|
+
Status: ✅ All tests failing (Red)
|
|
173
|
+
|
|
174
|
+
## Test Files
|
|
175
|
+
|
|
176
|
+
| File | Tests | Status |
|
|
177
|
+
|------|-------|--------|
|
|
178
|
+
| tests/auth.test.ts | 4 | ❌ Failing |
|
|
179
|
+
| tests/session.test.ts | 3 | ❌ Failing |
|
|
180
|
+
|
|
181
|
+
## Test Output
|
|
182
|
+
|
|
183
|
+
{test runner output showing failures}
|
|
184
|
+
|
|
185
|
+
## Coverage Map
|
|
186
|
+
|
|
187
|
+
| Test | Task |
|
|
188
|
+
|------|------|
|
|
189
|
+
| user can log in with valid credentials | 01-task-1 |
|
|
190
|
+
| login rejects invalid password | 01-task-1 |
|
|
191
|
+
| session persists across requests | 01-task-2 |
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Step 7: Execute Implementation (Green)
|
|
195
|
+
|
|
196
|
+
Now implement the code to make tests pass. Work through each task sequentially:
|
|
197
|
+
|
|
198
|
+
**For each task in the plan:**
|
|
199
|
+
|
|
200
|
+
#### 7a. Read the task
|
|
201
|
+
Review the task's:
|
|
202
|
+
- Goal and expected behavior
|
|
203
|
+
- Acceptance criteria
|
|
204
|
+
- Test cases (now written and failing)
|
|
205
|
+
|
|
206
|
+
#### 7b. Implement the code
|
|
207
|
+
Write the minimum code needed to pass the tests:
|
|
208
|
+
- Create files specified in the task
|
|
209
|
+
- Follow existing project patterns
|
|
210
|
+
- Reference the failing tests for exact expected behavior
|
|
211
|
+
|
|
212
|
+
#### 7c. Run tests for this task
|
|
213
|
+
```bash
|
|
214
|
+
npm test -- tests/auth/login.test.ts # specific file
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- ✅ Tests pass → Continue
|
|
218
|
+
- ❌ Tests fail → Fix implementation, retry
|
|
219
|
+
|
|
220
|
+
#### 7d. Commit this task
|
|
221
|
+
```bash
|
|
222
|
+
git add src/auth/login.ts tests/auth/login.test.ts
|
|
223
|
+
git commit -m "feat: {task-title} - phase {N}"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### 7e. Move to next task
|
|
227
|
+
Repeat 7a-7d for each task in the phase.
|
|
228
|
+
|
|
229
|
+
**Critical Rules:**
|
|
230
|
+
- Implement **one task at a time**
|
|
231
|
+
- Run tests **after each task**
|
|
232
|
+
- Commit **after each passing task**
|
|
233
|
+
- Do NOT batch — sequential execution catches issues early
|
|
234
|
+
|
|
235
|
+
### Step 8: Verify All Tests Pass (Green)
|
|
236
|
+
|
|
237
|
+
After execution completes, run tests again:
|
|
238
|
+
```bash
|
|
239
|
+
npm test
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Check output:
|
|
243
|
+
- ✅ All tests PASS → Continue to verify
|
|
244
|
+
- ❌ Some tests fail → Report which tasks need fixes
|
|
245
|
+
|
|
246
|
+
### Step 9: Update Test Summary
|
|
247
|
+
|
|
248
|
+
Update `.planning/phases/{phase}-TESTS.md`:
|
|
249
|
+
|
|
250
|
+
```markdown
|
|
251
|
+
Status: ✅ All tests passing (Green)
|
|
252
|
+
|
|
253
|
+
## Final Test Output
|
|
254
|
+
|
|
255
|
+
{test runner output showing all pass}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Framework Defaults (for new projects)
|
|
259
|
+
|
|
260
|
+
If no test framework detected, set up based on PROJECT.md:
|
|
261
|
+
|
|
262
|
+
| Stack in PROJECT.md | Framework | Setup |
|
|
263
|
+
|---------------------|-----------|-------|
|
|
264
|
+
| Next.js, React, Vite | Vitest | `npm install -D vitest`, create `vitest.config.ts` |
|
|
265
|
+
| Node.js, Express | Vitest | `npm install -D vitest`, create `vitest.config.ts` |
|
|
266
|
+
| Python, FastAPI, Flask | pytest | `pip install pytest`, create `pytest.ini` |
|
|
267
|
+
| Go | go test | Built-in, create `*_test.go` files |
|
|
268
|
+
| Ruby, Rails | RSpec | `gem install rspec`, `rspec --init` |
|
|
269
|
+
|
|
270
|
+
Default Vitest config:
|
|
271
|
+
```typescript
|
|
272
|
+
import { defineConfig } from 'vitest/config'
|
|
273
|
+
|
|
274
|
+
export default defineConfig({
|
|
275
|
+
test: {
|
|
276
|
+
globals: true,
|
|
277
|
+
environment: 'node', // or 'jsdom' for React
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Default pytest.ini:
|
|
283
|
+
```ini
|
|
284
|
+
[pytest]
|
|
285
|
+
testpaths = tests
|
|
286
|
+
python_files = test_*.py
|
|
287
|
+
python_functions = test_*
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Example Run
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
User: /tlc:build 1
|
|
294
|
+
|
|
295
|
+
Claude: Loading phase 1 plans...
|
|
296
|
+
Found 2 plans:
|
|
297
|
+
- 01-01-PLAN.md: User authentication
|
|
298
|
+
- 01-02-PLAN.md: Session management
|
|
299
|
+
|
|
300
|
+
Checking test framework... Vitest detected.
|
|
301
|
+
|
|
302
|
+
Writing tests for 01-01-PLAN.md...
|
|
303
|
+
Created: tests/auth/login.test.ts (4 tests)
|
|
304
|
+
Created: tests/auth/logout.test.ts (2 tests)
|
|
305
|
+
|
|
306
|
+
Writing tests for 01-02-PLAN.md...
|
|
307
|
+
Created: tests/session/session.test.ts (5 tests)
|
|
308
|
+
|
|
309
|
+
Running tests...
|
|
310
|
+
❌ 11 tests failing (expected - no implementation yet)
|
|
311
|
+
|
|
312
|
+
Created 01-TESTS.md
|
|
313
|
+
|
|
314
|
+
Implementing task 1/3: User login...
|
|
315
|
+
✅ tests/auth/login.test.ts passing
|
|
316
|
+
Committed: feat: user login - phase 1
|
|
317
|
+
|
|
318
|
+
Implementing task 2/3: User registration...
|
|
319
|
+
✅ tests/auth/register.test.ts passing
|
|
320
|
+
Committed: feat: user registration - phase 1
|
|
321
|
+
|
|
322
|
+
Implementing task 3/3: Session management...
|
|
323
|
+
✅ tests/session/session.test.ts passing
|
|
324
|
+
Committed: feat: session management - phase 1
|
|
325
|
+
|
|
326
|
+
Running tests again...
|
|
327
|
+
✅ 11 tests passing
|
|
328
|
+
|
|
329
|
+
Phase 1 complete. Ready for /tlc:verify 1
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Error Recovery
|
|
333
|
+
|
|
334
|
+
**Tests error instead of fail:**
|
|
335
|
+
- Missing imports → Add mocks for dependencies
|
|
336
|
+
- Syntax errors → Fix test code
|
|
337
|
+
- Framework issues → Check config
|
|
338
|
+
|
|
339
|
+
**Tests pass before implementation:**
|
|
340
|
+
- Code already exists? Check if reimplementing
|
|
341
|
+
- Tests too weak? Strengthen assertions
|
|
342
|
+
- Wrong file paths? Check imports
|
|
343
|
+
|
|
344
|
+
**Some tests fail after implementation:**
|
|
345
|
+
- Report specific failures
|
|
346
|
+
- Suggest running `/tlc:build {phase}` again to retry
|
|
347
|
+
- Or manually fix and run `/tlc:status` to verify
|