@hanzo/persona 1.0.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.
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // Load the mega personalities file
7
+ const megaFile = path.join(__dirname, '../personalities/mega-personalities.json');
8
+ const data = JSON.parse(fs.readFileSync(megaFile, 'utf8'));
9
+
10
+ // Create profiles directory
11
+ const profilesDir = path.join(__dirname, '../profiles');
12
+ if (!fs.existsSync(profilesDir)) {
13
+ fs.mkdirSync(profilesDir, { recursive: true });
14
+ }
15
+
16
+ // Standard template for each personality
17
+ function createPersonalityFile(person) {
18
+ // Clean up the ID to make a valid filename
19
+ const filename = person.id.replace(/[^a-z0-9_-]/gi, '_').toLowerCase();
20
+
21
+ const personalityData = {
22
+ id: person.id,
23
+ name: person.name,
24
+ category: person.category,
25
+ ocean: person.ocean,
26
+ description: person.description || null,
27
+ philosophy: person.philosophy || null,
28
+ tools: person.tools || null,
29
+ programmer: person.programmer || null
30
+ };
31
+
32
+ // Remove null fields
33
+ Object.keys(personalityData).forEach(key => {
34
+ if (personalityData[key] === null) {
35
+ delete personalityData[key];
36
+ }
37
+ });
38
+
39
+ return { filename, data: personalityData };
40
+ }
41
+
42
+ // Process all personalities
43
+ let index = [];
44
+ let categories = {};
45
+
46
+ data.personalities.forEach(person => {
47
+ const { filename, data: personalityData } = createPersonalityFile(person);
48
+ const filepath = path.join(profilesDir, `${filename}.json`);
49
+
50
+ // Write individual file
51
+ fs.writeFileSync(filepath, JSON.stringify(personalityData, null, 2));
52
+
53
+ // Add to index
54
+ index.push({
55
+ id: person.id,
56
+ name: person.name,
57
+ category: person.category,
58
+ file: `${filename}.json`
59
+ });
60
+
61
+ // Track categories
62
+ if (!categories[person.category]) {
63
+ categories[person.category] = [];
64
+ }
65
+ categories[person.category].push({
66
+ id: person.id,
67
+ name: person.name,
68
+ file: `${filename}.json`
69
+ });
70
+
71
+ console.log(`Created: ${filename}.json`);
72
+ });
73
+
74
+ // Create index.json
75
+ const indexData = {
76
+ total: index.length,
77
+ categories: Object.keys(categories).sort(),
78
+ personalities: index.sort((a, b) => a.name.localeCompare(b.name))
79
+ };
80
+
81
+ fs.writeFileSync(
82
+ path.join(profilesDir, 'index.json'),
83
+ JSON.stringify(indexData, null, 2)
84
+ );
85
+
86
+ // Create categories.json
87
+ fs.writeFileSync(
88
+ path.join(profilesDir, 'categories.json'),
89
+ JSON.stringify(categories, null, 2)
90
+ );
91
+
92
+ console.log(`\nāœ… Created ${index.length} personality files`);
93
+ console.log(`šŸ“ Files saved to: ${profilesDir}`);
94
+ console.log(`šŸ“‹ Index created: index.json`);
95
+ console.log(`šŸ“Š Categories: ${Object.keys(categories).join(', ')}`);
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // Configuration
7
+ const PERSONAS_DIR = path.join(__dirname, '..', 'personalities_v2');
8
+ const SCHEMA_PATH = path.join(PERSONAS_DIR, 'schemas', 'personality.schema.json');
9
+
10
+ // Color codes for terminal output
11
+ const colors = {
12
+ reset: '\x1b[0m',
13
+ red: '\x1b[31m',
14
+ green: '\x1b[32m',
15
+ yellow: '\x1b[33m',
16
+ blue: '\x1b[34m'
17
+ };
18
+
19
+ // Validation results
20
+ let totalFiles = 0;
21
+ let validFiles = 0;
22
+ let errors = [];
23
+ let warnings = [];
24
+
25
+ // Validate OCEAN scores
26
+ function validateOcean(ocean, id) {
27
+ const issues = [];
28
+
29
+ if (!ocean) {
30
+ issues.push(`Missing OCEAN scores`);
31
+ return issues;
32
+ }
33
+
34
+ const requiredFields = ['openness', 'conscientiousness', 'extraversion', 'agreeableness', 'neuroticism'];
35
+
36
+ requiredFields.forEach(field => {
37
+ if (ocean[field] === undefined) {
38
+ issues.push(`Missing OCEAN.${field}`);
39
+ } else if (typeof ocean[field] !== 'number') {
40
+ issues.push(`OCEAN.${field} must be a number (got ${typeof ocean[field]})`);
41
+ } else if (ocean[field] < 0 || ocean[field] > 100) {
42
+ issues.push(`OCEAN.${field} must be between 0-100 (got ${ocean[field]})`);
43
+ }
44
+ });
45
+
46
+ return issues;
47
+ }
48
+
49
+ // Validate a single personality file
50
+ function validateFile(filePath) {
51
+ const filename = path.basename(filePath);
52
+ const fileErrors = [];
53
+ const fileWarnings = [];
54
+
55
+ try {
56
+ const content = fs.readFileSync(filePath, 'utf8');
57
+ let data;
58
+
59
+ // Parse JSON
60
+ try {
61
+ data = JSON.parse(content);
62
+ } catch (err) {
63
+ fileErrors.push(`Invalid JSON: ${err.message}`);
64
+ return { errors: fileErrors, warnings: fileWarnings };
65
+ }
66
+
67
+ // Check required fields
68
+ const requiredFields = ['id', 'name', 'category', 'ocean', 'personality'];
69
+ requiredFields.forEach(field => {
70
+ if (!data[field]) {
71
+ fileErrors.push(`Missing required field: ${field}`);
72
+ }
73
+ });
74
+
75
+ // Validate ID
76
+ if (data.id) {
77
+ const expectedFilename = `${data.id}.json`;
78
+ if (filename !== expectedFilename) {
79
+ fileErrors.push(`Filename mismatch: expected ${expectedFilename}, got ${filename}`);
80
+ }
81
+
82
+ if (!/^[a-z0-9_-]+$/.test(data.id)) {
83
+ fileErrors.push(`Invalid ID format: ${data.id} (must be lowercase alphanumeric with - or _)`);
84
+ }
85
+ }
86
+
87
+ // Validate category
88
+ const validCategories = [
89
+ 'programmer', 'philosopher', 'scientist', 'religious', 'revolutionary',
90
+ 'writer', 'artist', 'musician', 'filmmaker', 'comedian', 'architect',
91
+ 'athlete', 'explorer', 'activist', 'tech_leader', 'leader', 'pioneer',
92
+ 'special', 'systems', 'master', 'language-creator', 'historian',
93
+ 'gaming', 'blockchain', 'media', 'poet', 'statesman', 'mathematician',
94
+ 'composer'
95
+ ];
96
+
97
+ if (data.category && !validCategories.includes(data.category)) {
98
+ fileWarnings.push(`Unknown category: ${data.category}`);
99
+ }
100
+
101
+ // Validate OCEAN scores
102
+ const oceanIssues = validateOcean(data.ocean, data.id);
103
+ fileErrors.push(...oceanIssues);
104
+
105
+ // Validate personality object
106
+ if (data.personality) {
107
+ if (!data.personality.summary) {
108
+ fileWarnings.push(`Missing personality.summary`);
109
+ }
110
+ if (!data.personality.philosophy) {
111
+ fileWarnings.push(`Missing personality.philosophy`);
112
+ }
113
+ }
114
+
115
+ // Check for recommended fields
116
+ if (!data.tags || data.tags.length === 0) {
117
+ fileWarnings.push(`No tags defined`);
118
+ }
119
+
120
+ if (!data.quotes || data.quotes.length === 0) {
121
+ fileWarnings.push(`No quotes defined`);
122
+ }
123
+
124
+ // For programmers, check technical info
125
+ if ((data.category === 'programmer' || data.category === 'tech_leader') && !data.technical) {
126
+ fileWarnings.push(`Programmer/tech_leader without technical info`);
127
+ }
128
+
129
+ // Check for duplicate fields or typos
130
+ const knownFields = [
131
+ '$schema', 'id', 'name', 'fullName', 'category', 'subcategory', 'tags',
132
+ 'metadata', 'ocean', 'personality', 'technical', 'quotes', 'behavioral'
133
+ ];
134
+
135
+ Object.keys(data).forEach(key => {
136
+ if (!knownFields.includes(key)) {
137
+ fileWarnings.push(`Unknown field: ${key}`);
138
+ }
139
+ });
140
+
141
+ } catch (err) {
142
+ fileErrors.push(`Error reading file: ${err.message}`);
143
+ }
144
+
145
+ return { errors: fileErrors, warnings: fileWarnings };
146
+ }
147
+
148
+ // Validate all files in a directory
149
+ function validateDirectory(dirPath, dirName) {
150
+ if (!fs.existsSync(dirPath)) {
151
+ return;
152
+ }
153
+
154
+ const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.json'));
155
+
156
+ console.log(`\n${colors.blue}Validating ${dirName}/${colors.reset} (${files.length} files)`);
157
+
158
+ files.forEach(file => {
159
+ totalFiles++;
160
+ const filePath = path.join(dirPath, file);
161
+ const result = validateFile(filePath);
162
+
163
+ if (result.errors.length === 0 && result.warnings.length === 0) {
164
+ validFiles++;
165
+ process.stdout.write(colors.green + '.' + colors.reset);
166
+ } else if (result.errors.length > 0) {
167
+ process.stdout.write(colors.red + 'E' + colors.reset);
168
+ errors.push({
169
+ file: `${dirName}/${file}`,
170
+ errors: result.errors
171
+ });
172
+ if (result.warnings.length > 0) {
173
+ warnings.push({
174
+ file: `${dirName}/${file}`,
175
+ warnings: result.warnings
176
+ });
177
+ }
178
+ } else {
179
+ validFiles++;
180
+ process.stdout.write(colors.yellow + 'W' + colors.reset);
181
+ warnings.push({
182
+ file: `${dirName}/${file}`,
183
+ warnings: result.warnings
184
+ });
185
+ }
186
+ });
187
+ }
188
+
189
+ // Check for duplicate IDs
190
+ function checkDuplicates() {
191
+ console.log('\n\n' + colors.blue + 'Checking for duplicate IDs...' + colors.reset);
192
+
193
+ const idMap = new Map();
194
+ const duplicates = [];
195
+
196
+ // Scan all directories
197
+ const dirs = fs.readdirSync(PERSONAS_DIR)
198
+ .filter(d => !['dist', 'index', 'schemas', 'scripts'].includes(d))
199
+ .filter(d => fs.statSync(path.join(PERSONAS_DIR, d)).isDirectory());
200
+
201
+ dirs.forEach(dir => {
202
+ const dirPath = path.join(PERSONAS_DIR, dir);
203
+ const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.json'));
204
+
205
+ files.forEach(file => {
206
+ try {
207
+ const content = JSON.parse(fs.readFileSync(path.join(dirPath, file), 'utf8'));
208
+ const id = content.id;
209
+
210
+ if (idMap.has(id)) {
211
+ duplicates.push({
212
+ id,
213
+ files: [idMap.get(id), `${dir}/${file}`]
214
+ });
215
+ } else {
216
+ idMap.set(id, `${dir}/${file}`);
217
+ }
218
+ } catch (err) {
219
+ // Ignore parse errors (handled elsewhere)
220
+ }
221
+ });
222
+ });
223
+
224
+ if (duplicates.length > 0) {
225
+ console.log(colors.red + '\nāœ— Found duplicate IDs:' + colors.reset);
226
+ duplicates.forEach(dup => {
227
+ console.log(` ID "${dup.id}" appears in:`);
228
+ dup.files.forEach(f => console.log(` - ${f}`));
229
+ });
230
+ } else {
231
+ console.log(colors.green + 'āœ“ No duplicate IDs found' + colors.reset);
232
+ }
233
+
234
+ return duplicates.length === 0;
235
+ }
236
+
237
+ // Main validation function
238
+ async function validate() {
239
+ console.log(colors.blue + '=== Personality Validation ===' + colors.reset);
240
+
241
+ // Check if schema exists
242
+ if (!fs.existsSync(SCHEMA_PATH)) {
243
+ console.error(colors.red + 'Schema file not found at:' + colors.reset, SCHEMA_PATH);
244
+ process.exit(1);
245
+ }
246
+
247
+ // Validate each directory
248
+ const dirs = [
249
+ 'programmers', 'philosophers', 'scientists', 'religious',
250
+ 'revolutionaries', 'writers', 'artists', 'architects',
251
+ 'athletes', 'explorers', 'activists', 'leaders',
252
+ 'pioneers', 'special'
253
+ ];
254
+
255
+ dirs.forEach(dir => {
256
+ validateDirectory(path.join(PERSONAS_DIR, dir), dir);
257
+ });
258
+
259
+ // Check for duplicates
260
+ const noDuplicates = checkDuplicates();
261
+
262
+ // Print results
263
+ console.log('\n\n' + colors.blue + '=== Validation Results ===' + colors.reset);
264
+ console.log(`Total files: ${totalFiles}`);
265
+ console.log(`Valid files: ${colors.green}${validFiles}${colors.reset}`);
266
+ console.log(`Files with errors: ${colors.red}${errors.length}${colors.reset}`);
267
+ console.log(`Files with warnings: ${colors.yellow}${warnings.length}${colors.reset}`);
268
+
269
+ // Print errors
270
+ if (errors.length > 0) {
271
+ console.log('\n' + colors.red + '=== Errors ===' + colors.reset);
272
+ errors.forEach(err => {
273
+ console.log(`\n${err.file}:`);
274
+ err.errors.forEach(e => console.log(` āœ— ${e}`));
275
+ });
276
+ }
277
+
278
+ // Print warnings (optional)
279
+ if (warnings.length > 0 && process.argv.includes('--warnings')) {
280
+ console.log('\n' + colors.yellow + '=== Warnings ===' + colors.reset);
281
+ warnings.forEach(warn => {
282
+ console.log(`\n${warn.file}:`);
283
+ warn.warnings.forEach(w => console.log(` ⚠ ${w}`));
284
+ });
285
+ } else if (warnings.length > 0) {
286
+ console.log(`\n${colors.yellow}Run with --warnings to see ${warnings.length} warnings${colors.reset}`);
287
+ }
288
+
289
+ // Exit with error if validation failed
290
+ if (errors.length > 0 || !noDuplicates) {
291
+ console.log('\n' + colors.red + 'āœ— Validation failed' + colors.reset);
292
+ process.exit(1);
293
+ } else {
294
+ console.log('\n' + colors.green + 'āœ“ All validations passed!' + colors.reset);
295
+ }
296
+ }
297
+
298
+ // Run validation
299
+ if (require.main === module) {
300
+ validate().catch(err => {
301
+ console.error('Validation failed:', err);
302
+ process.exit(1);
303
+ });
304
+ }
305
+
306
+ module.exports = { validate, validateFile };