@chemx/create-chemx 26.9.25-943 → 26.9.28-276

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 (54) hide show
  1. package/cli/audit/ast-visitors.js +7 -1
  2. package/cli/audit/extended-text-patterns.js +6 -3
  3. package/cli/audit/prompts.js +1 -1
  4. package/cli/audit/rules-helpers.js +7 -1
  5. package/cli/audit/rules.js +1 -1
  6. package/cli/commands/cmd-router.js +20 -2
  7. package/cli/commands-schema.js +65 -6
  8. package/cli/config/loader.js +1 -0
  9. package/cli/config/profiles.js +3 -0
  10. package/cli/friction-fixes.spec.js +96 -0
  11. package/cli/generator-help.js +26 -6
  12. package/cli/generator-jig-cli.js +67 -0
  13. package/cli/generator-jig.js +479 -0
  14. package/cli/generator-jig.spec.js +148 -0
  15. package/cli/generator.js +15 -2
  16. package/cli/index.js +4 -1
  17. package/cli/installer-templates.js +8 -3
  18. package/cli/mcp/manifests.js +29 -6
  19. package/cli/mcp/tools-generate.js +22 -2
  20. package/cli/mcp/tools-patch.js +4 -3
  21. package/cli/mcp/tools-read.js +53 -9
  22. package/cli/mcp/tools-team-locks.js +10 -3
  23. package/cli/mcp/tools-team-tasks.js +5 -2
  24. package/cli/mcp/tools-verify.js +2 -0
  25. package/cli/mcp/tools-write.js +2 -1
  26. package/cli/mcp/tools.js +96 -4
  27. package/cli/patcher.js +3 -2
  28. package/cli/path-scope.js +63 -0
  29. package/cli/path-traversal.spec.js +242 -0
  30. package/cli/project-detector.js +105 -2
  31. package/cli/reader-cli.js +146 -0
  32. package/cli/reader-logic.js +392 -0
  33. package/cli/reader-logic.spec.js +160 -0
  34. package/cli/reader.js +81 -172
  35. package/cli/search-commands-graph.js +125 -1
  36. package/cli/search-commands.js +7 -1
  37. package/cli/search-db.js +2 -0
  38. package/cli/search-queries-graph.js +294 -0
  39. package/cli/search-queries.js +6 -1
  40. package/cli/search.js +28 -30
  41. package/cli/team/team-commands-lock.js +17 -5
  42. package/cli/team/team-commands.js +2 -2
  43. package/cli/team/team-db-locks.js +50 -15
  44. package/cli/team/team-memory.js +30 -7
  45. package/cli/team/team-memory.spec.js +24 -0
  46. package/cli/tesseract.spec.js +2 -2
  47. package/cli/trace.spec.js +92 -0
  48. package/cli/verify-helpers.js +46 -3
  49. package/cli/verify-lint.js +224 -0
  50. package/cli/verify-lint.spec.js +44 -0
  51. package/cli/verify.js +8 -1
  52. package/cli/verify.spec.js +17 -0
  53. package/package.json +1 -1
  54. package/scripts/pre-commit.sh +8 -3
@@ -23,12 +23,18 @@ export const createAstVisitors = ({ relativePath, violations, hookRegistry, conf
23
23
  const isCustomHook = isCustomHookFunction(astPath);
24
24
 
25
25
  // Pillar 3: Hook Saturation
26
+ const CONTROLLER_WRAPPER_EXCLUSIONS = new Set([
27
+ 'useSharedStatusController',
28
+ 'useSharedController',
29
+ 'useController'
30
+ ]);
26
31
  let hookCount = 0;
27
32
  astPath.traverse({
28
33
  CallExpression(callPath) {
29
34
  const callee = callPath.node.callee;
30
35
  if (t.isIdentifier(callee) && /^use[A-Z0-9]/.test(callee.name)) {
31
- if (callPath.getFunctionParent() === astPath) {
36
+ const isExcluded = CONTROLLER_WRAPPER_EXCLUSIONS.has(callee.name);
37
+ if (!isExcluded && callPath.getFunctionParent() === astPath) {
32
38
  hookCount += 1;
33
39
  }
34
40
  }
@@ -32,7 +32,10 @@ const JAVASCRIPT_URL_TEMPLATE_PATTERN = /\b(?:href|src|action|formaction)\s*=\s*
32
32
  /**
33
33
  * Pillar 10: Check for co-located test files for molecule capsules.
34
34
  */
35
- export const checkMoleculeCoLocatedTest = (filePath, relativePath, violations) => {
35
+ export const checkMoleculeCoLocatedTest = (filePath, relativePath, violations, config = {}) => {
36
+ const enforceColocated = config.enforceColocatedTests ?? (config.profile === 'atomic-strict');
37
+ if (!enforceColocated) return;
38
+
36
39
  const baseName = path.basename(filePath);
37
40
  const ext = path.extname(filePath);
38
41
 
@@ -194,8 +197,8 @@ export const checkControllerViewContract = (filePath, relativePath, content, vio
194
197
  }
195
198
  };
196
199
 
197
- export const checkExtendedTextPatterns = (content, lines, relativePath, filePath, violations) => {
198
- checkMoleculeCoLocatedTest(filePath, relativePath, violations);
200
+ export const checkExtendedTextPatterns = (content, lines, relativePath, filePath, violations, config = {}) => {
201
+ checkMoleculeCoLocatedTest(filePath, relativePath, violations, config);
199
202
  checkControllerViewContract(filePath, relativePath, content, violations);
200
203
 
201
204
  const isTestFile = /\.(test|spec)\.[jt]sx?$/.test(filePath)
@@ -66,7 +66,7 @@ export const formatGroupedPromptViolations = (violations = [], options = {}) =>
66
66
  export const buildAgentCommandsSection = () => [
67
67
  '### AI AGENT DISCOVERY & REFACTORING COMMANDS:',
68
68
  '- Claim Work: Run `chemx team task list --status=queued` and claim via `chemx team task claim <id> --as=@coder`.',
69
- '- Inspect Target: Run `chemx read <target_path> --outline` to inspect AST signatures without token bloat.',
69
+ '- Inspect Target: Run `chemx read <target_path> --outline` for AST signatures or `--outline --enrich` for signatures + logic flow without token bloat.',
70
70
  '- Verify & Auto-Resolve: Run `chemx verify`. Clean files auto-reconcile tasks to done in SQLite.',
71
71
  '- MCP Invocations: If invoking via MCP, pass command string directly via `chemx({ command: "..." })`.'
72
72
  ].join('\n');
@@ -17,7 +17,13 @@ export const countLogicalOperators = (node) => {
17
17
  export const extractParseableCode = (content, ext) => {
18
18
  if (ext === '.vue') {
19
19
  const scriptMatch = content.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
20
- return scriptMatch ? scriptMatch[1] : '';
20
+ if (!scriptMatch) return '';
21
+ const scriptStartIndex = scriptMatch.index || 0;
22
+ const preScript = content.slice(0, scriptStartIndex);
23
+ const openTag = scriptMatch[0].match(/<script\b[^>]*>/i)?.[0] || '';
24
+ const preContent = preScript + openTag;
25
+ const leadingNewlines = preContent.split('\n').length - 1;
26
+ return '\n'.repeat(leadingNewlines) + scriptMatch[1];
21
27
  }
22
28
  return content;
23
29
  };
@@ -107,7 +107,7 @@ export const auditCode = (content, filePath, relativePath, options = {}) => {
107
107
  checkSlopTextPatterns(content, lines, relativePath, violations);
108
108
 
109
109
  // Pillars 8-11: Extended Fast Checks (A11y, Security secrets, Fake tests, Co-located specs)
110
- checkExtendedTextPatterns(content, lines, relativePath, filePath, violations);
110
+ checkExtendedTextPatterns(content, lines, relativePath, filePath, violations, config);
111
111
 
112
112
  const lang = getLanguageForFile(filePath);
113
113
  if (lang?.id === 'csharp') {
@@ -75,6 +75,16 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
75
75
  await runSearch(rawArgs.slice(1), true);
76
76
  break;
77
77
  }
78
+ case 'trace': {
79
+ const { runTraceCli } = await import('../search-commands-graph.js');
80
+ await runTraceCli(rawArgs.slice(1), true);
81
+ break;
82
+ }
83
+ case 'backtrace': {
84
+ const { runBacktraceCli } = await import('../search-commands-graph.js');
85
+ await runBacktraceCli(rawArgs.slice(1), true);
86
+ break;
87
+ }
78
88
  case 'build':
79
89
  case 'run':
80
90
  case 'wrap': {
@@ -166,9 +176,10 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
166
176
  case 'g':
167
177
  case 'gen':
168
178
  case 'generate':
169
- case 'capsule': {
179
+ case 'capsule':
180
+ case 'jig': {
170
181
  const { runGenerateWizard } = await import('../scaffold.js');
171
- await runGenerateWizard(rawArgs.slice(1));
182
+ await runGenerateWizard(rawArgs);
172
183
  break;
173
184
  }
174
185
  case 'badge':
@@ -201,6 +212,13 @@ export const dispatchCommand = async (firstArg, rawArgs, runAudit, getPackageVer
201
212
  await runTypecheckAudit(rawArgs.slice(1), true);
202
213
  break;
203
214
  }
215
+ case 'lint':
216
+ case 'check:lint':
217
+ case 'eslint': {
218
+ const { runLintAudit } = await import('../verify-lint.js');
219
+ await runLintAudit(rawArgs.slice(1), true);
220
+ break;
221
+ }
204
222
  case 'test':
205
223
  case 'tests':
206
224
  case 'check:test': {
@@ -16,7 +16,9 @@ export const COMMANDS_SCHEMA = [
16
16
  { flag: '-i, --inspect', desc: 'Inspect props and hooks without full source' },
17
17
  { flag: '--tier=<tier>', desc: 'Filter by tier: atom, molecule, organism, hook, view' },
18
18
  { flag: '--blast-radius', desc: 'Map direct consumers, transitive dependents & impacted tiers (aliases: --blast, --impact)' },
19
- { flag: '--max-depth=<N>', desc: 'Max depth for blast radius traversal (default: 5)' },
19
+ { flag: '--trace', desc: 'Forward call trace: downstream functions invoked by target' },
20
+ { flag: '--backtrace', desc: 'Reverse backtrace: upstream callers and causal paths leading to target' },
21
+ { flag: '--max-depth=<N>', desc: 'Max depth for blast radius / trace traversal (default: 5)' },
20
22
  { flag: '--semantic', desc: 'Concept search via vector cosine similarity' },
21
23
  { flag: '--hybrid', desc: 'Blended BM25 keyword + Vector RRF ranking' },
22
24
  { flag: '--hazards', desc: 'Query architectural rule violations directly (flags: --rule=<id>, --critical)' },
@@ -26,6 +28,8 @@ export const COMMANDS_SCHEMA = [
26
28
  examples: [
27
29
  'npx chemx search "badge"',
28
30
  'pnpm q a-button --blast-radius --json',
31
+ 'pnpm q useUserCardController --trace',
32
+ 'pnpm q handleAction --backtrace',
29
33
  'pnpm q "button click handler" --semantic',
30
34
  'pnpm q "useAttentionCardController" --hybrid --json',
31
35
  'pnpm q "useTheme" --inspect',
@@ -34,13 +38,18 @@ export const COMMANDS_SCHEMA = [
34
38
  },
35
39
  {
36
40
  name: 'read',
37
- aliases: ['view'],
41
+ aliases: ['view', 'r'],
38
42
  usage: 'npx chemx read <file> [options]',
39
- summary: 'Token-minified file reader with AST outline extraction.',
40
- description: 'Extracts structural AST outlines, stripped comments, line ranges, or targeted symbol declarations to minimize LLM context token consumption.',
43
+ summary: 'Token-minified file reader with AST outline and logic extraction.',
44
+ description: 'Extracts structural AST outlines, logic skeletons, stripped comments, line ranges, or targeted symbol declarations to minimize LLM context token consumption.',
41
45
  flags: [
42
- { flag: '--outline', desc: 'Signatures only (80%+ token savings)' },
43
- { flag: '--symbol=<name>', desc: 'Target a specific symbol definition' },
46
+ { flag: '--outline, -o', desc: 'Signatures only (80%+ token savings)' },
47
+ { flag: '--logic, -l', desc: 'AST logic skeleton: control flow, guards, and mutations without decorative syntax' },
48
+ { flag: '--template, -t', desc: 'Extract template markup only (Vue/Svelte/JSX)' },
49
+ { flag: '--enrich', desc: 'Append compacted logic skeleton after outline (free-lunch overlay). Compose with --outline for signatures + logic in one call.' },
50
+ { flag: '--trace=<name>', desc: 'Append forward call trace card inline (requires --enrich)' },
51
+ { flag: '--backtrace=<name>', desc: 'Append reverse caller chain card inline (requires --enrich)' },
52
+ { flag: '--symbol=<name>, -s', desc: 'Target a specific symbol definition' },
44
53
  { flag: '--connections', desc: 'Include caller graph and dependent references alongside symbol' },
45
54
  { flag: '--strip-comments', desc: 'Remove all code comments' },
46
55
  { flag: '--compact', desc: 'Remove blank lines and indentation' },
@@ -50,10 +59,42 @@ export const COMMANDS_SCHEMA = [
50
59
  ],
51
60
  examples: [
52
61
  'npx chemx read src/store.ts --outline',
62
+ 'npx chemx read src/controller.ts --outline --enrich',
63
+ 'npx chemx read src/card.vue --outline --enrich --trace=handleSubmit',
64
+ 'npx chemx read src/controller.ts --logic',
65
+ 'npx chemx read src/card.vue --template',
53
66
  'npx chemx read api.ts --symbol=login --connections',
54
67
  'npx chemx read src/ui/atoms/a-button/types.d.ts --symbol=ButtonVariant'
55
68
  ]
56
69
  },
70
+ {
71
+ name: 'trace',
72
+ usage: 'npx chemx trace <symbol> [options]',
73
+ summary: 'Forward call trace: inspects downstream function invocations.',
74
+ description: 'Maps the execution tree of functions, services, and external APIs called by the target symbol.',
75
+ flags: [
76
+ { flag: '--max-depth=<N>', desc: 'Max depth for call trace traversal (default: 3)' },
77
+ { flag: '--json', desc: 'Output call tree as minified JSON' }
78
+ ],
79
+ examples: [
80
+ 'npx chemx trace useCartController',
81
+ 'npx chemx trace handleCheckout --json'
82
+ ]
83
+ },
84
+ {
85
+ name: 'backtrace',
86
+ usage: 'npx chemx backtrace <symbol> [options]',
87
+ summary: 'Reverse call backtrace: maps upstream caller chains leading to target.',
88
+ description: 'Traces the causal path of how user interactions, components, and views trigger the target symbol.',
89
+ flags: [
90
+ { flag: '--max-depth=<N>', desc: 'Max depth for backtrace traversal (default: 5)' },
91
+ { flag: '--json', desc: 'Output causal path as minified JSON' }
92
+ ],
93
+ examples: [
94
+ 'npx chemx backtrace handleCheckout',
95
+ 'npx chemx backtrace postOrder --json'
96
+ ]
97
+ },
57
98
  {
58
99
  name: 'patch',
59
100
  aliases: ['edit'],
@@ -178,6 +219,24 @@ export const COMMANDS_SCHEMA = [
178
219
  'pnpm chemx verify --json'
179
220
  ]
180
221
  },
222
+ {
223
+ name: 'lint',
224
+ aliases: ['check:lint', 'eslint'],
225
+ usage: 'npx chemx lint [path] [options]',
226
+ summary: 'Token-conserving linter runner and tree-formatter with automatic fix.',
227
+ description: 'Executes ESLint with token-efficient diagnostics, target scoping, and automatic --fix support.',
228
+ flags: [
229
+ { flag: '--fix', desc: 'Automatically fix fixable lint and tree-formatting errors' },
230
+ { flag: '--json', desc: 'Output structured diagnostics as JSON for AI agents' },
231
+ { flag: '--raw', desc: 'Do not capture or format output' }
232
+ ],
233
+ examples: [
234
+ 'npx chemx lint',
235
+ 'npx chemx lint --fix',
236
+ 'npx chemx lint apps/youmeos/components/blueprints/desktop/u-desktop.vue --fix',
237
+ 'pnpm chemx lint --json'
238
+ ]
239
+ },
181
240
  {
182
241
  name: 'pillars',
183
242
  aliases: ['config:pillars', 'rules'],
@@ -25,6 +25,7 @@ const normalizeRuleKeys = (rules = {}) => {
25
25
  'max-prop-count': 'maxPropCount',
26
26
  'prefer-design-tokens': 'preferDesignTokens',
27
27
  'enforce-file-length': 'enforceFileLength',
28
+ 'enforce-colocated-tests': 'enforceColocatedTests',
28
29
  'rule-of-three-abstractions': 'ruleOfThreeAbstractions',
29
30
  'ai-slop-detection': 'aiSlopDetection',
30
31
  'max-line-count-warning': 'maxLineCountWarning'
@@ -14,6 +14,7 @@ export const PROFILES = {
14
14
  maxPropCount: 7,
15
15
  preferDesignTokens: 'warning',
16
16
  enforceFileLength: false,
17
+ enforceColocatedTests: false,
17
18
  ruleOfThreeAbstractions: true,
18
19
  aiSlopDetection: 'critical',
19
20
  maxLineCountWarning: 250
@@ -28,6 +29,7 @@ export const PROFILES = {
28
29
  maxPropCount: 5,
29
30
  preferDesignTokens: 'error',
30
31
  enforceFileLength: true,
32
+ enforceColocatedTests: true,
31
33
  ruleOfThreeAbstractions: false,
32
34
  aiSlopDetection: 'critical',
33
35
  maxLineCountWarning: 100
@@ -42,6 +44,7 @@ export const PROFILES = {
42
44
  maxPropCount: 12,
43
45
  preferDesignTokens: 'off',
44
46
  enforceFileLength: false,
47
+ enforceColocatedTests: false,
45
48
  ruleOfThreeAbstractions: true,
46
49
  aiSlopDetection: 'medium',
47
50
  maxLineCountWarning: 500
@@ -0,0 +1,96 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { handleChemx } from './mcp/tools.js';
6
+ import { auditCode } from './audit/rules.js';
7
+
8
+ test('Friction 1: team_task add and team task add persist task rows', async () => {
9
+ const resCmd = await handleChemx({ command: 'team task add CLI Task Test' });
10
+ assert.ok(resCmd && resCmd.id, 'Expected task object with id from team task add');
11
+ assert.strictEqual(resCmd.title, 'CLI Task Test');
12
+
13
+ const resCmd2 = await handleChemx({ command: 'team_task add Direct SubCmd Task' });
14
+ assert.ok(resCmd2 && resCmd2.id, 'Expected task object with id from team_task add');
15
+ assert.strictEqual(resCmd2.title, 'Direct SubCmd Task');
16
+
17
+ const resInfer = await handleChemx({ action: 'team_task', params: { title: 'Inferred Add Task' } });
18
+ assert.ok(resInfer && resInfer.id, 'Expected task object with id when title is given');
19
+ assert.strictEqual(resInfer.title, 'Inferred Add Task');
20
+ });
21
+
22
+ test('Friction 2: patch handles all parameter aliases and command strings', async () => {
23
+ const testFile = path.resolve('test-patch-spec.txt');
24
+ fs.writeFileSync(testFile, 'line 1\nOLD_STRING\nline 3\n');
25
+
26
+ try {
27
+ await handleChemx({ action: 'patch', params: { path: 'test-patch-spec.txt', target: 'OLD_STRING', replace: 'NEW_STRING_1' } });
28
+ assert.strictEqual(fs.readFileSync(testFile, 'utf8'), 'line 1\nNEW_STRING_1\nline 3\n');
29
+
30
+ await handleChemx({ action: 'patch', params: { path: 'test-patch-spec.txt', search: 'NEW_STRING_1', replacement: 'NEW_STRING_2' } });
31
+ assert.strictEqual(fs.readFileSync(testFile, 'utf8'), 'line 1\nNEW_STRING_2\nline 3\n');
32
+
33
+ await handleChemx({ action: 'patch', params: { path: 'test-patch-spec.txt', targetContent: 'NEW_STRING_2', replacementContent: 'FINAL_STRING' } });
34
+ assert.strictEqual(fs.readFileSync(testFile, 'utf8'), 'line 1\nFINAL_STRING\nline 3\n');
35
+ } finally {
36
+ if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
37
+ }
38
+ });
39
+
40
+ test('Friction 3: Vue file AST line numbers match file line numbers exactly', () => {
41
+ const vueContent = [
42
+ '<template>',
43
+ ' <div>',
44
+ ' <p>Line 3</p>',
45
+ ' <p>Line 4</p>',
46
+ ' <p>Line 5</p>',
47
+ ' <p>Line 6</p>',
48
+ ' <p>Line 7</p>',
49
+ ' <p>Line 8</p>',
50
+ ' <p>Line 9</p>',
51
+ ' <p>Line 10</p>',
52
+ ' </div>',
53
+ '</template>',
54
+ '',
55
+ '<script setup lang="ts">',
56
+ 'const cond = a && b && c;',
57
+ '</script>'
58
+ ].join('\n');
59
+
60
+ const recorded = [];
61
+ const fakeReg = { record: (type, sig, loc) => recorded.push({ type, sig, loc }) };
62
+ auditCode(vueContent, 'SampleVue.vue', 'SampleVue.vue', { patternRegistry: fakeReg });
63
+
64
+ const condRec = recorded.find((r) => r.type === 'PREDICATE_LOGIC');
65
+ assert.ok(condRec, 'Expected PREDICATE_LOGIC recorded');
66
+ assert.strictEqual(condRec.loc.line, 15, 'Expected condition on line 15');
67
+ });
68
+
69
+ test('Friction 4: HOOK_SATURATION excludes canonical controller return wrappers', () => {
70
+ const hookCode = [
71
+ 'export function useMyComposable() {',
72
+ ' const a = useA();',
73
+ ' const b = useB();',
74
+ ' const c = useC();',
75
+ ' const d = useD();',
76
+ ' const e = useE();',
77
+ ' return useSharedStatusController({ a, b, c, d, e });',
78
+ '}'
79
+ ].join('\n');
80
+
81
+ const hookViolations = auditCode(hookCode, 'useMyComposable.ts', 'src/useMyComposable.ts');
82
+ const hookSat = hookViolations.filter((v) => v.rule === 'HOOK_SATURATION');
83
+ assert.strictEqual(hookSat.length, 0, 'useSharedStatusController should not trigger HOOK_SATURATION');
84
+ });
85
+
86
+ test('Friction 5: TEST_MISSING_COLOCATED is gated by profile', () => {
87
+ const compCode = '<template><button>Click</button></template>';
88
+
89
+ const defaultViolations = auditCode(compCode, 'src/components/molecules/m-button.vue', 'src/components/molecules/m-button.vue', { config: { profile: 'pragmatic' } });
90
+ const testMissingDefault = defaultViolations.filter((v) => v.rule === 'TEST_MISSING_COLOCATED');
91
+ assert.strictEqual(testMissingDefault.length, 0, 'Pragmatic profile should not flag TEST_MISSING_COLOCATED');
92
+
93
+ const strictViolations = auditCode(compCode, 'src/components/molecules/m-button.vue', 'src/components/molecules/m-button.vue', { config: { profile: 'atomic-strict' } });
94
+ const testMissingStrict = strictViolations.filter((v) => v.rule === 'TEST_MISSING_COLOCATED');
95
+ assert.strictEqual(testMissingStrict.length, 1, 'Atomic-strict profile should flag TEST_MISSING_COLOCATED');
96
+ });
@@ -12,18 +12,29 @@ export const printGenerateHelp = () => {
12
12
  ` ${CYAN}npx chemx generate${RESET} <name> [options]`,
13
13
  ` ${CYAN}npx chemx generate${RESET} <tier> <name> [options]`,
14
14
  '',
15
- `${BOLD}TIERS${RESET}`,
16
- ` ${CYAN}atom${RESET} (a-) Foundational UI elements (raw HTML permitted)`,
15
+ `${BOLD}TIERS & JIG KINDS${RESET}`,
17
16
  ` ${CYAN}molecule${RESET} (m-) Groups of atoms (no raw HTML, co-located controller)`,
17
+ ` ${CYAN}atom${RESET} (a-) Foundational UI elements (raw HTML permitted)`,
18
18
  ` ${CYAN}organism${RESET} (o-) Complex feature modules`,
19
19
  ` ${CYAN}hook${RESET} (use-) Domain state / headless composables (< 5 return properties)`,
20
20
  ` ${CYAN}view${RESET} (v-) Declarative Table-of-Contents views`,
21
+ ` ${CYAN}service${RESET} Domain service / API client with Result tuples and spec`,
22
+ ` ${CYAN}route${RESET} HTTP API route handler with schema validation and test`,
23
+ ` ${CYAN}store${RESET} State store / reactive machine with discriminated unions`,
24
+ ` ${CYAN}repo${RESET} Database repository with CRUD methods and Result tuples`,
25
+ ` ${CYAN}util${RESET} Pure stateless utility functions with test spec`,
26
+ ` ${CYAN}spec${RESET} Isolated unit test spec for an existing target`,
21
27
  '',
22
28
  `${BOLD}OPTIONS${RESET}`,
23
- ` ${CYAN}--tier=<tier>${RESET} Specify component tier`,
29
+ ` ${CYAN}--tier=<tier>${RESET} Specify component tier (atom, molecule, organism, hook, view)`,
30
+ ` ${CYAN}--jig=<kind>${RESET} Specify programmatic file kind (service, route, store, repo, util, spec)`,
24
31
  ` ${CYAN}--framework=<react|vue|svelte>${RESET} Framework flavor (default: auto-detected or react)`,
25
- ` ${CYAN}--dir=<path>${RESET} Target directory (default: src/components/<tier>s)`,
32
+ ` ${CYAN}--dir=<path>${RESET} Target directory (default: src/components/<tier>s or src/<kind>s)`,
26
33
  ` ${CYAN}--desc="<text>"${RESET} Describe functionality to tailor archetype and state`,
34
+ ` ${CYAN}--methods="<m1,m2>"${RESET} Method names or signatures for service/repo/store`,
35
+ ` ${CYAN}--state="<s1,s2>"${RESET} State property definitions for store or controller`,
36
+ ` ${CYAN}--routes="<r1,r2>"${RESET} Route definitions for API route jig (e.g. GET /items, POST /items)`,
37
+ ` ${CYAN}--schema="<json>"${RESET} Structured JSON schema definition payload`,
27
38
  ` ${CYAN}--dry-run${RESET} Preview planned files and lines without touching disk`,
28
39
  ` ${CYAN}--lean${RESET} Generate minimal capsule without controller/spec`,
29
40
  ` ${CYAN}--json${RESET} Output result as minified JSON`,
@@ -32,14 +43,23 @@ export const printGenerateHelp = () => {
32
43
  '',
33
44
  `${BOLD}EXAMPLES${RESET}`,
34
45
  ` ${CYAN}npx chemx generate m-task-list --framework=react${RESET}`,
35
- ` ${CYAN}npx chemx generate m-task-list --desc="add, toggle, remove items"${RESET}`,
36
- ` ${CYAN}npx chemx generate m-task-list --dry-run${RESET}`,
46
+ ` ${CYAN}npx chemx generate --jig=service payment-gateway --methods="processCharge,refund"${RESET}`,
47
+ ` ${CYAN}npx chemx jig route api-orders --routes="GET /orders, POST /orders"${RESET}`,
48
+ ` ${CYAN}npx chemx jig store session-machine --desc="user authentication lifecycle"${RESET}`,
37
49
  ` ${CYAN}npx chemx generate hook use-task-filter${RESET}`,
38
50
  ` ${CYAN}npx chemx generate view v-user-profile${RESET}`,
39
51
  '',
52
+ `${BOLD}TOKEN ECONOMICS & AGENT JIG${RESET}`,
53
+ ` ${DIM}Thesis: Passing a 30-50 token parameter payload to chemx generate / jig replaces${RESET}`,
54
+ ` ${DIM} 1,000-2,500 output tokens of repetitive TypeScript, SCSS, and spec boilerplate.${RESET}`,
55
+ ` ${DIM}Reduction: 90%+ fewer LLM output tokens consumed per component, service, or route.${RESET}`,
56
+ ` ${DIM}Invariants: Guarantees <100 LOC limits, Result tuples, 2-stage booleans, zero synthetic data,${RESET}`,
57
+ ` ${DIM} and co-located unit test specs without LLM syntax drift or hallucination.${RESET}`,
58
+ '',
40
59
  `${BOLD}LINE LIMITS${RESET}`,
41
60
  ` ${DIM}Molecule capsule template: < 100 lines (outer bound)${RESET}`,
42
61
  ` ${DIM}Co-located controller hook: < 100 lines (pure reactive state)${RESET}`,
62
+ ` ${DIM}Domain service / repo: < 100 lines (stateless or cohesive atomic class)${RESET}`,
43
63
  ` ${DIM}Table of Contents view: 10-20 lines (declarative slot assembly)${RESET}`,
44
64
  ''
45
65
  ];
@@ -0,0 +1,67 @@
1
+ import { createJigFiles, normalizeJigKind, JIG_KINDS } from './generator-jig.js';
2
+
3
+ export const handleJigCli = ({ rawArgs, positional, rawName, dirArg, descArg, isDryRun, isJson }) => {
4
+ const jigFlag = (rawArgs.find((a) => a.startsWith('--jig=')) || '').split('=')[1];
5
+ const kindFlag = (rawArgs.find((a) => a.startsWith('--kind=')) || '').split('=')[1];
6
+
7
+ let resolvedKind = jigFlag || kindFlag;
8
+ let resolvedName = rawName;
9
+
10
+ if (!resolvedKind && positional[0] && JIG_KINDS.has(positional[0].toLowerCase())) {
11
+ resolvedKind = positional[0].toLowerCase();
12
+ resolvedName = positional[1] || rawName;
13
+ } else if (positional[0] && positional[0].toLowerCase() === 'jig') {
14
+ resolvedKind = positional[1];
15
+ resolvedName = positional[2] || rawName;
16
+ }
17
+ resolvedKind = normalizeJigKind(resolvedKind || 'service');
18
+
19
+ const methodsArg = (rawArgs.find((a) => a.startsWith('--methods=')) || '').replace(/^--methods=/, '');
20
+ const stateArg = (rawArgs.find((a) => a.startsWith('--state=')) || '').replace(/^--state=/, '');
21
+ const routesArg = (rawArgs.find((a) => a.startsWith('--routes=')) || '').replace(/^--routes=/, '');
22
+ const presetArg = (rawArgs.find((a) => a.startsWith('--preset=')) || '').replace(/^--preset=/, '');
23
+ const schemaArg = (rawArgs.find((a) => a.startsWith('--schema=')) || '').replace(/^--schema=/, '');
24
+
25
+ let schemaData = {};
26
+ if (schemaArg) {
27
+ try {
28
+ schemaData = JSON.parse(schemaArg);
29
+ } catch {
30
+ // ignore JSON parse error
31
+ }
32
+ }
33
+
34
+ const jigRes = createJigFiles({
35
+ kind: schemaData.kind || resolvedKind,
36
+ name: schemaData.name || resolvedName || `sample-${resolvedKind}`,
37
+ dir: dirArg,
38
+ preset: presetArg,
39
+ methods: schemaData.methods || methodsArg,
40
+ state: schemaData.state || stateArg,
41
+ routes: schemaData.routes || routesArg,
42
+ desc: descArg || schemaData.desc,
43
+ dryRun: isDryRun,
44
+ cwd: process.cwd()
45
+ });
46
+
47
+ if (isJson) {
48
+ process.stdout.write(JSON.stringify(jigRes) + '\n');
49
+ return jigRes;
50
+ }
51
+
52
+ if (jigRes.dryRun) {
53
+ process.stdout.write(`\n\x1b[1m\x1b[33m[DRY RUN]\x1b[0m Would generate programmatic jig (${jigRes.kind}) at \x1b[36m${jigRes.relativeDir}/\x1b[0m:\n`);
54
+ for (const f of jigRes.previews || []) {
55
+ process.stdout.write(` \x1b[33m•\x1b[0m ${f.file} (${f.lines} lines)\n`);
56
+ }
57
+ process.stdout.write('\n\x1b[2mDry run complete. No files were written to disk.\x1b[0m\n\n');
58
+ return jigRes;
59
+ }
60
+
61
+ process.stdout.write(`\n\x1b[1m\x1b[32m✔ Successfully generated programmatic jig (${jigRes.kind}):\x1b[0m \x1b[36m${jigRes.relativeDir}/\x1b[0m\n`);
62
+ for (const f of jigRes.filesCreated) {
63
+ process.stdout.write(` \x1b[32m✔\x1b[0m ${f}\n`);
64
+ }
65
+ process.stdout.write('\n\x1b[2mChemical X Standards verified: < 100 lines per file, Result tuples, co-located spec tests.\x1b[0m\n\n');
66
+ return jigRes;
67
+ };