@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.
- package/README.md +68 -0
- package/index.js +36 -0
- package/package.json +40 -0
- package/schemas/persona_schema.yaml +295 -0
- package/schemas/personality.schema.json +228 -0
- package/scripts/add-personality.js +220 -0
- package/scripts/build.js +345 -0
- package/scripts/enhance-scientists.js +215 -0
- package/scripts/enhance_all_personalities.py +926 -0
- package/scripts/migrate.js +384 -0
- package/scripts/parallel-validate.js +322 -0
- package/scripts/split-personalities.js +95 -0
- package/scripts/validate.js +306 -0
- package/tools/unix_tools.yaml +349 -0
|
@@ -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 };
|