@claudetools/tools 0.4.0 → 0.5.1
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/README.md +60 -4
- package/dist/cli.js +0 -0
- package/dist/codedna/parser.d.ts +40 -3
- package/dist/codedna/parser.js +65 -8
- package/dist/codedna/registry.js +4 -1
- package/dist/codedna/template-engine.js +66 -32
- package/dist/handlers/codedna-handlers.d.ts +1 -1
- package/dist/handlers/codedna-handlers.js +27 -0
- package/dist/helpers/api-client.js +7 -0
- package/dist/helpers/codedna-monitoring.d.ts +34 -0
- package/dist/helpers/codedna-monitoring.js +159 -0
- package/dist/helpers/error-tracking.d.ts +73 -0
- package/dist/helpers/error-tracking.js +164 -0
- package/dist/helpers/usage-analytics.d.ts +91 -0
- package/dist/helpers/usage-analytics.js +256 -0
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +73 -0
- package/docs/AUTO-REGISTRATION.md +353 -0
- package/docs/CLAUDE4_PROMPT_ANALYSIS.md +589 -0
- package/docs/ENTITY_DSL_REFERENCE.md +685 -0
- package/docs/MODERN_STACK_COMPLETE_GUIDE.md +706 -0
- package/docs/PROMPT_STANDARDIZATION_RESULTS.md +324 -0
- package/docs/PROMPT_TIER_TEMPLATES.md +787 -0
- package/docs/RESEARCH_METHODOLOGY_EXTRACTION.md +336 -0
- package/package.json +12 -3
- package/scripts/verify-prompt-compliance.sh +197 -0
package/README.md
CHANGED
|
@@ -67,19 +67,75 @@ CLAUDETOOLS_API_URL=https://api.claudetools.dev
|
|
|
67
67
|
- **Impact analysis** for changes
|
|
68
68
|
- **Pattern detection** (security, performance)
|
|
69
69
|
|
|
70
|
+
### CodeDNA (AI Code Generation)
|
|
71
|
+
- **95-99% token savings** by generating production code from compact Entity DSL specs
|
|
72
|
+
- **Production-ready APIs** with models, controllers, routes, validation, tests
|
|
73
|
+
- **Express.js generator** available (FastAPI, NestJS, React coming soon)
|
|
74
|
+
- **Template registry** hosted on ClaudeTools API with global CDN
|
|
75
|
+
|
|
76
|
+
### 10/10 Prompt Engineering Framework
|
|
77
|
+
- **Production-proven architecture** from Claude 4 analysis (~60K char prompt)
|
|
78
|
+
- **4 complexity tiers** (Minimal/Standard/Professional/Enterprise) with progressive disclosure
|
|
79
|
+
- **7 semantic layers** with XML boundaries for machine-parseability
|
|
80
|
+
- **Token-optimized templates** (500t → 10000t based on complexity)
|
|
81
|
+
- **Compliance verification** with automated validation script
|
|
82
|
+
|
|
83
|
+
#### Quick Example
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Instead of AI writing 1000 lines of code (30,000 tokens)...
|
|
87
|
+
// Use a compact Entity DSL spec (150 tokens):
|
|
88
|
+
|
|
89
|
+
codedna_generate_api({
|
|
90
|
+
spec: "User(email:string:unique, password:string:hashed, age:integer:min(18))",
|
|
91
|
+
framework: "express",
|
|
92
|
+
options: {
|
|
93
|
+
auth: true,
|
|
94
|
+
validation: true,
|
|
95
|
+
tests: true,
|
|
96
|
+
database: "postgresql"
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Returns 6 complete files:
|
|
101
|
+
// - src/models/user.model.ts
|
|
102
|
+
// - src/controllers/user.controller.ts
|
|
103
|
+
// - src/routes/user.routes.ts
|
|
104
|
+
// - src/validators/user.validator.ts
|
|
105
|
+
// - src/middleware/auth.middleware.ts
|
|
106
|
+
// - tests/user.test.ts
|
|
107
|
+
|
|
108
|
+
// Token savings: 11,100 tokens (98.7%)
|
|
109
|
+
// Cost savings: $0.11 per generation
|
|
110
|
+
// Time savings: 2-3 minutes → 10 seconds
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Available Tools
|
|
114
|
+
|
|
115
|
+
- `codedna_generate_api(spec, framework, options)` - Generate complete REST API
|
|
116
|
+
- `codedna_validate_spec(spec)` - Validate Entity DSL syntax
|
|
117
|
+
- `codedna_list_generators()` - List available code generators
|
|
118
|
+
|
|
119
|
+
See [CODEDNA_README.md](./CODEDNA_README.md) for full documentation.
|
|
120
|
+
|
|
70
121
|
## CLI
|
|
71
122
|
|
|
72
123
|
```bash
|
|
73
|
-
claudetools --setup
|
|
74
|
-
claudetools --version
|
|
75
|
-
claudetools --help
|
|
76
|
-
claudetools
|
|
124
|
+
claudetools --setup # Interactive configuration
|
|
125
|
+
claudetools --version # Show version
|
|
126
|
+
claudetools --help # Show help
|
|
127
|
+
claudetools # Start MCP server
|
|
128
|
+
npm run prompt:verify # Verify prompt compliance with 10/10 framework
|
|
77
129
|
```
|
|
78
130
|
|
|
79
131
|
## Documentation
|
|
80
132
|
|
|
81
133
|
- [GitHub](https://github.com/claudetools/memory)
|
|
82
134
|
- [Configuration Guide](./CONFIG.md)
|
|
135
|
+
- [CodeDNA Guide](./CODEDNA_README.md) - AI code generation with 95-99% token savings
|
|
136
|
+
- [10/10 Prompt Framework](./docs/PROMPT_TIER_TEMPLATES.md) - Production-grade prompt engineering
|
|
137
|
+
- [Claude 4 Analysis](./docs/CLAUDE4_PROMPT_ANALYSIS.md) - Insights from ~60K char production prompt
|
|
138
|
+
- [Research Methodology](./docs/RESEARCH_METHODOLOGY_EXTRACTION.md) - Claude.ai Desktop research patterns
|
|
83
139
|
|
|
84
140
|
## License
|
|
85
141
|
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/codedna/parser.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
export interface EntitySpec {
|
|
2
2
|
name: string;
|
|
3
3
|
fields: Field[];
|
|
4
|
+
hooks?: LifecycleHooks;
|
|
5
|
+
permissions?: Permission[];
|
|
6
|
+
}
|
|
7
|
+
export interface LifecycleHooks {
|
|
8
|
+
beforeCreate?: string[];
|
|
9
|
+
afterCreate?: string[];
|
|
10
|
+
beforeUpdate?: string[];
|
|
11
|
+
afterUpdate?: string[];
|
|
12
|
+
beforeDelete?: string[];
|
|
13
|
+
afterDelete?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface Permission {
|
|
16
|
+
action: 'create' | 'read' | 'update' | 'delete';
|
|
17
|
+
roles?: string[];
|
|
18
|
+
condition?: string;
|
|
4
19
|
}
|
|
5
20
|
export interface Field {
|
|
6
21
|
name: string;
|
|
@@ -9,13 +24,20 @@ export interface Field {
|
|
|
9
24
|
}
|
|
10
25
|
export type FieldType = {
|
|
11
26
|
kind: 'primitive';
|
|
12
|
-
value: 'string' | 'integer' | 'decimal' | 'boolean' | 'datetime';
|
|
27
|
+
value: 'string' | 'integer' | 'decimal' | 'boolean' | 'datetime' | 'email' | 'url' | 'json';
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'array';
|
|
30
|
+
itemType: 'string' | 'integer' | 'decimal' | 'boolean';
|
|
13
31
|
} | {
|
|
14
32
|
kind: 'reference';
|
|
15
33
|
entity: string;
|
|
34
|
+
relation?: 'oneToMany' | 'manyToMany';
|
|
16
35
|
} | {
|
|
17
36
|
kind: 'enum';
|
|
18
37
|
values: string[];
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'computed';
|
|
40
|
+
expression: string;
|
|
19
41
|
};
|
|
20
42
|
export type Constraint = {
|
|
21
43
|
kind: 'unique';
|
|
@@ -34,6 +56,21 @@ export type Constraint = {
|
|
|
34
56
|
} | {
|
|
35
57
|
kind: 'default';
|
|
36
58
|
value: string;
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'length';
|
|
61
|
+
min?: number;
|
|
62
|
+
max?: number;
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'pattern';
|
|
65
|
+
regex: string;
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'email';
|
|
68
|
+
} | {
|
|
69
|
+
kind: 'url';
|
|
70
|
+
} | {
|
|
71
|
+
kind: 'nullable';
|
|
72
|
+
} | {
|
|
73
|
+
kind: 'immutable';
|
|
37
74
|
};
|
|
38
75
|
export declare class EntityParser {
|
|
39
76
|
/**
|
|
@@ -54,11 +91,11 @@ export declare class EntityParser {
|
|
|
54
91
|
*/
|
|
55
92
|
private parseField;
|
|
56
93
|
/**
|
|
57
|
-
* Parse field type (primitive, reference, enum)
|
|
94
|
+
* Parse field type (primitive, reference, enum, array, computed)
|
|
58
95
|
*/
|
|
59
96
|
private parseType;
|
|
60
97
|
/**
|
|
61
|
-
* Parse field constraint (unique, required, hashed, min, max, default)
|
|
98
|
+
* Parse field constraint (unique, required, hashed, min, max, default, length, pattern, email, url, nullable, immutable)
|
|
62
99
|
*/
|
|
63
100
|
private parseConstraint;
|
|
64
101
|
}
|
package/dist/codedna/parser.js
CHANGED
|
@@ -22,12 +22,12 @@ export class EntityParser {
|
|
|
22
22
|
}
|
|
23
23
|
const name = nameMatch[1];
|
|
24
24
|
// Extract fields section
|
|
25
|
-
const fieldsMatch = spec.match(/\((
|
|
25
|
+
const fieldsMatch = spec.match(/\((.*)\)$/);
|
|
26
26
|
if (!fieldsMatch) {
|
|
27
27
|
throw new Error('Invalid entity spec: missing closing parenthesis');
|
|
28
28
|
}
|
|
29
29
|
const fieldsStr = fieldsMatch[1];
|
|
30
|
-
const fields = this.parseFields(fieldsStr);
|
|
30
|
+
const fields = fieldsStr.trim() ? this.parseFields(fieldsStr) : [];
|
|
31
31
|
return { name, fields };
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
@@ -84,14 +84,36 @@ export class EntityParser {
|
|
|
84
84
|
return { name, type, constraints };
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
* Parse field type (primitive, reference, enum)
|
|
87
|
+
* Parse field type (primitive, reference, enum, array, computed)
|
|
88
88
|
*/
|
|
89
89
|
parseType(typeStr) {
|
|
90
|
-
// Check for reference:
|
|
90
|
+
// Check for reference with relation: refMany(EntityName) or refOne(EntityName)
|
|
91
|
+
// oneToMany = entity has many of this type (e.g., User has many Posts)
|
|
92
|
+
// manyToMany = entities have many-to-many relationship
|
|
93
|
+
const refManyMatch = typeStr.match(/^refMany\(([A-Z][a-zA-Z0-9]*)\)$/);
|
|
94
|
+
if (refManyMatch) {
|
|
95
|
+
return { kind: 'reference', entity: refManyMatch[1], relation: 'manyToMany' };
|
|
96
|
+
}
|
|
97
|
+
const refOneMatch = typeStr.match(/^refOne\(([A-Z][a-zA-Z0-9]*)\)$/);
|
|
98
|
+
if (refOneMatch) {
|
|
99
|
+
return { kind: 'reference', entity: refOneMatch[1], relation: 'oneToMany' };
|
|
100
|
+
}
|
|
101
|
+
// Check for reference: ref(EntityName) - default simple reference
|
|
91
102
|
const refMatch = typeStr.match(/^ref\(([A-Z][a-zA-Z0-9]*)\)$/);
|
|
92
103
|
if (refMatch) {
|
|
93
104
|
return { kind: 'reference', entity: refMatch[1] };
|
|
94
105
|
}
|
|
106
|
+
// Check for array: array(string), array(integer), etc.
|
|
107
|
+
const arrayMatch = typeStr.match(/^array\((string|integer|decimal|boolean)\)$/);
|
|
108
|
+
if (arrayMatch) {
|
|
109
|
+
const itemType = arrayMatch[1];
|
|
110
|
+
return { kind: 'array', itemType };
|
|
111
|
+
}
|
|
112
|
+
// Check for computed: computed(expression)
|
|
113
|
+
const computedMatch = typeStr.match(/^computed\((.+)\)$/);
|
|
114
|
+
if (computedMatch) {
|
|
115
|
+
return { kind: 'computed', expression: computedMatch[1] };
|
|
116
|
+
}
|
|
95
117
|
// Check for enum: enum(val1,val2,val3)
|
|
96
118
|
const enumMatch = typeStr.match(/^enum\((.+)\)$/);
|
|
97
119
|
if (enumMatch) {
|
|
@@ -101,15 +123,15 @@ export class EntityParser {
|
|
|
101
123
|
}
|
|
102
124
|
return { kind: 'enum', values };
|
|
103
125
|
}
|
|
104
|
-
// Primitive types
|
|
105
|
-
const primitives = ['string', 'integer', 'decimal', 'boolean', 'datetime'];
|
|
126
|
+
// Primitive types (including new email, url, json)
|
|
127
|
+
const primitives = ['string', 'integer', 'decimal', 'boolean', 'datetime', 'email', 'url', 'json'];
|
|
106
128
|
if (primitives.includes(typeStr)) {
|
|
107
129
|
return { kind: 'primitive', value: typeStr };
|
|
108
130
|
}
|
|
109
131
|
throw new Error(`Unknown field type: ${typeStr}`);
|
|
110
132
|
}
|
|
111
133
|
/**
|
|
112
|
-
* Parse field constraint (unique, required, hashed, min, max, default)
|
|
134
|
+
* Parse field constraint (unique, required, hashed, min, max, default, length, pattern, email, url, nullable, immutable)
|
|
113
135
|
*/
|
|
114
136
|
parseConstraint(constraintStr) {
|
|
115
137
|
// Simple constraints
|
|
@@ -121,7 +143,15 @@ export class EntityParser {
|
|
|
121
143
|
return { kind: 'hashed' };
|
|
122
144
|
if (constraintStr === 'index')
|
|
123
145
|
return { kind: 'index' };
|
|
124
|
-
|
|
146
|
+
if (constraintStr === 'email')
|
|
147
|
+
return { kind: 'email' };
|
|
148
|
+
if (constraintStr === 'url')
|
|
149
|
+
return { kind: 'url' };
|
|
150
|
+
if (constraintStr === 'nullable')
|
|
151
|
+
return { kind: 'nullable' };
|
|
152
|
+
if (constraintStr === 'immutable')
|
|
153
|
+
return { kind: 'immutable' };
|
|
154
|
+
// Parameterized constraints: min(18), max(100), default(true), length(10,100), pattern(/regex/)
|
|
125
155
|
const paramMatch = constraintStr.match(/^([a-z]+)\((.+)\)$/);
|
|
126
156
|
if (paramMatch) {
|
|
127
157
|
const [, kind, value] = paramMatch;
|
|
@@ -142,6 +172,33 @@ export class EntityParser {
|
|
|
142
172
|
if (kind === 'default') {
|
|
143
173
|
return { kind: 'default', value };
|
|
144
174
|
}
|
|
175
|
+
if (kind === 'length') {
|
|
176
|
+
// Support both length(max) and length(min,max)
|
|
177
|
+
const parts = value.split(',').map(v => v.trim());
|
|
178
|
+
if (parts.length === 1) {
|
|
179
|
+
const maxValue = Number(parts[0]);
|
|
180
|
+
if (isNaN(maxValue)) {
|
|
181
|
+
throw new Error(`Invalid length constraint value: ${parts[0]}`);
|
|
182
|
+
}
|
|
183
|
+
return { kind: 'length', max: maxValue };
|
|
184
|
+
}
|
|
185
|
+
else if (parts.length === 2) {
|
|
186
|
+
const minValue = Number(parts[0]);
|
|
187
|
+
const maxValue = Number(parts[1]);
|
|
188
|
+
if (isNaN(minValue) || isNaN(maxValue)) {
|
|
189
|
+
throw new Error(`Invalid length constraint values: ${value}`);
|
|
190
|
+
}
|
|
191
|
+
return { kind: 'length', min: minValue, max: maxValue };
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
throw new Error(`Invalid length constraint format: ${value}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (kind === 'pattern') {
|
|
198
|
+
// Remove surrounding slashes if present
|
|
199
|
+
const regex = value.replace(/^\/(.+)\/$/, '$1');
|
|
200
|
+
return { kind: 'pattern', regex };
|
|
201
|
+
}
|
|
145
202
|
}
|
|
146
203
|
throw new Error(`Unknown constraint: ${constraintStr}`);
|
|
147
204
|
}
|
package/dist/codedna/registry.js
CHANGED
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { errorTracker } from '../helpers/error-tracking.js';
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
export class TemplateRegistry {
|
|
13
14
|
baseUrl;
|
|
14
15
|
cacheDir;
|
|
15
16
|
useCache;
|
|
16
|
-
constructor(baseUrl = 'https://
|
|
17
|
+
constructor(baseUrl = 'https://api.claudetools.dev/api/v1/codedna', useCache = true) {
|
|
17
18
|
this.baseUrl = baseUrl;
|
|
18
19
|
this.cacheDir = path.join(__dirname, '../../templates');
|
|
19
20
|
this.useCache = useCache;
|
|
@@ -82,6 +83,8 @@ export class TemplateRegistry {
|
|
|
82
83
|
}
|
|
83
84
|
catch (error) {
|
|
84
85
|
console.warn('Failed to fetch template from registry, using local fallback:', error);
|
|
86
|
+
// Track template fetch error
|
|
87
|
+
await errorTracker.trackTemplateFetchError(generatorId, templateFile, error instanceof Error ? error : new Error(String(error)));
|
|
85
88
|
return this.getLocalTemplate(generatorId, templateFile);
|
|
86
89
|
}
|
|
87
90
|
}
|
|
@@ -30,6 +30,8 @@ export class TemplateEngine {
|
|
|
30
30
|
addFilters() {
|
|
31
31
|
// Pluralize: User → Users
|
|
32
32
|
this.env.addFilter('plural', (str) => {
|
|
33
|
+
if (str === 'person')
|
|
34
|
+
return 'people';
|
|
33
35
|
if (str.endsWith('y'))
|
|
34
36
|
return str.slice(0, -1) + 'ies';
|
|
35
37
|
if (str.endsWith('s'))
|
|
@@ -44,64 +46,101 @@ export class TemplateEngine {
|
|
|
44
46
|
this.env.addFilter('upper', (str) => {
|
|
45
47
|
return str.toUpperCase();
|
|
46
48
|
});
|
|
47
|
-
// camelCase: user_name → userName
|
|
49
|
+
// camelCase: user_name → userName, first-name → firstName, my value → myValue
|
|
48
50
|
this.env.addFilter('camel', (str) => {
|
|
49
|
-
return str
|
|
51
|
+
return str
|
|
52
|
+
.replace(/[-_\s]([a-zA-Z])/g, (_, letter) => letter.toUpperCase())
|
|
53
|
+
.replace(/^[A-Z]/, letter => letter.toLowerCase());
|
|
50
54
|
});
|
|
51
|
-
// PascalCase: user_name → UserName
|
|
55
|
+
// PascalCase: user_name → UserName, first-name → FirstName, my value → MyValue
|
|
52
56
|
this.env.addFilter('pascal', (str) => {
|
|
53
|
-
const camel = str.replace(/_([a-
|
|
57
|
+
const camel = str.replace(/[-_\s]([a-zA-Z])/g, (_, letter) => letter.toUpperCase());
|
|
54
58
|
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
55
59
|
});
|
|
56
|
-
// snake_case: userName → user_name
|
|
60
|
+
// snake_case: userName → user_name, FirstName → first_name
|
|
57
61
|
this.env.addFilter('snake', (str) => {
|
|
58
|
-
return str
|
|
62
|
+
return str
|
|
63
|
+
.replace(/[A-Z]/g, letter => '_' + letter.toLowerCase())
|
|
64
|
+
.replace(/^_/, '');
|
|
59
65
|
});
|
|
60
|
-
// kebab-case: userName → user-name
|
|
66
|
+
// kebab-case: userName → user-name, FirstName → first-name
|
|
61
67
|
this.env.addFilter('kebab', (str) => {
|
|
62
|
-
return str
|
|
68
|
+
return str
|
|
69
|
+
.replace(/[A-Z]/g, letter => '-' + letter.toLowerCase())
|
|
70
|
+
.replace(/^-/, '');
|
|
63
71
|
});
|
|
64
72
|
// Check if array includes value
|
|
65
73
|
this.env.addFilter('includes', (arr, val) => {
|
|
66
74
|
return Array.isArray(arr) && arr.includes(val);
|
|
67
75
|
});
|
|
68
|
-
// Get TypeScript type for field
|
|
69
|
-
this.env.addFilter('tsType', (
|
|
70
|
-
if (
|
|
76
|
+
// Get TypeScript type for field type
|
|
77
|
+
this.env.addFilter('tsType', (type) => {
|
|
78
|
+
if (type.kind === 'primitive') {
|
|
71
79
|
const typeMap = {
|
|
72
80
|
string: 'string',
|
|
73
81
|
integer: 'number',
|
|
74
82
|
decimal: 'number',
|
|
75
83
|
boolean: 'boolean',
|
|
76
84
|
datetime: 'Date',
|
|
85
|
+
email: 'string',
|
|
86
|
+
url: 'string',
|
|
87
|
+
json: 'any',
|
|
77
88
|
};
|
|
78
|
-
return typeMap[
|
|
89
|
+
return typeMap[type.value] || 'any';
|
|
79
90
|
}
|
|
80
|
-
if (
|
|
81
|
-
|
|
91
|
+
if (type.kind === 'array') {
|
|
92
|
+
const itemTypeMap = {
|
|
93
|
+
string: 'string',
|
|
94
|
+
integer: 'number',
|
|
95
|
+
decimal: 'number',
|
|
96
|
+
boolean: 'boolean',
|
|
97
|
+
};
|
|
98
|
+
return `${itemTypeMap[type.itemType]}[]`;
|
|
99
|
+
}
|
|
100
|
+
if (type.kind === 'reference') {
|
|
101
|
+
return type.entity;
|
|
102
|
+
}
|
|
103
|
+
if (type.kind === 'enum') {
|
|
104
|
+
return type.values.map((v) => `'${v}'`).join(' | ');
|
|
82
105
|
}
|
|
83
|
-
if (
|
|
84
|
-
return
|
|
106
|
+
if (type.kind === 'computed') {
|
|
107
|
+
return 'any'; // Computed fields can be any type
|
|
85
108
|
}
|
|
86
109
|
return 'any';
|
|
87
110
|
});
|
|
88
|
-
// Get SQL type for field
|
|
89
|
-
this.env.addFilter('sqlType', (
|
|
90
|
-
if (
|
|
111
|
+
// Get SQL type for field type
|
|
112
|
+
this.env.addFilter('sqlType', (type) => {
|
|
113
|
+
if (type.kind === 'primitive') {
|
|
91
114
|
const typeMap = {
|
|
92
115
|
string: 'VARCHAR(255)',
|
|
93
116
|
integer: 'INTEGER',
|
|
94
117
|
decimal: 'DECIMAL(10,2)',
|
|
95
118
|
boolean: 'BOOLEAN',
|
|
96
119
|
datetime: 'TIMESTAMP',
|
|
120
|
+
email: 'VARCHAR(255)',
|
|
121
|
+
url: 'VARCHAR(512)',
|
|
122
|
+
json: 'JSONB',
|
|
97
123
|
};
|
|
98
|
-
return typeMap[
|
|
124
|
+
return typeMap[type.value] || 'TEXT';
|
|
99
125
|
}
|
|
100
|
-
if (
|
|
126
|
+
if (type.kind === 'array') {
|
|
127
|
+
// PostgreSQL array syntax
|
|
128
|
+
const itemTypeMap = {
|
|
129
|
+
string: 'TEXT',
|
|
130
|
+
integer: 'INTEGER',
|
|
131
|
+
decimal: 'DECIMAL(10,2)',
|
|
132
|
+
boolean: 'BOOLEAN',
|
|
133
|
+
};
|
|
134
|
+
return `${itemTypeMap[type.itemType]}[]`;
|
|
135
|
+
}
|
|
136
|
+
if (type.kind === 'reference') {
|
|
101
137
|
return 'INTEGER'; // Foreign key
|
|
102
138
|
}
|
|
103
|
-
if (
|
|
104
|
-
return `ENUM(${
|
|
139
|
+
if (type.kind === 'enum') {
|
|
140
|
+
return `ENUM(${type.values.map((v) => `'${v}'`).join(',')})`;
|
|
141
|
+
}
|
|
142
|
+
if (type.kind === 'computed') {
|
|
143
|
+
return 'GENERATED'; // Virtual/computed column
|
|
105
144
|
}
|
|
106
145
|
return 'TEXT';
|
|
107
146
|
});
|
|
@@ -109,22 +148,17 @@ export class TemplateEngine {
|
|
|
109
148
|
this.env.addFilter('hasConstraint', (field, constraintKind) => {
|
|
110
149
|
return field.constraints.some(c => c.kind === constraintKind);
|
|
111
150
|
});
|
|
112
|
-
// Get constraint
|
|
151
|
+
// Get constraint object (for min, max, default)
|
|
113
152
|
this.env.addFilter('getConstraint', (field, constraintKind) => {
|
|
114
|
-
|
|
115
|
-
if (!constraint)
|
|
116
|
-
return null;
|
|
117
|
-
if ('value' in constraint)
|
|
118
|
-
return constraint.value;
|
|
119
|
-
return true;
|
|
153
|
+
return field.constraints.find(c => c.kind === constraintKind) || null;
|
|
120
154
|
});
|
|
121
155
|
// Join array with custom separator
|
|
122
156
|
this.env.addFilter('joinWith', (arr, separator) => {
|
|
123
157
|
return arr.join(separator);
|
|
124
158
|
});
|
|
125
|
-
// First letter uppercase
|
|
159
|
+
// First letter uppercase, rest lowercase
|
|
126
160
|
this.env.addFilter('capitalize', (str) => {
|
|
127
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
161
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
128
162
|
});
|
|
129
163
|
// Indent lines
|
|
130
164
|
this.env.addFilter('indent', (str, spaces = 2) => {
|
|
@@ -104,7 +104,7 @@ export declare function handleValidateSpec(args: any): Promise<{
|
|
|
104
104
|
fields: {
|
|
105
105
|
name: string;
|
|
106
106
|
type: import("../codedna/parser.js").FieldType;
|
|
107
|
-
constraints: ("default" | "min" | "max" | "required" | "unique" | "hashed" | "index")[];
|
|
107
|
+
constraints: ("default" | "length" | "email" | "min" | "max" | "url" | "required" | "pattern" | "nullable" | "unique" | "hashed" | "index" | "immutable")[];
|
|
108
108
|
}[];
|
|
109
109
|
};
|
|
110
110
|
summary: {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { validateSpec } from '../codedna/parser.js';
|
|
9
9
|
import { TemplateRegistry } from '../codedna/registry.js';
|
|
10
10
|
import { ExpressApiGenerator } from '../codedna/generators/express-api.js';
|
|
11
|
+
import { errorTracker } from '../helpers/error-tracking.js';
|
|
12
|
+
import { analytics } from '../helpers/usage-analytics.js';
|
|
11
13
|
// Singleton registry instance
|
|
12
14
|
const registry = new TemplateRegistry();
|
|
13
15
|
/**
|
|
@@ -18,6 +20,8 @@ export async function handleGenerateApi(args) {
|
|
|
18
20
|
// Validate Entity DSL specification
|
|
19
21
|
const validation = validateSpec(spec);
|
|
20
22
|
if (!validation.valid) {
|
|
23
|
+
// Track validation error
|
|
24
|
+
await errorTracker.trackValidationError(spec, validation.errors || []);
|
|
21
25
|
return {
|
|
22
26
|
error: 'Invalid entity specification',
|
|
23
27
|
details: validation.errors,
|
|
@@ -47,8 +51,24 @@ export async function handleGenerateApi(args) {
|
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
try {
|
|
54
|
+
const startTime = Date.now();
|
|
50
55
|
// Generate code
|
|
51
56
|
const result = await generator.generate(validation.parsed, options);
|
|
57
|
+
const executionTime = Date.now() - startTime;
|
|
58
|
+
// Track successful generation
|
|
59
|
+
await analytics.trackGeneration({
|
|
60
|
+
operation: 'generate_api',
|
|
61
|
+
generator: `${framework}-api`,
|
|
62
|
+
framework,
|
|
63
|
+
entityName: validation.parsed.name,
|
|
64
|
+
fieldCount: validation.parsed.fields.length,
|
|
65
|
+
specLength: spec.length,
|
|
66
|
+
filesGenerated: result.metadata.filesGenerated,
|
|
67
|
+
linesOfCode: result.metadata.linesOfCode,
|
|
68
|
+
tokensSaved: result.metadata.estimatedTokensSaved,
|
|
69
|
+
options: options,
|
|
70
|
+
executionTimeMs: executionTime,
|
|
71
|
+
});
|
|
52
72
|
return {
|
|
53
73
|
success: true,
|
|
54
74
|
files: result.files,
|
|
@@ -62,6 +82,8 @@ export async function handleGenerateApi(args) {
|
|
|
62
82
|
};
|
|
63
83
|
}
|
|
64
84
|
catch (error) {
|
|
85
|
+
// Track generation error
|
|
86
|
+
await errorTracker.trackGenerationError(framework, spec, error instanceof Error ? error : new Error(String(error)));
|
|
65
87
|
return {
|
|
66
88
|
error: 'Code generation failed',
|
|
67
89
|
message: error instanceof Error ? error.message : String(error),
|
|
@@ -129,6 +151,8 @@ export async function handleListGenerators() {
|
|
|
129
151
|
};
|
|
130
152
|
}
|
|
131
153
|
catch (error) {
|
|
154
|
+
// Track network error for registry access
|
|
155
|
+
await errorTracker.trackNetworkError('list_generators', error instanceof Error ? error : new Error(String(error)));
|
|
132
156
|
return {
|
|
133
157
|
error: 'Failed to list generators',
|
|
134
158
|
message: error instanceof Error ? error.message : String(error),
|
|
@@ -141,7 +165,9 @@ export async function handleListGenerators() {
|
|
|
141
165
|
export async function handleValidateSpec(args) {
|
|
142
166
|
const { spec } = args;
|
|
143
167
|
const validation = validateSpec(spec);
|
|
168
|
+
// Track validation
|
|
144
169
|
if (validation.valid && validation.parsed) {
|
|
170
|
+
await analytics.trackValidation(spec, true, validation.parsed.name, validation.parsed.fields.length);
|
|
145
171
|
return {
|
|
146
172
|
valid: true,
|
|
147
173
|
entity: {
|
|
@@ -160,6 +186,7 @@ export async function handleValidateSpec(args) {
|
|
|
160
186
|
},
|
|
161
187
|
};
|
|
162
188
|
}
|
|
189
|
+
await analytics.trackValidation(spec, false);
|
|
163
190
|
return {
|
|
164
191
|
valid: false,
|
|
165
192
|
errors: validation.errors,
|
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
// API Client and Memory Operations
|
|
3
3
|
// =============================================================================
|
|
4
4
|
import { API_BASE_URL, DEFAULT_USER_ID } from './config.js';
|
|
5
|
+
import { getConfig } from './config-manager.js';
|
|
5
6
|
export async function apiRequest(endpoint, method = 'GET', body) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
const apiKey = config.apiKey || process.env.CLAUDETOOLS_API_KEY || process.env.MEMORY_API_KEY;
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
throw new Error('No API key found. Set CLAUDETOOLS_API_KEY or MEMORY_API_KEY in environment or ~/.claudetools/config.json');
|
|
11
|
+
}
|
|
6
12
|
const url = `${API_BASE_URL}${endpoint}`;
|
|
7
13
|
const options = {
|
|
8
14
|
method,
|
|
9
15
|
headers: {
|
|
10
16
|
'Content-Type': 'application/json',
|
|
17
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
11
18
|
},
|
|
12
19
|
};
|
|
13
20
|
if (body) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ErrorRateResult } from './error-tracking.js';
|
|
2
|
+
export interface DashboardMetrics {
|
|
3
|
+
timeRange: string;
|
|
4
|
+
errorMetrics: ErrorRateResult;
|
|
5
|
+
alerts: Alert[];
|
|
6
|
+
recommendations: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface Alert {
|
|
9
|
+
level: 'info' | 'warning' | 'critical';
|
|
10
|
+
message: string;
|
|
11
|
+
metric: string;
|
|
12
|
+
value: number;
|
|
13
|
+
threshold: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get dashboard metrics for a time range
|
|
17
|
+
*/
|
|
18
|
+
export declare function getDashboardMetrics(startTime: string, endTime: string): Promise<DashboardMetrics>;
|
|
19
|
+
/**
|
|
20
|
+
* Get error metrics for last 24 hours
|
|
21
|
+
*/
|
|
22
|
+
export declare function getLast24Hours(): Promise<DashboardMetrics>;
|
|
23
|
+
/**
|
|
24
|
+
* Get error metrics for last 7 days
|
|
25
|
+
*/
|
|
26
|
+
export declare function getLast7Days(): Promise<DashboardMetrics>;
|
|
27
|
+
/**
|
|
28
|
+
* Print dashboard to console
|
|
29
|
+
*/
|
|
30
|
+
export declare function printDashboard(metrics: DashboardMetrics): void;
|
|
31
|
+
/**
|
|
32
|
+
* CLI command: npm run codedna:monitor
|
|
33
|
+
*/
|
|
34
|
+
export declare function runMonitoring(): Promise<void>;
|