tlc-claude-code 2.4.1 → 2.4.3

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 (34) hide show
  1. package/.claude/commands/tlc/autofix.md +70 -6
  2. package/.claude/commands/tlc/build.md +138 -6
  3. package/.claude/commands/tlc/coverage.md +70 -6
  4. package/.claude/commands/tlc/discuss.md +244 -129
  5. package/.claude/commands/tlc/docs.md +70 -6
  6. package/.claude/commands/tlc/e2e-verify.md +1 -1
  7. package/.claude/commands/tlc/edge-cases.md +70 -6
  8. package/.claude/commands/tlc/plan.md +147 -8
  9. package/.claude/commands/tlc/quick.md +70 -6
  10. package/.claude/commands/tlc/review.md +70 -6
  11. package/.claude/commands/tlc/tlc.md +204 -473
  12. package/CLAUDE.md +6 -5
  13. package/package.json +4 -1
  14. package/scripts/dev-link.sh +29 -0
  15. package/scripts/test-package.sh +54 -0
  16. package/scripts/version-sync.js +42 -0
  17. package/scripts/version-sync.test.js +100 -0
  18. package/server/lib/model-router.js +11 -2
  19. package/server/lib/model-router.test.js +27 -1
  20. package/server/lib/orchestration/codex-orchestrator.js +185 -0
  21. package/server/lib/orchestration/codex-orchestrator.test.js +221 -0
  22. package/server/lib/orchestration/dep-linker.js +61 -0
  23. package/server/lib/orchestration/dep-linker.test.js +174 -0
  24. package/server/lib/router-config.js +18 -3
  25. package/server/lib/router-config.test.js +57 -1
  26. package/server/lib/routing/index.js +34 -0
  27. package/server/lib/routing/index.test.js +33 -0
  28. package/server/lib/routing-command.js +11 -2
  29. package/server/lib/routing-command.test.js +39 -1
  30. package/server/lib/routing-preamble.integration.test.js +319 -0
  31. package/server/lib/routing-preamble.js +116 -0
  32. package/server/lib/routing-preamble.test.js +266 -0
  33. package/server/lib/task-router-config.js +35 -14
  34. package/server/lib/task-router-config.test.js +77 -13
@@ -10,12 +10,76 @@ This command supports multi-model routing via `~/.tlc/config.json`.
10
10
 
11
11
  1. Read routing config:
12
12
  ```bash
13
- node -e "
14
- const { resolveRouting } = require('./server/lib/task-router-config');
15
- const flagModel = (process.argv.find(a => a.startsWith("--model")) || "").replace("--model=", "").replace("--model ", "") || null;
16
- const r = resolveRouting({ command: 'autofix', flagModel, projectDir: process.cwd(), homeDir: process.env.HOME });
17
- console.log(JSON.stringify(r));
18
- " 2>/dev/null
13
+ node -e "const fs = require('fs');
14
+ const path = require('path');
15
+ const os = require('os');
16
+ function readJson(filePath, fileSystem) {
17
+ try {
18
+ return JSON.parse(fileSystem.readFileSync(filePath, 'utf8'));
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function loadPersonalConfig(options) {
24
+ const configPath = path.join(options.homeDir, '.tlc', 'config.json');
25
+ return readJson(configPath, options.fs);
26
+ }
27
+ function loadProjectOverride(options) {
28
+ const configPath = path.join(options.projectDir, '.tlc.json');
29
+ const data = readJson(configPath, options.fs);
30
+ return data && data.task_routing_override ? data.task_routing_override : null;
31
+ }
32
+ function resolveRouting(options) {
33
+ let models = ['claude'];
34
+ let strategy = 'single';
35
+ let source = 'shipped-defaults';
36
+ let providers;
37
+ const personal = loadPersonalConfig({ homeDir: options.homeDir, fs: options.fs });
38
+ if (personal) {
39
+ if (personal.model_providers) {
40
+ providers = personal.model_providers;
41
+ }
42
+ const personalRouting = personal.task_routing && personal.task_routing[options.command];
43
+ if (personalRouting) {
44
+ if (Array.isArray(personalRouting.models)) {
45
+ models = personalRouting.models.slice();
46
+ } else if (typeof personalRouting.model === 'string') {
47
+ models = [personalRouting.model];
48
+ }
49
+ if (personalRouting.strategy) {
50
+ strategy = personalRouting.strategy;
51
+ }
52
+ source = 'personal-config';
53
+ }
54
+ }
55
+ const projectOverride = loadProjectOverride({ projectDir: options.projectDir, fs: options.fs });
56
+ if (projectOverride) {
57
+ const overrideEntry = projectOverride[options.command];
58
+ if (overrideEntry) {
59
+ if (Array.isArray(overrideEntry.models)) {
60
+ models = overrideEntry.models.slice();
61
+ } else if (typeof overrideEntry.model === 'string') {
62
+ models = [overrideEntry.model];
63
+ }
64
+ if (overrideEntry.strategy) {
65
+ strategy = overrideEntry.strategy;
66
+ }
67
+ source = 'project-override';
68
+ }
69
+ }
70
+ if (options.flagModel) {
71
+ models = [options.flagModel];
72
+ strategy = 'single';
73
+ source = 'flag-override';
74
+ }
75
+ const result = { models, strategy, source };
76
+ if (providers) {
77
+ result.providers = providers;
78
+ }
79
+ return result;
80
+ }
81
+ const result = resolveRouting({ command: \"autofix\", flagModel: process.argv[1], projectDir: process.cwd(), homeDir: process.env.HOME || os.homedir(), fs });
82
+ process.stdout.write(JSON.stringify(result));" 2>/dev/null
19
83
  ```
20
84
 
21
85
  2. If `models[0]` is NOT `claude` (i.e., routed to external model):
@@ -10,12 +10,76 @@ This command supports multi-model routing via `~/.tlc/config.json`.
10
10
 
11
11
  1. Read routing config:
12
12
  ```bash
13
- node -e "
14
- const { resolveRouting } = require('./server/lib/task-router-config');
15
- const flagModel = (process.argv.find(a => a.startsWith("--model")) || "").replace("--model=", "").replace("--model ", "") || null;
16
- const r = resolveRouting({ command: 'build', flagModel, projectDir: process.cwd(), homeDir: process.env.HOME });
17
- console.log(JSON.stringify(r));
18
- " 2>/dev/null
13
+ node -e "const fs = require('fs');
14
+ const path = require('path');
15
+ const os = require('os');
16
+ function readJson(filePath, fileSystem) {
17
+ try {
18
+ return JSON.parse(fileSystem.readFileSync(filePath, 'utf8'));
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function loadPersonalConfig(options) {
24
+ const configPath = path.join(options.homeDir, '.tlc', 'config.json');
25
+ return readJson(configPath, options.fs);
26
+ }
27
+ function loadProjectOverride(options) {
28
+ const configPath = path.join(options.projectDir, '.tlc.json');
29
+ const data = readJson(configPath, options.fs);
30
+ return data && data.task_routing_override ? data.task_routing_override : null;
31
+ }
32
+ function resolveRouting(options) {
33
+ let models = ['claude'];
34
+ let strategy = 'single';
35
+ let source = 'shipped-defaults';
36
+ let providers;
37
+ const personal = loadPersonalConfig({ homeDir: options.homeDir, fs: options.fs });
38
+ if (personal) {
39
+ if (personal.model_providers) {
40
+ providers = personal.model_providers;
41
+ }
42
+ const personalRouting = personal.task_routing && personal.task_routing[options.command];
43
+ if (personalRouting) {
44
+ if (Array.isArray(personalRouting.models)) {
45
+ models = personalRouting.models.slice();
46
+ } else if (typeof personalRouting.model === 'string') {
47
+ models = [personalRouting.model];
48
+ }
49
+ if (personalRouting.strategy) {
50
+ strategy = personalRouting.strategy;
51
+ }
52
+ source = 'personal-config';
53
+ }
54
+ }
55
+ const projectOverride = loadProjectOverride({ projectDir: options.projectDir, fs: options.fs });
56
+ if (projectOverride) {
57
+ const overrideEntry = projectOverride[options.command];
58
+ if (overrideEntry) {
59
+ if (Array.isArray(overrideEntry.models)) {
60
+ models = overrideEntry.models.slice();
61
+ } else if (typeof overrideEntry.model === 'string') {
62
+ models = [overrideEntry.model];
63
+ }
64
+ if (overrideEntry.strategy) {
65
+ strategy = overrideEntry.strategy;
66
+ }
67
+ source = 'project-override';
68
+ }
69
+ }
70
+ if (options.flagModel) {
71
+ models = [options.flagModel];
72
+ strategy = 'single';
73
+ source = 'flag-override';
74
+ }
75
+ const result = { models, strategy, source };
76
+ if (providers) {
77
+ result.providers = providers;
78
+ }
79
+ return result;
80
+ }
81
+ const result = resolveRouting({ command: \"build\", flagModel: process.argv[1], projectDir: process.cwd(), homeDir: process.env.HOME || os.homedir(), fs });
82
+ process.stdout.write(JSON.stringify(result));" 2>/dev/null
19
83
  ```
20
84
 
21
85
  2. If `models[0]` is NOT `claude` (i.e., routed to external model):
@@ -212,6 +276,34 @@ This is the core TLC command. Tests before code, one task at a time.
212
276
 
213
277
  ## Process
214
278
 
279
+ ### Step 0: Create Phase Branch (Mandatory)
280
+
281
+ **Never commit directly to the main branch.** Before ANY work begins:
282
+
283
+ ```bash
284
+ # Get main branch name from .tlc.json or default to "main"
285
+ mainBranch=$(node -e "try{console.log(require('./.tlc.json').git?.mainBranch||'main')}catch{console.log('main')}" 2>/dev/null)
286
+
287
+ # Check if already on a phase branch
288
+ currentBranch=$(git branch --show-current)
289
+ if [[ "$currentBranch" == phase/* ]]; then
290
+ echo "Already on phase branch: $currentBranch"
291
+ else
292
+ # Create phase branch
293
+ branchName="phase/${phase_number}"
294
+ git checkout -b "$branchName" "$mainBranch" 2>/dev/null || git checkout "$branchName"
295
+ echo "Working on branch: $branchName"
296
+ fi
297
+ ```
298
+
299
+ **This is not optional.** All commits go to the phase branch. When the phase is complete:
300
+ 1. Push the branch: `git push -u origin phase/{N}`
301
+ 2. Create PR: `gh pr create --base main --head phase/{N}`
302
+ 3. CI validates on the PR
303
+ 4. Merge when green
304
+
305
+ **Why:** TLC projects use integration branches. Committing to main bypasses CI, review, and the PR workflow we built. Eat your own soup.
306
+
215
307
  ### Step 1: Load Plans
216
308
 
217
309
  Read all `.planning/phases/{phase}-*-PLAN.md` files for this phase.
@@ -852,6 +944,46 @@ Verdict: ❌ CHANGES REQUESTED
852
944
 
853
945
  **CRITICAL: Phase is NOT complete until review passes.**
854
946
 
947
+ ### Step 11: Push Branch and Create PR (Mandatory)
948
+
949
+ **After review passes, push the phase branch and create a PR. Ask the user before pushing.**
950
+
951
+ ```bash
952
+ # Get current branch
953
+ branchName=$(git branch --show-current)
954
+ mainBranch=$(node -e "try{console.log(require('./.tlc.json').git?.mainBranch||'main')}catch{console.log('main')}" 2>/dev/null)
955
+
956
+ # Confirm with user
957
+ echo "Phase complete. Ready to push $branchName and create PR to $mainBranch?"
958
+ ```
959
+
960
+ After user approval:
961
+
962
+ ```bash
963
+ # Push branch
964
+ git push -u origin "$branchName"
965
+
966
+ # Create PR with phase summary
967
+ gh pr create \
968
+ --base "$mainBranch" \
969
+ --head "$branchName" \
970
+ --title "Phase {N}: {Phase Name}" \
971
+ --body "## Summary
972
+ - {task count} tasks completed
973
+ - {test count} tests passing
974
+ - Review: APPROVED
975
+
976
+ ## Tasks
977
+ {list of completed tasks from PLAN.md}
978
+
979
+ ## Test Results
980
+ {test runner summary}"
981
+ ```
982
+
983
+ **The PR is the deliverable, not the commits.** CI runs on the PR. Merge when green.
984
+
985
+ **If the user has already been working on main** (e.g., first time using branching): push main directly but note that future phases MUST use branches.
986
+
855
987
  ## Framework Defaults
856
988
 
857
989
  ### TLC Default: Mocha Stack
@@ -10,12 +10,76 @@ This command supports multi-model routing via `~/.tlc/config.json`.
10
10
 
11
11
  1. Read routing config:
12
12
  ```bash
13
- node -e "
14
- const { resolveRouting } = require('./server/lib/task-router-config');
15
- const flagModel = (process.argv.find(a => a.startsWith("--model")) || "").replace("--model=", "").replace("--model ", "") || null;
16
- const r = resolveRouting({ command: 'coverage', flagModel, projectDir: process.cwd(), homeDir: process.env.HOME });
17
- console.log(JSON.stringify(r));
18
- " 2>/dev/null
13
+ node -e "const fs = require('fs');
14
+ const path = require('path');
15
+ const os = require('os');
16
+ function readJson(filePath, fileSystem) {
17
+ try {
18
+ return JSON.parse(fileSystem.readFileSync(filePath, 'utf8'));
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function loadPersonalConfig(options) {
24
+ const configPath = path.join(options.homeDir, '.tlc', 'config.json');
25
+ return readJson(configPath, options.fs);
26
+ }
27
+ function loadProjectOverride(options) {
28
+ const configPath = path.join(options.projectDir, '.tlc.json');
29
+ const data = readJson(configPath, options.fs);
30
+ return data && data.task_routing_override ? data.task_routing_override : null;
31
+ }
32
+ function resolveRouting(options) {
33
+ let models = ['claude'];
34
+ let strategy = 'single';
35
+ let source = 'shipped-defaults';
36
+ let providers;
37
+ const personal = loadPersonalConfig({ homeDir: options.homeDir, fs: options.fs });
38
+ if (personal) {
39
+ if (personal.model_providers) {
40
+ providers = personal.model_providers;
41
+ }
42
+ const personalRouting = personal.task_routing && personal.task_routing[options.command];
43
+ if (personalRouting) {
44
+ if (Array.isArray(personalRouting.models)) {
45
+ models = personalRouting.models.slice();
46
+ } else if (typeof personalRouting.model === 'string') {
47
+ models = [personalRouting.model];
48
+ }
49
+ if (personalRouting.strategy) {
50
+ strategy = personalRouting.strategy;
51
+ }
52
+ source = 'personal-config';
53
+ }
54
+ }
55
+ const projectOverride = loadProjectOverride({ projectDir: options.projectDir, fs: options.fs });
56
+ if (projectOverride) {
57
+ const overrideEntry = projectOverride[options.command];
58
+ if (overrideEntry) {
59
+ if (Array.isArray(overrideEntry.models)) {
60
+ models = overrideEntry.models.slice();
61
+ } else if (typeof overrideEntry.model === 'string') {
62
+ models = [overrideEntry.model];
63
+ }
64
+ if (overrideEntry.strategy) {
65
+ strategy = overrideEntry.strategy;
66
+ }
67
+ source = 'project-override';
68
+ }
69
+ }
70
+ if (options.flagModel) {
71
+ models = [options.flagModel];
72
+ strategy = 'single';
73
+ source = 'flag-override';
74
+ }
75
+ const result = { models, strategy, source };
76
+ if (providers) {
77
+ result.providers = providers;
78
+ }
79
+ return result;
80
+ }
81
+ const result = resolveRouting({ command: \"coverage\", flagModel: process.argv[1], projectDir: process.cwd(), homeDir: process.env.HOME || os.homedir(), fs });
82
+ process.stdout.write(JSON.stringify(result));" 2>/dev/null
19
83
  ```
20
84
 
21
85
  2. If `models[0]` is NOT `claude` (i.e., routed to external model):