tlc-claude-code 2.2.1 → 2.4.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/.claude/agents/builder.md +17 -0
- package/.claude/commands/tlc/audit.md +12 -0
- package/.claude/commands/tlc/autofix.md +31 -0
- package/.claude/commands/tlc/build.md +98 -24
- package/.claude/commands/tlc/coverage.md +31 -0
- package/.claude/commands/tlc/discuss.md +31 -0
- package/.claude/commands/tlc/docs.md +31 -0
- package/.claude/commands/tlc/edge-cases.md +31 -0
- package/.claude/commands/tlc/guard.md +9 -0
- package/.claude/commands/tlc/init.md +12 -1
- package/.claude/commands/tlc/plan.md +31 -0
- package/.claude/commands/tlc/quick.md +31 -0
- package/.claude/commands/tlc/review.md +50 -0
- package/.claude/hooks/tlc-session-init.sh +14 -3
- package/CODING-STANDARDS.md +217 -10
- package/bin/setup-autoupdate.js +316 -87
- package/bin/setup-autoupdate.test.js +454 -34
- package/package.json +1 -1
- package/scripts/project-docs.js +1 -1
- package/server/lib/careful-patterns.js +142 -0
- package/server/lib/careful-patterns.test.js +164 -0
- package/server/lib/cli-dispatcher.js +98 -0
- package/server/lib/cli-dispatcher.test.js +249 -0
- package/server/lib/command-router.js +171 -0
- package/server/lib/command-router.test.js +336 -0
- package/server/lib/field-report.js +92 -0
- package/server/lib/field-report.test.js +195 -0
- package/server/lib/orchestration/worktree-manager.js +133 -0
- package/server/lib/orchestration/worktree-manager.test.js +198 -0
- package/server/lib/overdrive-command.js +31 -9
- package/server/lib/overdrive-command.test.js +25 -26
- package/server/lib/prompt-packager.js +98 -0
- package/server/lib/prompt-packager.test.js +185 -0
- package/server/lib/review-fixer.js +107 -0
- package/server/lib/review-fixer.test.js +152 -0
- package/server/lib/routing-command.js +159 -0
- package/server/lib/routing-command.test.js +290 -0
- package/server/lib/scope-checker.js +127 -0
- package/server/lib/scope-checker.test.js +175 -0
- package/server/lib/skill-validator.js +165 -0
- package/server/lib/skill-validator.test.js +289 -0
- package/server/lib/standards/standards-injector.js +6 -0
- package/server/lib/task-router-config.js +142 -0
- package/server/lib/task-router-config.test.js +428 -0
- package/server/lib/test-selector.js +127 -0
- package/server/lib/test-selector.test.js +172 -0
- package/server/setup.sh +271 -271
- package/server/templates/CLAUDE.md +6 -0
- package/server/templates/CODING-STANDARDS.md +356 -10
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// @depends server/lib/test-selector.js
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
parseDependsComment,
|
|
5
|
+
buildTestMap,
|
|
6
|
+
getAffectedTests,
|
|
7
|
+
formatSelection,
|
|
8
|
+
} from './test-selector.js';
|
|
9
|
+
|
|
10
|
+
describe('test-selector', () => {
|
|
11
|
+
describe('parseDependsComment', () => {
|
|
12
|
+
it('extracts a single @depends path', () => {
|
|
13
|
+
const result = parseDependsComment('// @depends src/lib/auth.js');
|
|
14
|
+
expect(result).toEqual(['src/lib/auth.js']);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('extracts multiple @depends from separate lines', () => {
|
|
18
|
+
const content = '// @depends src/a.js\n// @depends src/b.js';
|
|
19
|
+
const result = parseDependsComment(content);
|
|
20
|
+
expect(result).toEqual(['src/a.js', 'src/b.js']);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns empty array when no @depends present', () => {
|
|
24
|
+
const result = parseDependsComment('no depends here');
|
|
25
|
+
expect(result).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('handles comma-separated dependencies on one line', () => {
|
|
29
|
+
const result = parseDependsComment('// @depends src/a.js, src/b.js');
|
|
30
|
+
expect(result).toEqual(['src/a.js', 'src/b.js']);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('handles empty string', () => {
|
|
34
|
+
const result = parseDependsComment('');
|
|
35
|
+
expect(result).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('trims whitespace from paths', () => {
|
|
39
|
+
const result = parseDependsComment('// @depends src/foo.js ');
|
|
40
|
+
expect(result).toEqual(['src/foo.js']);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('ignores @depends inside non-comment lines', () => {
|
|
44
|
+
const content = 'const x = "// @depends fake.js";\n// @depends real.js';
|
|
45
|
+
const result = parseDependsComment(content);
|
|
46
|
+
// Both lines start with // so the string literal line won't match
|
|
47
|
+
// Actually the first line starts with 'const' so it won't match
|
|
48
|
+
expect(result).toEqual(['real.js']);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('buildTestMap', () => {
|
|
53
|
+
it('builds mapping from test files to their dependencies', async () => {
|
|
54
|
+
const mockReadFile = async (filePath) => {
|
|
55
|
+
const files = {
|
|
56
|
+
'tests/auth.test.js': '// @depends src/auth.js\nimport { auth } from "../src/auth.js";',
|
|
57
|
+
'tests/db.test.js': '// @depends src/db.js, src/models.js\nimport { db } from "../src/db.js";',
|
|
58
|
+
'tests/utils.test.js': 'import { utils } from "../src/utils.js";',
|
|
59
|
+
};
|
|
60
|
+
if (files[filePath] === undefined) {
|
|
61
|
+
throw new Error(`ENOENT: no such file: ${filePath}`);
|
|
62
|
+
}
|
|
63
|
+
return files[filePath];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const testFiles = ['tests/auth.test.js', 'tests/db.test.js', 'tests/utils.test.js'];
|
|
67
|
+
const result = await buildTestMap(testFiles, { readFile: mockReadFile });
|
|
68
|
+
|
|
69
|
+
expect(result).toBeInstanceOf(Map);
|
|
70
|
+
expect(result.get('tests/auth.test.js')).toEqual(['src/auth.js']);
|
|
71
|
+
expect(result.get('tests/db.test.js')).toEqual(['src/db.js', 'src/models.js']);
|
|
72
|
+
expect(result.get('tests/utils.test.js')).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('handles readFile errors gracefully by treating as no-depends', async () => {
|
|
76
|
+
const mockReadFile = async () => {
|
|
77
|
+
throw new Error('ENOENT: file not found');
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const result = await buildTestMap(['missing.test.js'], { readFile: mockReadFile });
|
|
81
|
+
|
|
82
|
+
expect(result.get('missing.test.js')).toEqual([]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns empty map for empty test file list', async () => {
|
|
86
|
+
const mockReadFile = async () => '';
|
|
87
|
+
const result = await buildTestMap([], { readFile: mockReadFile });
|
|
88
|
+
|
|
89
|
+
expect(result).toBeInstanceOf(Map);
|
|
90
|
+
expect(result.size).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('getAffectedTests', () => {
|
|
95
|
+
const testMap = new Map([
|
|
96
|
+
['tests/auth.test.js', ['src/auth.js']],
|
|
97
|
+
['tests/db.test.js', ['src/db.js', 'src/models.js']],
|
|
98
|
+
['tests/utils.test.js', []], // no @depends — always included
|
|
99
|
+
['tests/api.test.js', ['src/api.js', 'src/auth.js']],
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
it('returns tests depending on a changed file', () => {
|
|
103
|
+
const result = getAffectedTests(['src/auth.js'], testMap);
|
|
104
|
+
|
|
105
|
+
expect(result).toContain('tests/auth.test.js');
|
|
106
|
+
expect(result).toContain('tests/api.test.js');
|
|
107
|
+
// no-depends tests are always included
|
|
108
|
+
expect(result).toContain('tests/utils.test.js');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('returns only no-depends tests when changed file is unrelated', () => {
|
|
112
|
+
const result = getAffectedTests(['src/unrelated.js'], testMap);
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual(['tests/utils.test.js']);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('always includes tests with no @depends annotation', () => {
|
|
118
|
+
const result = getAffectedTests(['src/db.js'], testMap);
|
|
119
|
+
|
|
120
|
+
expect(result).toContain('tests/utils.test.js');
|
|
121
|
+
expect(result).toContain('tests/db.test.js');
|
|
122
|
+
expect(result).not.toContain('tests/auth.test.js');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('returns only no-depends tests when changedFiles is empty', () => {
|
|
126
|
+
const result = getAffectedTests([], testMap);
|
|
127
|
+
|
|
128
|
+
expect(result).toEqual(['tests/utils.test.js']);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('handles empty test map', () => {
|
|
132
|
+
const result = getAffectedTests(['src/auth.js'], new Map());
|
|
133
|
+
|
|
134
|
+
expect(result).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('handles multiple changed files', () => {
|
|
138
|
+
const result = getAffectedTests(['src/auth.js', 'src/db.js'], testMap);
|
|
139
|
+
|
|
140
|
+
expect(result).toContain('tests/auth.test.js');
|
|
141
|
+
expect(result).toContain('tests/db.test.js');
|
|
142
|
+
expect(result).toContain('tests/api.test.js');
|
|
143
|
+
expect(result).toContain('tests/utils.test.js');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('formatSelection', () => {
|
|
148
|
+
it('returns summary with skip count when not all tests selected', () => {
|
|
149
|
+
const result = formatSelection(3, 10);
|
|
150
|
+
|
|
151
|
+
expect(result).toBe('Running 3 of 10 tests (7 skipped — dependencies unchanged)');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns "all tests" message when all selected', () => {
|
|
155
|
+
const result = formatSelection(10, 10);
|
|
156
|
+
|
|
157
|
+
expect(result).toBe('Running all 10 tests');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('handles 1 of 1', () => {
|
|
161
|
+
const result = formatSelection(1, 1);
|
|
162
|
+
|
|
163
|
+
expect(result).toBe('Running all 1 tests');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('handles 0 of N', () => {
|
|
167
|
+
const result = formatSelection(0, 5);
|
|
168
|
+
|
|
169
|
+
expect(result).toBe('Running 0 of 5 tests (5 skipped — dependencies unchanged)');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|