@claudetools/tools 0.7.0 → 0.7.2

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/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { dirname, join } from 'node:path';
10
10
  import { runSetup, runUninstall, runInit, runCleanup } from './setup.js';
11
11
  import { startServer } from './index.js';
12
12
  import { startWatcher, stopWatcher, watcherStatus } from './watcher.js';
13
+ import { generateCodebaseMap, generateCodebaseMapLocal } from './helpers/codebase-mapper.js';
13
14
  // Get version from package.json
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = dirname(__filename);
@@ -53,6 +54,8 @@ Commands:
53
54
  watch Start the file watcher daemon
54
55
  watch --stop Stop the watcher daemon
55
56
  watch --status Check watcher status
57
+ map Generate codebase map for current project
58
+ map --local Generate map locally without uploading
56
59
 
57
60
  Running without options starts the MCP server.
58
61
 
@@ -103,6 +106,65 @@ else if (positionals[0] === 'watch') {
103
106
  });
104
107
  }
105
108
  }
109
+ else if (positionals[0] === 'map') {
110
+ // Handle map command
111
+ const mapArgs = process.argv.slice(3); // Get args after 'map'
112
+ const isLocal = mapArgs.includes('--local');
113
+ (async () => {
114
+ const cwd = process.cwd();
115
+ if (isLocal) {
116
+ console.log(`Generating codebase map for: ${cwd}`);
117
+ const { index, markdown } = generateCodebaseMapLocal(cwd);
118
+ console.log(`\nGenerated map with ${index.summary.totalFiles} files, ${index.summary.totalSymbols} symbols`);
119
+ console.log(`\n--- CODEBASE MAP ---\n`);
120
+ console.log(markdown);
121
+ }
122
+ else {
123
+ // Load config to get API key and project ID
124
+ const { existsSync: exists, readFileSync: read } = await import('fs');
125
+ const { join: pathJoin, basename } = await import('path');
126
+ const { homedir } = await import('os');
127
+ const configPath = pathJoin(homedir(), '.claudetools', 'config.json');
128
+ const projectsPath = pathJoin(homedir(), '.claudetools', 'projects.json');
129
+ if (!exists(configPath)) {
130
+ console.error('Not configured. Run: claudetools --setup');
131
+ process.exit(1);
132
+ }
133
+ const config = JSON.parse(read(configPath, 'utf-8'));
134
+ if (!config.apiKey) {
135
+ console.error('No API key configured. Run: claudetools --setup');
136
+ process.exit(1);
137
+ }
138
+ // Find project ID for current directory
139
+ let projectId = null;
140
+ if (exists(projectsPath)) {
141
+ const projects = JSON.parse(read(projectsPath, 'utf-8'));
142
+ const binding = projects.bindings?.find((b) => b.local_path === cwd);
143
+ if (binding) {
144
+ projectId = binding.project_id;
145
+ }
146
+ }
147
+ if (!projectId) {
148
+ console.error('Current directory is not a registered project.');
149
+ console.error('Run: claudetools init');
150
+ process.exit(1);
151
+ }
152
+ console.log(`Generating codebase map for: ${basename(cwd)} (${projectId})`);
153
+ const result = await generateCodebaseMap(cwd, projectId, config.apiKey);
154
+ if (result.success) {
155
+ console.log('Codebase map generated and uploaded successfully!');
156
+ console.log(`View at: ${config.apiUrl || 'https://api.claudetools.dev'}/api/v1/codebase/${projectId}/map`);
157
+ }
158
+ else {
159
+ console.error('Failed to generate map:', result.error);
160
+ process.exit(1);
161
+ }
162
+ }
163
+ })().catch((error) => {
164
+ console.error('Map generation failed:', error);
165
+ process.exit(1);
166
+ });
167
+ }
106
168
  else {
107
169
  // Start MCP server
108
170
  startServer();
@@ -1,7 +1,7 @@
1
1
  import { EntitySpec } from '../parser.js';
2
2
  import { TemplateEngine } from '../template-engine.js';
3
3
  import { TemplateRegistry } from '../registry.js';
4
- import { GenerationResult, GenerationMetadata } from '../types.js';
4
+ import { GenerationResult, GenerationMetadata, PatternContext } from '../types.js';
5
5
  export declare abstract class BaseGenerator {
6
6
  protected engine: TemplateEngine;
7
7
  protected registry: TemplateRegistry;
@@ -10,6 +10,15 @@ export declare abstract class BaseGenerator {
10
10
  * Generate code from entity specification
11
11
  */
12
12
  generate(entity: EntitySpec, options?: any): Promise<GenerationResult>;
13
+ /**
14
+ * Resolve template variants based on detected patterns
15
+ * For each base template, check if a pattern-specific variant exists
16
+ */
17
+ protected resolveTemplateVariants(generatorId: string, baseTemplates: string[], patterns?: PatternContext): Promise<string[]>;
18
+ /**
19
+ * Build a map from base template names to resolved variants
20
+ */
21
+ private buildTemplateMap;
13
22
  /**
14
23
  * Get the generator ID (e.g., "express-api")
15
24
  */
@@ -25,7 +34,7 @@ export declare abstract class BaseGenerator {
25
34
  /**
26
35
  * Calculate generation metadata
27
36
  */
28
- protected calculateMetadata(generatorId: string, framework: string, entity: EntitySpec, files: Record<string, string>): GenerationMetadata;
37
+ protected calculateMetadata(generatorId: string, framework: string, entity: EntitySpec, files: Record<string, string>, patterns?: PatternContext): GenerationMetadata;
29
38
  /**
30
39
  * Validate options against generator capabilities
31
40
  */
@@ -5,7 +5,34 @@
5
5
  // Abstract base class for all code generators with common file generation
6
6
  // pipeline, metadata tracking, and utilities.
7
7
  //
8
+ // Pattern-Aware Template Selection:
9
+ // Templates can have variants based on detected patterns. For example:
10
+ // - validator.ts.j2 (default)
11
+ // - validator.zod.ts.j2 (if Zod is detected)
12
+ // - validator.yup.ts.j2 (if Yup is detected)
13
+ //
14
+ // The generator automatically selects the best variant based on detected patterns.
15
+ //
8
16
  import { TemplateEngine, buildContext } from '../template-engine.js';
17
+ /**
18
+ * Template variant mapping: pattern_id -> template suffix
19
+ */
20
+ const PATTERN_TEMPLATE_VARIANTS = {
21
+ 'zod-validation': '.zod',
22
+ 'yup-validation': '.yup',
23
+ 'react-hook-form': '.rhf',
24
+ 'formik': '.formik',
25
+ 'tanstack-query': '.tanstack',
26
+ 'swr-pattern': '.swr',
27
+ 'zustand': '.zustand',
28
+ 'redux-toolkit': '.redux',
29
+ 'tailwind': '.tailwind',
30
+ 'shadcn-ui': '.shadcn',
31
+ 'mui-patterns': '.mui',
32
+ 'chakra-patterns': '.chakra',
33
+ 'compound-components': '.compound',
34
+ 'headless-components': '.headless',
35
+ };
9
36
  export class BaseGenerator {
10
37
  engine;
11
38
  registry;
@@ -20,33 +47,84 @@ export class BaseGenerator {
20
47
  // Get generator metadata
21
48
  const generatorId = this.getGeneratorId();
22
49
  const metadata = await this.registry.getGeneratorMetadata(generatorId);
23
- // Get required templates
24
- const templateFiles = this.getRequiredTemplates(options);
50
+ // Get required templates (with pattern-aware variant selection)
51
+ const baseTemplates = this.getRequiredTemplates(options);
52
+ const templateFiles = await this.resolveTemplateVariants(generatorId, baseTemplates, options.patterns);
25
53
  const templates = await this.registry.getTemplates(generatorId, templateFiles);
26
54
  // Build template context
27
55
  const context = buildContext(entity, options);
28
56
  // Generate files
29
57
  const files = {};
30
58
  const fileMapping = this.getFileMapping(entity, options);
31
- for (const [outputPath, templateFile] of Object.entries(fileMapping)) {
32
- const template = templates[templateFile];
59
+ // Map base template names to resolved variants
60
+ const templateMap = this.buildTemplateMap(baseTemplates, templateFiles);
61
+ for (const [outputPath, baseTemplate] of Object.entries(fileMapping)) {
62
+ const resolvedTemplate = templateMap[baseTemplate] || baseTemplate;
63
+ const template = templates[resolvedTemplate];
33
64
  if (!template) {
34
- throw new Error(`Template not found: ${templateFile}`);
65
+ throw new Error(`Template not found: ${resolvedTemplate}`);
35
66
  }
36
67
  const content = this.engine.render(template, context);
37
68
  files[outputPath] = content;
38
69
  }
39
70
  // Calculate metadata
40
- const generationMetadata = this.calculateMetadata(generatorId, metadata.framework, entity, files);
71
+ const generationMetadata = this.calculateMetadata(generatorId, metadata.framework, entity, files, options.patterns);
41
72
  return {
42
73
  files,
43
74
  metadata: generationMetadata,
44
75
  };
45
76
  }
77
+ /**
78
+ * Resolve template variants based on detected patterns
79
+ * For each base template, check if a pattern-specific variant exists
80
+ */
81
+ async resolveTemplateVariants(generatorId, baseTemplates, patterns) {
82
+ if (!patterns?.detected?.length) {
83
+ return baseTemplates; // No patterns detected, use defaults
84
+ }
85
+ const resolvedTemplates = [];
86
+ for (const baseTemplate of baseTemplates) {
87
+ // Try to find a matching variant for each detected pattern
88
+ let resolved = baseTemplate;
89
+ // Sort patterns by confidence (highest first)
90
+ const sortedPatterns = [...patterns.detected].sort((a, b) => b.confidence - a.confidence);
91
+ for (const pattern of sortedPatterns) {
92
+ const suffix = PATTERN_TEMPLATE_VARIANTS[pattern.pattern_id];
93
+ if (!suffix)
94
+ continue;
95
+ // Build variant name: validator.ts.j2 -> validator.zod.ts.j2
96
+ const parts = baseTemplate.split('.');
97
+ if (parts.length >= 2) {
98
+ const variantName = `${parts[0]}${suffix}.${parts.slice(1).join('.')}`;
99
+ // Check if variant exists (try to fetch, cache result)
100
+ try {
101
+ await this.registry.getTemplate(generatorId, variantName);
102
+ resolved = variantName;
103
+ break; // Use first matching variant
104
+ }
105
+ catch {
106
+ // Variant doesn't exist, continue checking
107
+ }
108
+ }
109
+ }
110
+ resolvedTemplates.push(resolved);
111
+ }
112
+ return resolvedTemplates;
113
+ }
114
+ /**
115
+ * Build a map from base template names to resolved variants
116
+ */
117
+ buildTemplateMap(baseTemplates, resolvedTemplates) {
118
+ const map = {};
119
+ for (let i = 0; i < baseTemplates.length; i++) {
120
+ map[baseTemplates[i]] = resolvedTemplates[i];
121
+ }
122
+ return map;
123
+ }
46
124
  /**
47
125
  * Calculate generation metadata
48
126
  */
49
- calculateMetadata(generatorId, framework, entity, files) {
127
+ calculateMetadata(generatorId, framework, entity, files, patterns) {
50
128
  const filesGenerated = Object.keys(files).length;
51
129
  const linesOfCode = Object.values(files).reduce((sum, content) => sum + content.split('\n').length, 0);
52
130
  // Estimate token savings
@@ -54,7 +132,7 @@ export class BaseGenerator {
54
132
  // AI would generate all lines from scratch
55
133
  // With CodeDNA: ~150 tokens for the API call
56
134
  const estimatedTokensSaved = Math.max(0, linesOfCode * 25 - 150);
57
- return {
135
+ const metadata = {
58
136
  generator: generatorId,
59
137
  framework,
60
138
  entities: [entity.name],
@@ -62,6 +140,11 @@ export class BaseGenerator {
62
140
  linesOfCode,
63
141
  estimatedTokensSaved,
64
142
  };
143
+ // Add pattern information to metadata if patterns were used
144
+ if (patterns?.detected?.length) {
145
+ metadata.patternsApplied = patterns.detected.map(p => p.pattern_id);
146
+ }
147
+ return metadata;
65
148
  }
66
149
  /**
67
150
  * Validate options against generator capabilities
@@ -2,7 +2,8 @@
2
2
  // React Frontend Generator
3
3
  // =============================================================================
4
4
  //
5
- // Generate React components with ShadcnUI for forms, tables, and CRUD views.
5
+ // Generate React components for forms, tables, and CRUD views.
6
+ // Supports multiple UI libraries: shadcn, mui, chakra (default: shadcn)
6
7
  //
7
8
  import { BaseGenerator } from './base.js';
8
9
  export class ReactFrontendGenerator extends BaseGenerator {
@@ -71,6 +71,12 @@ export type Constraint = {
71
71
  kind: 'nullable';
72
72
  } | {
73
73
  kind: 'immutable';
74
+ } | {
75
+ kind: 'textarea';
76
+ } | {
77
+ kind: 'switch';
78
+ } | {
79
+ kind: 'radio';
74
80
  };
75
81
  export declare class EntityParser {
76
82
  /**
@@ -151,6 +151,13 @@ export class EntityParser {
151
151
  return { kind: 'nullable' };
152
152
  if (constraintStr === 'immutable')
153
153
  return { kind: 'immutable' };
154
+ // UI hints
155
+ if (constraintStr === 'textarea')
156
+ return { kind: 'textarea' };
157
+ if (constraintStr === 'switch')
158
+ return { kind: 'switch' };
159
+ if (constraintStr === 'radio')
160
+ return { kind: 'radio' };
154
161
  // Parameterized constraints: min(18), max(100), default(true), length(10,100), pattern(/regex/)
155
162
  const paramMatch = constraintStr.match(/^([a-z]+)\((.+)\)$/);
156
163
  if (paramMatch) {
@@ -1,18 +1,30 @@
1
1
  import { GeneratorMetadata } from './types.js';
2
2
  export interface RegistryResponse {
3
- generators: GeneratorMetadata[];
4
- version: string;
5
- updated: string;
3
+ success: boolean;
4
+ data: {
5
+ generators: GeneratorMetadata[];
6
+ byDomain: Record<string, GeneratorMetadata[]>;
7
+ summary: {
8
+ total: number;
9
+ api: number;
10
+ frontend: number;
11
+ component: number;
12
+ };
13
+ version: string;
14
+ };
6
15
  }
7
16
  export declare class TemplateRegistry {
8
17
  private baseUrl;
9
18
  private cacheDir;
10
19
  private useCache;
20
+ private registryCache;
11
21
  constructor(baseUrl?: string, useCache?: boolean);
12
22
  /**
13
23
  * List all available generators
24
+ * Fetches from Cloudflare D1 (single source of truth)
25
+ * @param domain Optional domain filter: 'api' | 'frontend' | 'component'
14
26
  */
15
- listGenerators(): Promise<GeneratorMetadata[]>;
27
+ listGenerators(domain?: 'api' | 'frontend' | 'component'): Promise<GeneratorMetadata[]>;
16
28
  /**
17
29
  * Get metadata for specific generator
18
30
  */
@@ -22,37 +34,31 @@ export declare class TemplateRegistry {
22
34
  */
23
35
  getTemplate(generatorId: string, templateFile: string): Promise<string>;
24
36
  /**
25
- * Get multiple templates at once
37
+ * Get multiple templates at once (parallel fetch)
26
38
  */
27
39
  getTemplates(generatorId: string, templateFiles: string[]): Promise<Record<string, string>>;
28
40
  /**
29
- * Check if template is cached locally
41
+ * Get cached template from file system
30
42
  */
31
43
  private getCachedTemplate;
32
44
  /**
33
- * Cache template locally
45
+ * Cache template to file system
34
46
  */
35
47
  private cacheTemplate;
36
48
  /**
37
- * Get local generators (fallback)
38
- */
39
- private getLocalGenerators;
40
- /**
41
- * Get local metadata (fallback)
42
- */
43
- private getLocalMetadata;
44
- /**
45
- * Get local template (fallback)
49
+ * Get template from bundled local files (fallback)
46
50
  */
47
51
  private getLocalTemplate;
48
52
  /**
49
- * Clear local cache
53
+ * Clear local template cache
50
54
  */
51
55
  clearCache(): Promise<void>;
52
56
  /**
53
57
  * Get cache statistics
54
58
  */
55
59
  getCacheStats(): Promise<{
60
+ registryCached: boolean;
61
+ registryAge: number | null;
56
62
  cachedGenerators: string[];
57
63
  totalFiles: number;
58
64
  totalSize: number;