@lenne.tech/cli 1.27.0 → 1.29.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 (40) hide show
  1. package/build/cli.js +7 -1
  2. package/build/commands/dev/doctor.js +27 -1
  3. package/build/commands/dev/down.js +22 -10
  4. package/build/commands/dev/status.js +4 -3
  5. package/build/commands/dev/test.js +12 -4
  6. package/build/commands/dev/up.js +90 -50
  7. package/build/commands/frontend/angular.js +48 -46
  8. package/build/commands/frontend/convert-mode.js +41 -39
  9. package/build/commands/frontend/nuxt.js +49 -47
  10. package/build/commands/fullstack/add-api.js +34 -32
  11. package/build/commands/fullstack/add-app.js +25 -23
  12. package/build/commands/fullstack/convert-mode.js +85 -65
  13. package/build/commands/fullstack/init.js +12 -0
  14. package/build/commands/fullstack/update.js +24 -0
  15. package/build/commands/server/add-property.js +42 -40
  16. package/build/commands/server/convert-mode.js +41 -39
  17. package/build/commands/server/create.js +65 -63
  18. package/build/commands/server/module.js +56 -54
  19. package/build/commands/server/object.js +42 -40
  20. package/build/commands/server/permissions.js +60 -58
  21. package/build/commands/ticket/list.js +78 -0
  22. package/build/commands/ticket/start.js +141 -0
  23. package/build/commands/ticket/stop.js +166 -0
  24. package/build/commands/ticket/switch.js +70 -0
  25. package/build/commands/ticket/test.js +80 -0
  26. package/build/commands/ticket/ticket.js +36 -0
  27. package/build/commands/tools/crawl.js +92 -90
  28. package/build/extensions/frontend-helper.js +8 -37
  29. package/build/extensions/server.js +8 -38
  30. package/build/lib/command-help.js +161 -0
  31. package/build/lib/dev-identity.js +18 -0
  32. package/build/lib/dev-patches.js +1 -1
  33. package/build/lib/dev-project.js +14 -0
  34. package/build/lib/dev-state.js +96 -0
  35. package/build/lib/dev-test-session.js +55 -35
  36. package/build/lib/dev-ticket.js +343 -0
  37. package/build/lib/vendor-claude-md.js +227 -0
  38. package/docs/lt-dev-ticket-workflow.html +603 -0
  39. package/docs/lt-dev-ticket-workflow.pdf +0 -0
  40. package/package.json +32 -1
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.help = void 0;
12
13
  const path_1 = require("path");
13
14
  const crawler_1 = require("../../lib/crawler");
14
15
  /**
@@ -18,6 +19,96 @@ const crawler_1 = require("../../lib/crawler");
18
19
  * shares the defuddle + Turndown extraction pipeline but runs headless
19
20
  * from Node and follows links / sitemaps automatically.
20
21
  */
22
+ exports.help = {
23
+ aliases: ['cr'],
24
+ description: 'Crawl a website into Markdown files (for Claude Code knowledge bases)',
25
+ name: 'crawl',
26
+ options: [
27
+ {
28
+ description: 'Start URL (absolute http/https URL)',
29
+ flag: '--url',
30
+ required: true,
31
+ type: 'string',
32
+ },
33
+ {
34
+ default: '.',
35
+ description: 'Output directory (created if missing)',
36
+ flag: '--out',
37
+ type: 'string',
38
+ },
39
+ {
40
+ default: 0,
41
+ description: 'Link depth. 0 = only start page; 1 = + direct links; N = up to N hops; "all" (or -1) = follow every same-origin link until --max-pages is reached',
42
+ flag: '--depth',
43
+ type: 'number|all',
44
+ },
45
+ {
46
+ default: true,
47
+ description: 'Download images and inline them with local paths',
48
+ flag: '--images',
49
+ type: 'boolean',
50
+ },
51
+ {
52
+ default: true,
53
+ description: 'Also seed queue from <origin>/sitemap.xml',
54
+ flag: '--sitemap',
55
+ type: 'boolean',
56
+ },
57
+ {
58
+ default: 4,
59
+ description: 'Parallel HTTP requests',
60
+ flag: '--concurrency',
61
+ type: 'number',
62
+ },
63
+ {
64
+ default: 200,
65
+ description: 'Maximum number of pages to crawl (safety cap)',
66
+ flag: '--max-pages',
67
+ type: 'number',
68
+ },
69
+ {
70
+ description: 'CSS selector for the main content container',
71
+ flag: '--selector',
72
+ type: 'string',
73
+ },
74
+ {
75
+ default: 20000,
76
+ description: 'HTTP request timeout in ms',
77
+ flag: '--timeout',
78
+ type: 'number',
79
+ },
80
+ {
81
+ default: false,
82
+ description: 'Shortcut for --depth all (follows every same-origin link until --max-pages)',
83
+ flag: '--all',
84
+ type: 'boolean',
85
+ },
86
+ {
87
+ default: true,
88
+ description: "Render pages through a headless browser before extracting (for SPAs like Vue/Nuxt/React/Angular). Uses playwright-core with system Chrome / Edge, falling back to Playwright's bundled chromium. Disable with --no-render for plain HTTP fetches.",
89
+ flag: '--render',
90
+ type: 'boolean',
91
+ },
92
+ {
93
+ default: false,
94
+ description: 'If --render cannot find any browser, auto-install Playwright chromium (one-time ~170 MB download).',
95
+ flag: '--install-browser',
96
+ type: 'boolean',
97
+ },
98
+ {
99
+ default: true,
100
+ description: 'After a multi-page crawl, remove any .md or image files inside <outDir>/pages and <outDir>/images that were not written by this run. Disable with --no-prune to preserve old files.',
101
+ flag: '--prune',
102
+ type: 'boolean',
103
+ },
104
+ {
105
+ default: false,
106
+ description: 'Skip confirmation prompts',
107
+ flag: '--noConfirm',
108
+ type: 'boolean',
109
+ },
110
+ ],
111
+ };
21
112
  const NewCommand = {
22
113
  alias: ['cr'],
23
114
  description: 'Crawl site to Markdown',
@@ -26,96 +117,7 @@ const NewCommand = {
26
117
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
27
118
  var _a, _b, _c, _d, _e;
28
119
  const { config, filesystem, helper, parameters, print: { error, info, spin, success, warning }, prompt: { confirm }, tools, } = toolbox;
29
- if (tools.helpJson({
30
- aliases: ['cr'],
31
- description: 'Crawl a website into Markdown files (for Claude Code knowledge bases)',
32
- name: 'crawl',
33
- options: [
34
- {
35
- description: 'Start URL (absolute http/https URL)',
36
- flag: '--url',
37
- required: true,
38
- type: 'string',
39
- },
40
- {
41
- default: '.',
42
- description: 'Output directory (created if missing)',
43
- flag: '--out',
44
- type: 'string',
45
- },
46
- {
47
- default: 0,
48
- description: 'Link depth. 0 = only start page; 1 = + direct links; N = up to N hops; "all" (or -1) = follow every same-origin link until --max-pages is reached',
49
- flag: '--depth',
50
- type: 'number|all',
51
- },
52
- {
53
- default: true,
54
- description: 'Download images and inline them with local paths',
55
- flag: '--images',
56
- type: 'boolean',
57
- },
58
- {
59
- default: true,
60
- description: 'Also seed queue from <origin>/sitemap.xml',
61
- flag: '--sitemap',
62
- type: 'boolean',
63
- },
64
- {
65
- default: 4,
66
- description: 'Parallel HTTP requests',
67
- flag: '--concurrency',
68
- type: 'number',
69
- },
70
- {
71
- default: 200,
72
- description: 'Maximum number of pages to crawl (safety cap)',
73
- flag: '--max-pages',
74
- type: 'number',
75
- },
76
- {
77
- description: 'CSS selector for the main content container',
78
- flag: '--selector',
79
- type: 'string',
80
- },
81
- {
82
- default: 20000,
83
- description: 'HTTP request timeout in ms',
84
- flag: '--timeout',
85
- type: 'number',
86
- },
87
- {
88
- default: false,
89
- description: 'Shortcut for --depth all (follows every same-origin link until --max-pages)',
90
- flag: '--all',
91
- type: 'boolean',
92
- },
93
- {
94
- default: true,
95
- description: "Render pages through a headless browser before extracting (for SPAs like Vue/Nuxt/React/Angular). Uses playwright-core with system Chrome / Edge, falling back to Playwright's bundled chromium. Disable with --no-render for plain HTTP fetches.",
96
- flag: '--render',
97
- type: 'boolean',
98
- },
99
- {
100
- default: false,
101
- description: 'If --render cannot find any browser, auto-install Playwright chromium (one-time ~170 MB download).',
102
- flag: '--install-browser',
103
- type: 'boolean',
104
- },
105
- {
106
- default: true,
107
- description: 'After a multi-page crawl, remove any .md or image files inside <outDir>/pages and <outDir>/images that were not written by this run. Disable with --no-prune to preserve old files.',
108
- flag: '--prune',
109
- type: 'boolean',
110
- },
111
- {
112
- default: false,
113
- description: 'Skip confirmation prompts',
114
- flag: '--noConfirm',
115
- type: 'boolean',
116
- },
117
- ],
118
- })) {
120
+ if (tools.helpJson(exports.help)) {
119
121
  return 'crawl';
120
122
  }
121
123
  tools.nonInteractiveHint('lt tools crawl <url> --out <dir> --depth 1 --noConfirm');
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.FrontendHelper = void 0;
13
13
  const markdown_table_1 = require("../lib/markdown-table");
14
+ const vendor_claude_md_1 = require("../lib/vendor-claude-md");
14
15
  /**
15
16
  * Frontend helper functions for project scaffolding
16
17
  * Provides reusable methods for setting up Nuxt and Angular frontends
@@ -390,17 +391,10 @@ class FrontendHelper {
390
391
  // ── 6. Clean CLAUDE.md vendor marker ─────────────────────────────────
391
392
  const claudeMdPath = path.join(dest, 'CLAUDE.md');
392
393
  if (filesystem.exists(claudeMdPath)) {
393
- let content = filesystem.read(claudeMdPath) || '';
394
- const markerStart = '<!-- lt-vendor-marker-frontend -->';
395
- const markerEnd = '---';
396
- if (content.includes(markerStart)) {
397
- const startIdx = content.indexOf(markerStart);
398
- const endIdx = content.indexOf(markerEnd, startIdx);
399
- if (endIdx > startIdx) {
400
- content = content.slice(0, startIdx) + content.slice(endIdx + markerEnd.length);
401
- content = content.replace(/^\n+/, '');
402
- filesystem.write(claudeMdPath, content);
403
- }
394
+ const content = filesystem.read(claudeMdPath) || '';
395
+ const cleaned = (0, vendor_claude_md_1.removeVendorBlock)(content, vendor_claude_md_1.FRONTEND_VENDOR_MARKER);
396
+ if (cleaned !== content) {
397
+ filesystem.write(claudeMdPath, cleaned);
404
398
  }
405
399
  }
406
400
  // ── Post-conversion verification ─────────────────────────────────────
@@ -615,32 +609,9 @@ class FrontendHelper {
615
609
  const claudeMdPath = path.join(dest, 'CLAUDE.md');
616
610
  if (filesystem.exists(claudeMdPath)) {
617
611
  const existing = filesystem.read(claudeMdPath) || '';
618
- const marker = '<!-- lt-vendor-marker-frontend -->';
619
- if (!existing.includes(marker)) {
620
- const vendorBlock = [
621
- marker,
622
- '',
623
- '# Vendor-Mode Notice (Frontend)',
624
- '',
625
- 'This frontend project runs in **vendor mode**: the `@lenne.tech/nuxt-extensions`',
626
- 'module has been copied directly into `app/core/` as first-class',
627
- 'project code. There is **no** `@lenne.tech/nuxt-extensions` npm dependency.',
628
- '',
629
- '- **Read framework code from `app/core/**`** — not from `node_modules/`.',
630
- "- **nuxt.config.ts** references `'./app/core/module'` instead of",
631
- " `'@lenne.tech/nuxt-extensions'`.",
632
- '- **Baseline + patch log** live in `app/core/VENDOR.md`. Log any',
633
- ' substantial local change there so the `nuxt-extensions-core-updater`',
634
- ' agent can classify it at sync time.',
635
- '- **Update flow:** run `/lt-dev:frontend:update-nuxt-extensions-core`.',
636
- '- **Contribute back:** run `/lt-dev:frontend:contribute-nuxt-extensions-core`.',
637
- '- **Freshness check:** `pnpm run check:vendor-freshness` warns when',
638
- ' upstream has a newer release than the baseline.',
639
- '',
640
- '---',
641
- '',
642
- ].join('\n');
643
- filesystem.write(claudeMdPath, vendorBlock + existing);
612
+ const patched = (0, vendor_claude_md_1.insertVendorBlockIfMissing)(existing, vendor_claude_md_1.FRONTEND_VENDOR_MARKER, (0, vendor_claude_md_1.buildFrontendVendorBlock)());
613
+ if (patched !== existing) {
614
+ filesystem.write(claudeMdPath, patched);
644
615
  }
645
616
  }
646
617
  // Merge upstream CLAUDE.md sections
@@ -47,6 +47,7 @@ const crypto = __importStar(require("crypto"));
47
47
  const path_1 = require("path");
48
48
  const ts = __importStar(require("typescript"));
49
49
  const markdown_table_1 = require("../lib/markdown-table");
50
+ const vendor_claude_md_1 = require("../lib/vendor-claude-md");
50
51
  /**
51
52
  * Server helper functions
52
53
  */
@@ -1585,36 +1586,9 @@ class Server {
1585
1586
  const apiClaudeMdPath = `${dest}/CLAUDE.md`;
1586
1587
  if (filesystem.exists(apiClaudeMdPath)) {
1587
1588
  const existing = filesystem.read(apiClaudeMdPath) || '';
1588
- const marker = '<!-- lt-vendor-marker -->';
1589
- if (!existing.includes(marker)) {
1590
- const vendorBlock = [
1591
- marker,
1592
- '',
1593
- '# Vendor-Mode Notice',
1594
- '',
1595
- 'This api project runs in **vendor mode**: the `@lenne.tech/nest-server`',
1596
- 'core/ tree has been copied directly into `src/core/` as first-class',
1597
- 'project code. There is **no** `@lenne.tech/nest-server` npm dependency.',
1598
- '',
1599
- '- **Read framework code from `src/core/**`** — not from `node_modules/`.',
1600
- '- **Generated imports use relative paths** to `src/core`, e.g.',
1601
- " `import { CrudService } from '../../../core';`",
1602
- ' The exact depth depends on the file location. `lt server module`',
1603
- ' computes it automatically.',
1604
- '- **Baseline + patch log** live in `src/core/VENDOR.md`. Log any',
1605
- ' substantial local change there so the `nest-server-core-updater`',
1606
- ' agent can classify it at sync time.',
1607
- '- **Update flow:** run `/lt-dev:backend:update-nest-server-core` (the',
1608
- ' agent clones upstream, computes a delta, and presents a review).',
1609
- '- **Contribute back:** run `/lt-dev:backend:contribute-nest-server-core`',
1610
- ' to propose local fixes as upstream PRs.',
1611
- '- **Freshness check:** `pnpm run check:vendor-freshness` warns (non-',
1612
- ' blockingly) when upstream has a newer release than the baseline.',
1613
- '',
1614
- '---',
1615
- '',
1616
- ].join('\n');
1617
- filesystem.write(apiClaudeMdPath, vendorBlock + existing);
1589
+ const patched = (0, vendor_claude_md_1.insertVendorBlockIfMissing)(existing, vendor_claude_md_1.BACKEND_VENDOR_MARKER, (0, vendor_claude_md_1.buildBackendVendorBlock)());
1590
+ if (patched !== existing) {
1591
+ filesystem.write(apiClaudeMdPath, patched);
1618
1592
  }
1619
1593
  }
1620
1594
  // ── 9c. Merge nest-server CLAUDE.md sections into project CLAUDE.md ──
@@ -2307,14 +2281,10 @@ class Server {
2307
2281
  // ── 6. Clean CLAUDE.md vendor marker ────────────────────────────────
2308
2282
  const claudeMdPath = `${dest}/CLAUDE.md`;
2309
2283
  if (filesystem.exists(claudeMdPath)) {
2310
- let content = filesystem.read(claudeMdPath) || '';
2311
- const marker = '<!-- lt-vendor-marker -->';
2312
- if (content.includes(marker)) {
2313
- // Remove everything from marker to the first `---` separator (end of vendor block)
2314
- content = content.replace(new RegExp(`${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?---\\s*\\n?`, ''), '');
2315
- // Remove leading whitespace/newlines
2316
- content = content.replace(/^\n+/, '');
2317
- filesystem.write(claudeMdPath, content);
2284
+ const content = filesystem.read(claudeMdPath) || '';
2285
+ const cleaned = (0, vendor_claude_md_1.removeVendorBlock)(content, vendor_claude_md_1.BACKEND_VENDOR_MARKER);
2286
+ if (cleaned !== content) {
2287
+ filesystem.write(claudeMdPath, cleaned);
2318
2288
  }
2319
2289
  }
2320
2290
  // ── 7. Restore tsconfig excludes ────────────────────────────────────
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * Generic `--help` / `-h` support for **every** lt command.
4
+ *
5
+ * Problem this solves: gluegun's built-in `.help()` only handles the top-level
6
+ * `lt --help` (the command list). For a subcommand, `lt fullstack convert-mode
7
+ * --help` simply *runs* the command — so a user who only wanted to read about a
8
+ * command accidentally triggers it. {@link installHelpInterceptor} wraps every
9
+ * loaded command so that, when help is requested, the command prints rich help
10
+ * and returns **without executing**.
11
+ *
12
+ * Two levels of detail:
13
+ * 1. Generic (always available) — usage, aliases and description from the
14
+ * command metadata gluegun already has.
15
+ * 2. Rich (opt-in) — a command module may `export const help: CommandHelp`
16
+ * describing options, features, examples and configuration. The interceptor
17
+ * loads it from `command.file` without running the command.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.installHelpInterceptor = installHelpInterceptor;
21
+ exports.isHelpRequested = isHelpRequested;
22
+ exports.loadCommandHelp = loadCommandHelp;
23
+ exports.renderCommandHelp = renderCommandHelp;
24
+ /**
25
+ * Wrap every command's `run` so that `--help` / `-h` prints help and returns
26
+ * without executing. Call once after `build().create()`, before `cli.run()`.
27
+ *
28
+ * Idempotent per command (guarded by `__helpWrapped`), so a command is never
29
+ * double-wrapped if this runs more than once in the same process (e.g. tests).
30
+ */
31
+ function installHelpInterceptor(commands, defaultCommand) {
32
+ for (const command of commands || []) {
33
+ if (typeof command.run !== 'function' || command.__helpWrapped) {
34
+ continue;
35
+ }
36
+ // Leave the top-level/default command and gluegun's preloaded builtins
37
+ // (`help`, `version` — they have `file === null`) to gluegun's own
38
+ // `.help()`, so that `lt --help` keeps printing the brand banner + full
39
+ // command list. Real file-backed subcommands (incl. `lt config help`) are
40
+ // still wrapped.
41
+ if (command === defaultCommand || !command.file || !(command.commandPath && command.commandPath.length)) {
42
+ continue;
43
+ }
44
+ const originalRun = command.run;
45
+ command.run = (toolbox) => {
46
+ if ((toolbox === null || toolbox === void 0 ? void 0 : toolbox.print) && isHelpRequested(toolbox.parameters)) {
47
+ renderCommandHelp(toolbox.print, command, loadCommandHelp(command.file));
48
+ return undefined;
49
+ }
50
+ return originalRun(toolbox);
51
+ };
52
+ command.__helpWrapped = true;
53
+ }
54
+ }
55
+ /**
56
+ * True when the invocation asks for help (`--help` or `-h`) — but NOT for
57
+ * `--help-json` (handled separately by `tools.helpJson`).
58
+ */
59
+ function isHelpRequested(parameters) {
60
+ const options = (parameters === null || parameters === void 0 ? void 0 : parameters.options) || {};
61
+ if (options['help-json'] === true || options.helpJson === true) {
62
+ return false;
63
+ }
64
+ return options.help === true || options.h === true;
65
+ }
66
+ /** Best-effort load of a command's rich `help` export from its file. */
67
+ function loadCommandHelp(file) {
68
+ if (!file) {
69
+ return undefined;
70
+ }
71
+ try {
72
+ const mod = require(file);
73
+ const help = (mod && (mod.help || (mod.default && mod.default.help)));
74
+ return help && typeof help === 'object' ? help : undefined;
75
+ }
76
+ catch (_a) {
77
+ return undefined;
78
+ }
79
+ }
80
+ /**
81
+ * Render human-readable help for a command to `print`. Uses the rich `help`
82
+ * definition when available, otherwise a useful generic fallback. Never runs
83
+ * the command.
84
+ */
85
+ function renderCommandHelp(print, command, help) {
86
+ var _a, _b, _c;
87
+ const { bold, cyan, dim, yellow } = print.colors;
88
+ const usage = usagePath(command);
89
+ const description = (help === null || help === void 0 ? void 0 : help.description) || command.description || '(no description)';
90
+ print.info('');
91
+ print.info(`${bold(usage)} — ${description}`);
92
+ const aliases = aliasList(command, help);
93
+ if (aliases.length) {
94
+ print.info(dim(`Aliases: ${aliases.join(', ')}`));
95
+ }
96
+ if ((_a = help === null || help === void 0 ? void 0 : help.features) === null || _a === void 0 ? void 0 : _a.length) {
97
+ print.info('');
98
+ print.info(bold('What it does:'));
99
+ for (const feature of help.features) {
100
+ print.info(` • ${feature}`);
101
+ }
102
+ }
103
+ print.info('');
104
+ print.info(bold('Usage:'));
105
+ print.info(` ${usage} [options]`);
106
+ if ((_b = help === null || help === void 0 ? void 0 : help.examples) === null || _b === void 0 ? void 0 : _b.length) {
107
+ print.info('');
108
+ print.info(bold('Examples:'));
109
+ for (const example of help.examples) {
110
+ print.info(` ${cyan(example.startsWith('lt ') ? example : `lt ${example}`)}`);
111
+ }
112
+ }
113
+ print.info('');
114
+ print.info(bold('Options:'));
115
+ const options = [...((help === null || help === void 0 ? void 0 : help.options) || [])];
116
+ for (const option of options) {
117
+ const meta = [];
118
+ if (option.required) {
119
+ meta.push('required');
120
+ }
121
+ if ((_c = option.values) === null || _c === void 0 ? void 0 : _c.length) {
122
+ meta.push(option.values.join('|'));
123
+ }
124
+ else if (option.type) {
125
+ meta.push(option.type);
126
+ }
127
+ if (option.default !== undefined) {
128
+ meta.push(`default: ${String(option.default)}`);
129
+ }
130
+ const metaText = meta.length ? dim(` (${meta.join(', ')})`) : '';
131
+ print.info(` ${option.flag.padEnd(28)} ${option.description}${metaText}`);
132
+ }
133
+ // Always-present global flags
134
+ print.info(` ${'--help, -h'.padEnd(28)} Show this help and exit (does not run the command)`);
135
+ print.info(` ${'--help-json'.padEnd(28)} Machine-readable help as JSON (where provided)`);
136
+ if (!options.some((o) => o.flag === '--noConfirm')) {
137
+ print.info(` ${'--noConfirm'.padEnd(28)} Skip confirmation prompts (where supported)`);
138
+ }
139
+ if (help === null || help === void 0 ? void 0 : help.configuration) {
140
+ print.info('');
141
+ print.info(bold('Configuration (lt.config.json / lt.config.yaml):'));
142
+ for (const line of help.configuration.split('\n')) {
143
+ print.info(` ${line}`);
144
+ }
145
+ }
146
+ if (!help) {
147
+ print.info('');
148
+ print.info(dim('Tip: see docs/commands.md and docs/lt.config.md for full reference.'));
149
+ }
150
+ print.info('');
151
+ print.info(yellow('This is help output — the command was NOT executed.'));
152
+ print.info('');
153
+ }
154
+ function aliasList(command, help) {
155
+ return (help === null || help === void 0 ? void 0 : help.aliases) || command.aliases || command.alias || [];
156
+ }
157
+ /** Resolve the user-facing invocation, e.g. `lt fullstack convert-mode`. */
158
+ function usagePath(command) {
159
+ const path = command.commandPath && command.commandPath.length ? command.commandPath : [command.name || ''];
160
+ return `lt ${path.filter(Boolean).join(' ')}`.trim();
161
+ }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildIdentity = buildIdentity;
4
4
  exports.buildTestIdentity = buildTestIdentity;
5
+ exports.buildTicketIdentity = buildTicketIdentity;
5
6
  exports.projectSlug = projectSlug;
6
7
  exports.slugify = slugify;
7
8
  /**
@@ -85,6 +86,23 @@ function buildTestIdentity(base, suffix = '-test') {
85
86
  }
86
87
  return { root: base.root, slug, subdomains };
87
88
  }
89
+ /**
90
+ * Derive a per-TICKET identity from a base identity (used by `lt ticket` /
91
+ * `lt dev up --ticket`). Suffixes the slug + every subdomain hostname with the
92
+ * ticket id, so each ticket worktree runs on its OWN URLs / ports / Caddy block
93
+ * / DB — fully parallel to and isolated from every other ticket and the base
94
+ * dev session.
95
+ *
96
+ * svl.localhost → svl-2200.localhost
97
+ * api.svl.localhost → api.svl-2200.localhost
98
+ *
99
+ * Mechanically identical to {@link buildTestIdentity} (a named wrapper for
100
+ * readability + intent at the call sites). `id` is already a clean slug (see
101
+ * `deriveTicketId` in dev-ticket.ts).
102
+ */
103
+ function buildTicketIdentity(base, id) {
104
+ return buildTestIdentity(base, `-${id}`);
105
+ }
88
106
  /**
89
107
  * Read the bare project name from package.json (scope stripped).
90
108
  * Falls back to directory basename if no package.json or no `name`.
@@ -264,7 +264,7 @@ function patchPlaywrightConfig(file) {
264
264
  if (!/const SHARDED\b/.test(after) && /export default defineConfig/.test(after)) {
265
265
  const shardConst = '// `lt dev test --shard N` saturates the CPU (N built SSR servers + N Chromium),\n' +
266
266
  '// slowing every navigation. Relax timeouts ONLY under that load — the CLI sets\n' +
267
- "// LT_DEV_TEST_SHARDS — so serial + CI keep their tight, fast-failing defaults.\n" +
267
+ '// LT_DEV_TEST_SHARDS — so serial + CI keep their tight, fast-failing defaults.\n' +
268
268
  "const SHARDED = Number(process.env.LT_DEV_TEST_SHARDS || '0') > 1;\n\n";
269
269
  after = after.replace(/(export default defineConfig)/, `${shardConst}$1`);
270
270
  count++;
@@ -4,6 +4,7 @@ exports.apiNeedsPortPatch = apiNeedsPortPatch;
4
4
  exports.appNeedsPortPatch = appNeedsPortPatch;
5
5
  exports.deriveDbName = deriveDbName;
6
6
  exports.deriveTestDbName = deriveTestDbName;
7
+ exports.deriveTicketDbName = deriveTicketDbName;
7
8
  exports.resolveLayout = resolveLayout;
8
9
  const fs_1 = require("fs");
9
10
  const path_1 = require("path");
@@ -68,6 +69,19 @@ function deriveTestDbName(devDbName) {
68
69
  const base = devDbName.replace(/-(local|dev)$/i, '');
69
70
  return `${base}-test`;
70
71
  }
72
+ /**
73
+ * Derive the per-TICKET database name from the project's dev DB name, so each
74
+ * ticket worktree reads/writes its OWN database and tickets never collide.
75
+ *
76
+ * svl-sports-system-local + "2200" → svl-sports-system-2200
77
+ *
78
+ * The ticket's isolated `lt dev test` stack then derives its test DB from this
79
+ * via {@link deriveTestDbName} → `svl-sports-system-2200-test`.
80
+ */
81
+ function deriveTicketDbName(devDbName, ticketId) {
82
+ const base = devDbName.replace(/-(local|dev)$/i, '');
83
+ return `${base}-${ticketId}`;
84
+ }
71
85
  /**
72
86
  * Resolve layout starting from `cwd`. Walks up to find a workspace if
73
87
  * cwd is inside `projects/api/` or `projects/app/`.