tlc-claude-code 2.4.10 → 2.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.
Files changed (86) hide show
  1. package/.claude/commands/tlc/autofix.md +34 -1
  2. package/.claude/commands/tlc/build.md +203 -27
  3. package/.claude/commands/tlc/ci.md +178 -414
  4. package/.claude/commands/tlc/coverage.md +34 -0
  5. package/.claude/commands/tlc/deploy.md +19 -6
  6. package/.claude/commands/tlc/discuss.md +34 -0
  7. package/.claude/commands/tlc/docs.md +35 -1
  8. package/.claude/commands/tlc/e2e.md +300 -0
  9. package/.claude/commands/tlc/edge-cases.md +35 -1
  10. package/.claude/commands/tlc/init.md +38 -8
  11. package/.claude/commands/tlc/issues.md +46 -0
  12. package/.claude/commands/tlc/new-project.md +46 -4
  13. package/.claude/commands/tlc/plan.md +76 -0
  14. package/.claude/commands/tlc/quick.md +33 -0
  15. package/.claude/commands/tlc/release.md +85 -135
  16. package/.claude/commands/tlc/restore.md +14 -0
  17. package/.claude/commands/tlc/review.md +80 -1
  18. package/.claude/commands/tlc/tlc.md +134 -0
  19. package/.claude/commands/tlc/verify.md +64 -65
  20. package/.claude/commands/tlc/watchci.md +10 -0
  21. package/.claude/hooks/tlc-block-tools.sh +13 -0
  22. package/.claude/hooks/tlc-session-init.sh +9 -0
  23. package/CODING-STANDARDS.md +35 -10
  24. package/package.json +1 -1
  25. package/server/lib/block-tools-hook.js +23 -0
  26. package/server/lib/e2e/acceptance-parser.js +132 -0
  27. package/server/lib/e2e/acceptance-parser.test.js +110 -0
  28. package/server/lib/e2e/framework-detector.js +47 -0
  29. package/server/lib/e2e/framework-detector.test.js +94 -0
  30. package/server/lib/e2e/log-assertions.js +107 -0
  31. package/server/lib/e2e/log-assertions.test.js +68 -0
  32. package/server/lib/e2e/test-generator.js +159 -0
  33. package/server/lib/e2e/test-generator.test.js +121 -0
  34. package/server/lib/e2e/verify-runner.js +191 -0
  35. package/server/lib/e2e/verify-runner.test.js +167 -0
  36. package/server/lib/github/config.js +458 -0
  37. package/server/lib/github/config.test.js +385 -0
  38. package/server/lib/github/gh-client.js +303 -0
  39. package/server/lib/github/gh-client.test.js +499 -0
  40. package/server/lib/github/gh-projects.js +594 -0
  41. package/server/lib/github/gh-projects.test.js +583 -0
  42. package/server/lib/github/index.js +19 -0
  43. package/server/lib/github/plan-sync.js +456 -0
  44. package/server/lib/github/plan-sync.test.js +805 -0
  45. package/server/lib/hooks/block-tools-hook.test.js +54 -0
  46. package/server/lib/orchestration/cli-dispatch.js +16 -1
  47. package/server/lib/orchestration/cli-dispatch.test.js +94 -8
  48. package/server/lib/orchestration/completion-checker.js +101 -0
  49. package/server/lib/orchestration/completion-checker.test.js +177 -0
  50. package/server/lib/orchestration/result-verifier.js +143 -0
  51. package/server/lib/orchestration/result-verifier.test.js +291 -0
  52. package/server/lib/orchestration/session-dispatcher.js +99 -0
  53. package/server/lib/orchestration/session-dispatcher.test.js +215 -0
  54. package/server/lib/orchestration/session-status.js +147 -0
  55. package/server/lib/orchestration/session-status.test.js +130 -0
  56. package/server/lib/release/agent-runner-updates.js +24 -0
  57. package/server/lib/release/agent-runner-updates.test.js +22 -0
  58. package/server/lib/release/changelog-generator.js +142 -0
  59. package/server/lib/release/changelog-generator.test.js +113 -0
  60. package/server/lib/release/ci-watcher.js +83 -0
  61. package/server/lib/release/ci-watcher.test.js +81 -0
  62. package/server/lib/release/health-checker.js +111 -0
  63. package/server/lib/release/health-checker.test.js +121 -0
  64. package/server/lib/release/release-pipeline.js +187 -0
  65. package/server/lib/release/release-pipeline.test.js +262 -0
  66. package/server/lib/release/version-bumper.js +183 -0
  67. package/server/lib/release/version-bumper.test.js +142 -0
  68. package/server/lib/routing-preamble.integration.test.js +12 -0
  69. package/server/lib/routing-preamble.js +13 -2
  70. package/server/lib/routing-preamble.test.js +49 -0
  71. package/server/lib/scaffolding/ci-detector.js +139 -0
  72. package/server/lib/scaffolding/ci-detector.test.js +198 -0
  73. package/server/lib/scaffolding/ci-scaffolder.js +347 -0
  74. package/server/lib/scaffolding/ci-scaffolder.test.js +157 -0
  75. package/server/lib/scaffolding/deploy-detector.js +135 -0
  76. package/server/lib/scaffolding/deploy-detector.test.js +106 -0
  77. package/server/lib/scaffolding/health-scaffold.js +374 -0
  78. package/server/lib/scaffolding/health-scaffold.test.js +99 -0
  79. package/server/lib/scaffolding/logger-scaffold.js +196 -0
  80. package/server/lib/scaffolding/logger-scaffold.test.js +146 -0
  81. package/server/lib/scaffolding/migration-detector.js +78 -0
  82. package/server/lib/scaffolding/migration-detector.test.js +127 -0
  83. package/server/lib/scaffolding/snapshot-manager.js +142 -0
  84. package/server/lib/scaffolding/snapshot-manager.test.js +225 -0
  85. package/server/lib/task-router-config.js +50 -20
  86. package/server/lib/task-router-config.test.js +29 -15
@@ -0,0 +1,191 @@
1
+ 'use strict';
2
+
3
+ const { parseAcceptanceCriteria } = require('./acceptance-parser.js');
4
+ const { detectE2eFramework } = require('./framework-detector.js');
5
+
6
+ function normalizeLogErrors(logErrors) {
7
+ if (Array.isArray(logErrors)) {
8
+ return logErrors
9
+ .map((entry) => String(entry || '').trim())
10
+ .filter(Boolean);
11
+ }
12
+
13
+ if (typeof logErrors === 'string') {
14
+ const trimmed = logErrors.trim();
15
+ return trimmed ? [trimmed] : [];
16
+ }
17
+
18
+ return [];
19
+ }
20
+
21
+ function buildExecFailureLogs(execResult) {
22
+ const explicitLogs = normalizeLogErrors(execResult && execResult.logErrors);
23
+ if (explicitLogs.length > 0) {
24
+ return explicitLogs;
25
+ }
26
+
27
+ const output = [execResult && execResult.stderr, execResult && execResult.stdout]
28
+ .filter((value) => typeof value === 'string')
29
+ .join('\n')
30
+ .trim();
31
+
32
+ if (output) {
33
+ return [output];
34
+ }
35
+
36
+ const exitCode = execResult && Number.isInteger(execResult.code) ? execResult.code : 1;
37
+ return [`E2E command exited with code ${exitCode}`];
38
+ }
39
+
40
+ function normalizeCriterionResult(entry, fallback) {
41
+ const status = entry && typeof entry.status === 'string'
42
+ ? entry.status.trim().toLowerCase()
43
+ : fallback.status;
44
+
45
+ return {
46
+ criterion: fallback.criterion,
47
+ status: status === 'verified' || status === 'manual' || status === 'failed'
48
+ ? status
49
+ : fallback.status,
50
+ screenshot: entry && typeof entry.screenshot === 'string' && entry.screenshot.trim()
51
+ ? entry.screenshot
52
+ : fallback.screenshot,
53
+ logErrors: entry && Object.prototype.hasOwnProperty.call(entry, 'logErrors')
54
+ ? normalizeLogErrors(entry.logErrors)
55
+ : fallback.logErrors,
56
+ };
57
+ }
58
+
59
+ function buildSummary(results) {
60
+ return results.reduce((summary, item) => {
61
+ if (item.status === 'verified') summary.verified += 1;
62
+ if (item.status === 'manual') summary.manual += 1;
63
+ if (item.status === 'failed') summary.failed += 1;
64
+ return summary;
65
+ }, { verified: 0, manual: 0, failed: 0 });
66
+ }
67
+
68
+ function buildCommand(framework) {
69
+ if (framework === 'playwright') {
70
+ return {
71
+ command: 'npx',
72
+ args: ['playwright', 'test', '--reporter=list'],
73
+ };
74
+ }
75
+
76
+ if (framework === 'supertest') {
77
+ return {
78
+ command: 'npx',
79
+ args: ['vitest', 'run'],
80
+ };
81
+ }
82
+
83
+ return null;
84
+ }
85
+
86
+ async function runVerification(options) {
87
+ const {
88
+ planPath,
89
+ projectDir,
90
+ exec,
91
+ fs,
92
+ } = options || {};
93
+
94
+ if (typeof planPath !== 'string' || !planPath.trim()) {
95
+ throw new TypeError('runVerification requires planPath');
96
+ }
97
+
98
+ if (typeof projectDir !== 'string' || !projectDir.trim()) {
99
+ throw new TypeError('runVerification requires projectDir');
100
+ }
101
+
102
+ if (typeof exec !== 'function') {
103
+ throw new TypeError('runVerification requires exec');
104
+ }
105
+
106
+ if (!fs || typeof fs.readFileSync !== 'function' || typeof fs.existsSync !== 'function') {
107
+ throw new TypeError('runVerification requires fs with readFileSync and existsSync');
108
+ }
109
+
110
+ const planContent = fs.readFileSync(planPath, 'utf8');
111
+ const criteria = parseAcceptanceCriteria({ planContent });
112
+ const manualResults = [];
113
+ const actionableCriteria = [];
114
+
115
+ for (const item of criteria) {
116
+ if (item.type === 'manual') {
117
+ manualResults.push({
118
+ criterion: item.criterion,
119
+ status: 'manual',
120
+ screenshot: null,
121
+ logErrors: [],
122
+ });
123
+ continue;
124
+ }
125
+
126
+ actionableCriteria.push(item);
127
+ }
128
+
129
+ if (actionableCriteria.length === 0) {
130
+ const results = manualResults;
131
+ return {
132
+ ...buildSummary(results),
133
+ results,
134
+ };
135
+ }
136
+
137
+ const { framework } = detectE2eFramework({ projectDir, fs });
138
+ const command = buildCommand(framework);
139
+
140
+ if (!command) {
141
+ const results = actionableCriteria.map(({ criterion }) => ({
142
+ criterion,
143
+ status: 'failed',
144
+ screenshot: null,
145
+ logErrors: ['No supported E2E framework detected'],
146
+ })).concat(manualResults);
147
+
148
+ return {
149
+ ...buildSummary(results),
150
+ results,
151
+ };
152
+ }
153
+
154
+ const execResult = await exec({
155
+ ...command,
156
+ cwd: projectDir,
157
+ });
158
+
159
+ const passed = execResult && execResult.code === 0;
160
+ const fallback = actionableCriteria.map(({ criterion }) => ({
161
+ criterion,
162
+ status: passed ? 'verified' : 'failed',
163
+ screenshot: execResult && typeof execResult.screenshot === 'string' ? execResult.screenshot : null,
164
+ logErrors: passed ? normalizeLogErrors(execResult && execResult.logErrors) : buildExecFailureLogs(execResult),
165
+ }));
166
+
167
+ const explicitResults = new Map();
168
+ for (const entry of Array.isArray(execResult && execResult.results) ? execResult.results : []) {
169
+ if (!entry || typeof entry.criterion !== 'string') {
170
+ continue;
171
+ }
172
+
173
+ explicitResults.set(entry.criterion.trim(), entry);
174
+ }
175
+
176
+ const actionableResults = fallback.map((item) => normalizeCriterionResult(
177
+ explicitResults.get(item.criterion),
178
+ item
179
+ ));
180
+
181
+ const results = actionableResults.concat(manualResults);
182
+
183
+ return {
184
+ ...buildSummary(results),
185
+ results,
186
+ };
187
+ }
188
+
189
+ module.exports = {
190
+ runVerification,
191
+ };
@@ -0,0 +1,167 @@
1
+ 'use strict';
2
+
3
+ import { describe, expect, it, vi } from 'vitest';
4
+
5
+ const { runVerification } = require('./verify-runner.js');
6
+
7
+ function createFs(planContent, extraFiles = {}) {
8
+ return {
9
+ readFileSync(path) {
10
+ if (path === '/tmp/phase-plan.md') {
11
+ return planContent;
12
+ }
13
+
14
+ if (Object.prototype.hasOwnProperty.call(extraFiles, path)) {
15
+ return extraFiles[path];
16
+ }
17
+
18
+ throw new Error(`Unexpected read: ${path}`);
19
+ },
20
+
21
+ existsSync(path) {
22
+ return Object.prototype.hasOwnProperty.call(extraFiles, path);
23
+ },
24
+ };
25
+ }
26
+
27
+ describe('runVerification', () => {
28
+ it('runs the detected E2E command and marks automatable criteria verified', async () => {
29
+ const fs = createFs(`
30
+ - [ ] User can click the export button
31
+ Data is retained for 30 days.
32
+ `, {
33
+ '/repo/package.json': JSON.stringify({
34
+ devDependencies: {
35
+ supertest: '^7.2.2',
36
+ },
37
+ }),
38
+ });
39
+
40
+ const exec = vi.fn().mockResolvedValue({
41
+ code: 0,
42
+ results: [
43
+ {
44
+ criterion: 'User can click the export button',
45
+ screenshot: 'artifacts/export.png',
46
+ logErrors: [],
47
+ },
48
+ ],
49
+ });
50
+
51
+ const result = await runVerification({
52
+ planPath: '/tmp/phase-plan.md',
53
+ projectDir: '/repo',
54
+ exec,
55
+ fs,
56
+ });
57
+
58
+ expect(exec).toHaveBeenCalledWith({
59
+ command: 'npx',
60
+ args: ['vitest', 'run'],
61
+ cwd: '/repo',
62
+ });
63
+ expect(result).toEqual({
64
+ verified: 1,
65
+ manual: 1,
66
+ failed: 0,
67
+ results: [
68
+ {
69
+ criterion: 'User can click the export button',
70
+ status: 'verified',
71
+ screenshot: 'artifacts/export.png',
72
+ logErrors: [],
73
+ },
74
+ {
75
+ criterion: 'Data is retained for 30 days.',
76
+ status: 'manual',
77
+ screenshot: null,
78
+ logErrors: [],
79
+ },
80
+ ],
81
+ });
82
+ });
83
+
84
+ it('uses playwright when the project has a playwright config', async () => {
85
+ const fs = createFs('- [ ] User can click the sign in button', {
86
+ '/repo/playwright.config.ts': 'export default {};',
87
+ });
88
+
89
+ const exec = vi.fn().mockResolvedValue({ code: 0 });
90
+
91
+ await runVerification({
92
+ planPath: '/tmp/phase-plan.md',
93
+ projectDir: '/repo',
94
+ exec,
95
+ fs,
96
+ });
97
+
98
+ expect(exec).toHaveBeenCalledWith({
99
+ command: 'npx',
100
+ args: ['playwright', 'test', '--reporter=list'],
101
+ cwd: '/repo',
102
+ });
103
+ });
104
+
105
+ it('fails automatable criteria when no supported E2E framework is detected', async () => {
106
+ const fs = createFs('- [ ] User can click the sign in button');
107
+ const exec = vi.fn();
108
+
109
+ const result = await runVerification({
110
+ planPath: '/tmp/phase-plan.md',
111
+ projectDir: '/repo',
112
+ exec,
113
+ fs,
114
+ });
115
+
116
+ expect(exec).not.toHaveBeenCalled();
117
+ expect(result).toEqual({
118
+ verified: 0,
119
+ manual: 0,
120
+ failed: 1,
121
+ results: [
122
+ {
123
+ criterion: 'User can click the sign in button',
124
+ status: 'failed',
125
+ screenshot: null,
126
+ logErrors: ['No supported E2E framework detected'],
127
+ },
128
+ ],
129
+ });
130
+ });
131
+
132
+ it('propagates failed executions into each automatable criterion', async () => {
133
+ const fs = createFs(`
134
+ Given the user is on the login page
135
+ When they click the sign in button
136
+ Then the dashboard page should load
137
+ `, {
138
+ '/repo/playwright.config.ts': 'export default {};',
139
+ });
140
+
141
+ const exec = vi.fn().mockResolvedValue({
142
+ code: 1,
143
+ stderr: 'Timeout while waiting for dashboard',
144
+ });
145
+
146
+ const result = await runVerification({
147
+ planPath: '/tmp/phase-plan.md',
148
+ projectDir: '/repo',
149
+ exec,
150
+ fs,
151
+ });
152
+
153
+ expect(result).toEqual({
154
+ verified: 0,
155
+ manual: 0,
156
+ failed: 1,
157
+ results: [
158
+ {
159
+ criterion: 'the dashboard page should load',
160
+ status: 'failed',
161
+ screenshot: null,
162
+ logErrors: ['Timeout while waiting for dashboard'],
163
+ },
164
+ ],
165
+ });
166
+ });
167
+ });