fraim 2.0.179 → 2.0.182

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 (64) hide show
  1. package/dist/src/ai-hub/desktop-main.js +2 -2
  2. package/dist/src/api/admin/payments.js +33 -0
  3. package/dist/src/api/admin/sales-leads.js +21 -0
  4. package/dist/src/api/payment/create-session.js +338 -0
  5. package/dist/src/api/payment/dashboard-link.js +149 -0
  6. package/dist/src/api/payment/session-details.js +31 -0
  7. package/dist/src/api/payment/webhook.js +587 -0
  8. package/dist/src/api/personas/me.js +29 -0
  9. package/dist/src/api/pricing/get-config.js +25 -0
  10. package/dist/src/api/sales/contact.js +44 -0
  11. package/dist/src/cli/commands/add-ide.js +9 -2
  12. package/dist/src/cli/commands/setup.js +14 -44
  13. package/dist/src/cli/distribution/marketplace-bundles.js +5 -1
  14. package/dist/src/cli/setup/ide-detector.js +7 -2
  15. package/dist/src/core/config-loader.js +10 -8
  16. package/dist/src/core/types.js +2 -1
  17. package/dist/src/db/payment-repository.js +61 -0
  18. package/dist/src/fraim/config-loader.js +11 -0
  19. package/dist/src/fraim/db-service.js +2387 -0
  20. package/dist/src/fraim/issues.js +152 -0
  21. package/dist/src/fraim/template-processor.js +184 -0
  22. package/dist/src/fraim/utils/request-utils.js +23 -0
  23. package/dist/src/middleware/auth.js +266 -0
  24. package/dist/src/middleware/cors-config.js +111 -0
  25. package/dist/src/middleware/logger.js +116 -0
  26. package/dist/src/middleware/rate-limit.js +110 -0
  27. package/dist/src/middleware/reject-query-api-key.js +45 -0
  28. package/dist/src/middleware/security-headers.js +41 -0
  29. package/dist/src/middleware/telemetry.js +134 -0
  30. package/dist/src/models/payment.js +2 -0
  31. package/dist/src/routes/analytics.js +1447 -0
  32. package/dist/src/routes/app-routes.js +32 -0
  33. package/dist/src/routes/auth-routes.js +505 -0
  34. package/dist/src/routes/oauth-routes.js +325 -0
  35. package/dist/src/routes/payment-routes.js +186 -0
  36. package/dist/src/routes/persona-catalog-routes.js +84 -0
  37. package/dist/src/services/admin-service.js +229 -0
  38. package/dist/src/services/audit-log-persistence.js +60 -0
  39. package/dist/src/services/audit-log.js +69 -0
  40. package/dist/src/services/cookie-service.js +129 -0
  41. package/dist/src/services/dashboard-access.js +27 -0
  42. package/dist/src/services/demo-seed-service.js +139 -0
  43. package/dist/src/services/email-code.js +23 -0
  44. package/dist/src/services/email-service-clean.js +782 -0
  45. package/dist/src/services/email-service.js +951 -0
  46. package/dist/src/services/installer-service.js +131 -0
  47. package/dist/src/services/mcp-oauth-store.js +33 -0
  48. package/dist/src/services/mcp-service.js +823 -0
  49. package/dist/src/services/oauth-helpers.js +127 -0
  50. package/dist/src/services/org-service.js +89 -0
  51. package/dist/src/services/persona-entitlement-service.js +288 -0
  52. package/dist/src/services/provider-service.js +215 -0
  53. package/dist/src/services/registry-service.js +628 -0
  54. package/dist/src/services/session-service.js +86 -0
  55. package/dist/src/services/trial-reminder-service.js +120 -0
  56. package/dist/src/services/usage-analytics-service.js +419 -0
  57. package/dist/src/services/workspace-identity.js +21 -0
  58. package/dist/src/types/analytics.js +2 -0
  59. package/dist/src/utils/payment-calculator.js +52 -0
  60. package/extensions/office-word/favicon.ico +0 -0
  61. package/extensions/office-word/icon-64.png +0 -0
  62. package/extensions/office-word/manifest.xml +33 -0
  63. package/extensions/office-word/taskpane.html +242 -0
  64. package/package.json +12 -2
@@ -0,0 +1,628 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RegistryService = void 0;
7
+ const fs_1 = require("fs");
8
+ const path_1 = require("path");
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const stub_generator_1 = require("../core/utils/stub-generator");
11
+ const job_parser_1 = require("../core/utils/job-parser");
12
+ const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
13
+ class RegistryService {
14
+ constructor() {
15
+ this.fileIndex = new Map();
16
+ this.registryPath = this.findRegistryPath();
17
+ try {
18
+ this.buildFileIndex();
19
+ }
20
+ catch (err) {
21
+ console.error('❌ RegistryService.buildFileIndex failed:', err?.message ?? err);
22
+ if (err?.stack)
23
+ console.error(err.stack);
24
+ throw err;
25
+ }
26
+ }
27
+ /**
28
+ * Find registry directory
29
+ * Checks multiple locations (dist/registry, registry/) and ensures robustness.
30
+ * Prioritizes source directory in development and dist/ in production.
31
+ */
32
+ findRegistryPath() {
33
+ const isProd = process.env.NODE_ENV === 'production';
34
+ // Define candidate locations
35
+ const locations = [
36
+ // Preference 1: Explicitly from process.cwd() / registry (Development preference)
37
+ (0, path_1.join)(process.cwd(), 'registry'),
38
+ // Preference 2: Under dist/ (Production preference)
39
+ (0, path_1.join)(process.cwd(), 'dist', 'registry'),
40
+ // Preference 3: Relative to this file's directory (Fallback)
41
+ // from src/services/ or dist/src/services/
42
+ (0, path_1.join)(__dirname, '..', '..', 'registry'),
43
+ (0, path_1.join)(__dirname, '..', '..', '..', 'registry')
44
+ ];
45
+ // If we're in production, try dist/ first
46
+ if (isProd) {
47
+ locations.unshift((0, path_1.join)(process.cwd(), 'dist', 'registry'));
48
+ }
49
+ for (const loc of locations) {
50
+ if ((0, fs_1.existsSync)(loc)) {
51
+ // Validation: A valid registry should have some expected subfolders
52
+ if ((0, fs_1.existsSync)((0, path_1.join)(loc, 'jobs')) || (0, fs_1.existsSync)((0, path_1.join)(loc, 'rules')) || (0, fs_1.existsSync)((0, path_1.join)(loc, 'stubs'))) {
53
+ return loc;
54
+ }
55
+ }
56
+ }
57
+ // Recursive search: Walk up from __dirname to find a valid 'registry'
58
+ let currentDir = __dirname;
59
+ while (currentDir !== (0, path_1.resolve)(currentDir, '..')) {
60
+ const candidate = (0, path_1.join)(currentDir, 'registry');
61
+ if ((0, fs_1.existsSync)(candidate) && ((0, fs_1.existsSync)((0, path_1.join)(candidate, 'jobs')) || (0, fs_1.existsSync)((0, path_1.join)(candidate, 'rules')))) {
62
+ return candidate;
63
+ }
64
+ const distCandidate = (0, path_1.join)(currentDir, 'dist', 'registry');
65
+ if ((0, fs_1.existsSync)(distCandidate) && (0, fs_1.existsSync)((0, path_1.join)(distCandidate, 'jobs'))) {
66
+ return distCandidate;
67
+ }
68
+ currentDir = (0, path_1.resolve)(currentDir, '..');
69
+ }
70
+ console.warn(`⚠️ RegistryService: registry directory not found in any standard location. Checked: ${locations.join(', ')}`);
71
+ // Final fallback: try to find something in cwd
72
+ return (0, path_1.join)(process.cwd(), 'registry');
73
+ }
74
+ /**
75
+ * Build an index of all files in registry directory
76
+ */
77
+ buildFileIndex() {
78
+ this.fileIndex.clear();
79
+ // 1. Index the main registry
80
+ if ((0, fs_1.existsSync)(this.registryPath)) {
81
+ console.log(`🔍 RegistryService: Indexing ${this.registryPath}...`);
82
+ this.indexDirectory(this.registryPath);
83
+ console.log(`✅ RegistryService: Indexed ${this.fileIndex.size} files.`);
84
+ }
85
+ else {
86
+ console.warn(`⚠️ RegistryService: registryPath does not exist: ${this.registryPath}`);
87
+ }
88
+ }
89
+ /**
90
+ * Index files in a directory
91
+ */
92
+ indexDirectory(dir, basePath = '') {
93
+ const indexFile = (dirPath, relativeBase = '') => {
94
+ let entries;
95
+ try {
96
+ entries = (0, fs_1.readdirSync)(dirPath, { withFileTypes: true });
97
+ }
98
+ catch (err) {
99
+ console.error(`❌ readdirSync failed for ${dirPath}:`, err?.message ?? err);
100
+ throw err;
101
+ }
102
+ for (const entry of entries) {
103
+ const fullPath = (0, path_1.join)(dirPath, entry.name);
104
+ // Normalize path separators to forward slashes for internal index
105
+ const relativePath = relativeBase ? (0, path_1.join)(relativeBase, entry.name) : entry.name;
106
+ const normalizedPath = relativePath.split(/[\\/]/).join('/');
107
+ let isFileEntry = entry.isFile();
108
+ const isDirectoryEntry = entry.isDirectory();
109
+ // On some environments (e.g., OneDrive/workspace virtualization),
110
+ // real files are surfaced as symlinks. Treat symlinked files as files.
111
+ if (!isFileEntry && !isDirectoryEntry && entry.isSymbolicLink()) {
112
+ try {
113
+ isFileEntry = (0, fs_1.statSync)(fullPath).isFile();
114
+ }
115
+ catch {
116
+ // Ignore broken symlinks
117
+ continue;
118
+ }
119
+ }
120
+ if (isDirectoryEntry) {
121
+ // Skip internal/junk directories
122
+ const isJunkDir = entry.name.includes('Issue ') ||
123
+ entry.name.includes('Master') ||
124
+ entry.name === 'dist' ||
125
+ entry.name === 'node_modules' ||
126
+ entry.name === '.git' ||
127
+ entry.name === '.github' ||
128
+ entry.name === 'github' ||
129
+ entry.name.startsWith('stubs.tmp-') ||
130
+ entry.name.endsWith('-phases');
131
+ if (isJunkDir) {
132
+ continue;
133
+ }
134
+ indexFile(fullPath, normalizedPath);
135
+ }
136
+ else if (isFileEntry) {
137
+ const extension = (0, path_1.extname)(entry.name).toLowerCase();
138
+ let type = 'other';
139
+ let category;
140
+ // Determine type and category based on path and extension
141
+ if (normalizedPath.includes('.github/')) {
142
+ // Skip github workflows
143
+ continue;
144
+ }
145
+ const isJob = normalizedPath.includes('jobs/');
146
+ const isSkill = normalizedPath.includes('skills/');
147
+ if (isJob) {
148
+ type = 'job';
149
+ const parts = normalizedPath.split('/');
150
+ const jobIdx = parts.lastIndexOf('jobs');
151
+ if (jobIdx !== -1 && parts[jobIdx + 1]) {
152
+ category = parts[jobIdx + 1];
153
+ if ((category === 'ai-employee' || category === 'ai-manager') && parts[jobIdx + 2]) {
154
+ category = parts[jobIdx + 2];
155
+ }
156
+ if (category.endsWith('.md')) {
157
+ category = undefined;
158
+ }
159
+ }
160
+ }
161
+ else if (isSkill) {
162
+ type = 'skill';
163
+ }
164
+ else if (normalizedPath.includes('rules/')) {
165
+ type = 'rule';
166
+ }
167
+ else if (normalizedPath.includes('templates/')) {
168
+ type = 'template';
169
+ }
170
+ else if (normalizedPath.includes('scripts/')) {
171
+ type = 'script';
172
+ }
173
+ else if (normalizedPath.includes('ai-manager-rules/') || normalizedPath.endsWith('-phases')) {
174
+ type = 'phase';
175
+ }
176
+ // Stubs are identified by path or content
177
+ const metadata = {
178
+ path: normalizedPath,
179
+ name: (0, path_1.basename)(entry.name, extension),
180
+ type,
181
+ category,
182
+ keywords: this.extractKeywords(entry.name, normalizedPath),
183
+ fullPath,
184
+ isStub: normalizedPath.includes('stubs/')
185
+ };
186
+ this.fileIndex.set(normalizedPath, metadata);
187
+ }
188
+ }
189
+ };
190
+ indexFile(dir, basePath);
191
+ }
192
+ extractKeywords(filename, path) {
193
+ const tokens = [...filename.split(/[-_.\s]/), ...path.split('/')];
194
+ return Array.from(new Set(tokens))
195
+ .map(t => t.toLowerCase())
196
+ .filter(t => t.length > 2 && !['fraim', 'rule', 'template', 'md', 'ts', 'js'].includes(t));
197
+ }
198
+ collectMarkdownRelativePaths(rootDir, currentRelativeDir = '') {
199
+ if (!(0, fs_1.existsSync)(rootDir)) {
200
+ return [];
201
+ }
202
+ const results = [];
203
+ const entries = (0, fs_1.readdirSync)(rootDir, { withFileTypes: true });
204
+ for (const entry of entries) {
205
+ const relativePath = currentRelativeDir
206
+ ? (0, path_1.join)(currentRelativeDir, entry.name)
207
+ : entry.name;
208
+ const normalizedRelativePath = relativePath.split(/[\\/]/).join('/');
209
+ const fullPath = (0, path_1.join)(rootDir, entry.name);
210
+ if (entry.isDirectory()) {
211
+ results.push(...this.collectMarkdownRelativePaths(fullPath, normalizedRelativePath));
212
+ continue;
213
+ }
214
+ if (entry.isFile() && entry.name.endsWith('.md')) {
215
+ results.push(normalizedRelativePath);
216
+ }
217
+ }
218
+ return results;
219
+ }
220
+ getAvailableJobs() {
221
+ return Array.from(this.fileIndex.values())
222
+ .filter(m => m.type === 'job' && !m.path.includes('stubs/'))
223
+ .map(m => m.name);
224
+ }
225
+ getRegistryPath() {
226
+ return this.registryPath;
227
+ }
228
+ getFileIndex() {
229
+ return this.fileIndex;
230
+ }
231
+ findFileByPath(path) {
232
+ // console.log(`[RegistryService] findFileByPath: ${path}`);
233
+ return this.findFileByPathInternal(path, new Set());
234
+ }
235
+ choosePreferredMatch(matches) {
236
+ if (matches.length === 0) {
237
+ return undefined;
238
+ }
239
+ return matches.find((match) => !match.isStub) || matches[0];
240
+ }
241
+ findFileByPathInternal(path, visited) {
242
+ const normalized = path.replace(/^\/+/, '').split(/[\\/]/).join('/');
243
+ if (visited.has(normalized))
244
+ return undefined;
245
+ visited.add(normalized);
246
+ const literal = this.fileIndex.get(normalized);
247
+ if (literal)
248
+ return literal;
249
+ if (!normalized.endsWith('.md')) {
250
+ const withMd = this.findFileByPathInternal(`${normalized}.md`, visited);
251
+ if (withMd)
252
+ return withMd;
253
+ }
254
+ // Role-based fallbacks for global registry
255
+ if (normalized.startsWith('skills/')) {
256
+ const sub = normalized.substring('skills/'.length);
257
+ if (!sub.startsWith('ai-employee/') && !sub.startsWith('ai-manager/')) {
258
+ return this.findFileByPathInternal(`skills/ai-employee/${sub}`, visited);
259
+ }
260
+ }
261
+ if (normalized.startsWith('rules/')) {
262
+ const sub = normalized.substring('rules/'.length);
263
+ if (!sub.startsWith('ai-employee/') && !sub.startsWith('ai-manager/')) {
264
+ return this.findFileByPathInternal(`rules/ai-employee/${sub}`, visited);
265
+ }
266
+ }
267
+ if (normalized.startsWith('jobs/')) {
268
+ const sub = normalized.substring('jobs/'.length);
269
+ if (!sub.startsWith('ai-employee/') && !sub.startsWith('ai-manager/')) {
270
+ return this.findFileByPathInternal(`jobs/ai-employee/${sub}`, visited) ||
271
+ this.findFileByPathInternal(`jobs/ai-manager/${sub}`, visited) ||
272
+ this.findFileByPathInternal(sub, visited);
273
+ }
274
+ }
275
+ if (normalized.startsWith('ai-employee/') || normalized.startsWith('ai-manager/')) {
276
+ const isTypePrefixed = normalized.includes('/jobs/') ||
277
+ normalized.includes('/skills/') || normalized.includes('/rules/');
278
+ if (!isTypePrefixed) {
279
+ return this.findFileByPathInternal(`jobs/${normalized}`, visited);
280
+ }
281
+ }
282
+ // Final fallback: suffix match for deeply nested leaf filenames
283
+ const suffixMatches = [];
284
+ for (const [indexPath, entry] of this.fileIndex.entries()) {
285
+ if (indexPath.endsWith(`/${normalized}`) || indexPath === normalized) {
286
+ suffixMatches.push(entry);
287
+ }
288
+ }
289
+ const preferredSuffixMatch = this.choosePreferredMatch(suffixMatches);
290
+ if (preferredSuffixMatch) {
291
+ return preferredSuffixMatch;
292
+ }
293
+ // Very desperate fallback: if it's just a filename, try matching it anywhere
294
+ if (!normalized.includes('/')) {
295
+ const filenameMatches = [];
296
+ for (const [indexPath, entry] of this.fileIndex.entries()) {
297
+ if (indexPath.endsWith(`/${normalized}`)) {
298
+ filenameMatches.push(entry);
299
+ }
300
+ }
301
+ return this.choosePreferredMatch(filenameMatches);
302
+ }
303
+ return undefined;
304
+ }
305
+ getMetadata(path) {
306
+ return this.fileIndex.get(path);
307
+ }
308
+ getSyncBundle() {
309
+ const files = [];
310
+ const allFiles = Array.from(this.fileIndex.values());
311
+ // 1. Add job STUBS (not full jobs) from registry/stubs/jobs
312
+ const jobStubs = allFiles.filter(f => f.type === 'job' && f.path.includes('stubs/jobs/'));
313
+ let jobStubCount = 0;
314
+ for (const stub of jobStubs) {
315
+ try {
316
+ const content = (0, fs_1.readFileSync)(stub.fullPath, 'utf8');
317
+ const relativePath = stub.path.replace('stubs/jobs/', '');
318
+ files.push({
319
+ path: relativePath,
320
+ content,
321
+ type: 'job'
322
+ });
323
+ jobStubCount++;
324
+ }
325
+ catch (error) {
326
+ console.error(`⚠️ Failed to read job stub ${stub.path}:`, error);
327
+ }
328
+ }
329
+ // 2b. Fallback: synthesize job stubs
330
+ const existingJobPaths = new Set(files.filter(f => f.type === 'job').map(f => f.path));
331
+ const jobs = allFiles.filter(f => f.type === 'job' && !f.path.includes('stubs/') && f.path.startsWith('jobs/') && f.path.endsWith('.md'));
332
+ if (jobStubCount < jobs.length) {
333
+ for (const job of jobs) {
334
+ try {
335
+ const relativePath = job.path.replace('jobs/', '');
336
+ if (existingJobPaths.has(relativePath)) {
337
+ continue;
338
+ }
339
+ const content = (0, fs_1.readFileSync)(job.fullPath, 'utf8');
340
+ const { intent, outcome, steps } = (0, stub_generator_1.parseRegistryJob)(content);
341
+ const stub = (0, stub_generator_1.generateJobStub)(job.name, relativePath, intent, outcome, steps);
342
+ files.push({
343
+ path: relativePath,
344
+ content: stub,
345
+ type: 'job'
346
+ });
347
+ existingJobPaths.add(relativePath);
348
+ }
349
+ catch (error) {
350
+ console.error(`⚠️ Failed to synthesize job stub ${job.path}:`, error);
351
+ }
352
+ }
353
+ }
354
+ // 2c. Add skill STUBS from registry/stubs/skills
355
+ const skillStubs = allFiles.filter(f => f.type === 'skill' && f.path.includes('stubs/skills/'));
356
+ const existingSkillPaths = new Set(files.filter(f => f.type === 'skill').map(f => f.path));
357
+ let skillStubCount = 0;
358
+ for (const stub of skillStubs) {
359
+ try {
360
+ const content = (0, fs_1.readFileSync)(stub.fullPath, 'utf8');
361
+ const relativePath = stub.path.replace('stubs/skills/', '');
362
+ if (existingSkillPaths.has(relativePath)) {
363
+ continue;
364
+ }
365
+ files.push({
366
+ path: relativePath,
367
+ content,
368
+ type: 'skill'
369
+ });
370
+ existingSkillPaths.add(relativePath);
371
+ skillStubCount++;
372
+ }
373
+ catch (error) {
374
+ console.error(`⚠️ Failed to read skill stub ${stub.path}:`, error);
375
+ }
376
+ }
377
+ // 2d. Fallback: synthesize skill stubs
378
+ const skills = allFiles.filter(f => f.type === 'skill' && !f.path.includes('stubs/') && f.path.startsWith('skills/') && f.path.endsWith('.md'));
379
+ if (skillStubCount < skills.length) {
380
+ for (const skill of skills) {
381
+ try {
382
+ const relativePath = skill.path.replace('skills/', '');
383
+ if (existingSkillPaths.has(relativePath)) {
384
+ continue;
385
+ }
386
+ const content = (0, fs_1.readFileSync)(skill.fullPath, 'utf8');
387
+ const { skillInput, skillOutput } = (0, stub_generator_1.parseRegistrySkill)(content);
388
+ const stub = (0, stub_generator_1.generateSkillStub)(skill.name, relativePath, skillInput, skillOutput);
389
+ files.push({
390
+ path: relativePath,
391
+ content: stub,
392
+ type: 'skill'
393
+ });
394
+ existingSkillPaths.add(relativePath);
395
+ }
396
+ catch (error) {
397
+ console.error(`⚠️ Failed to synthesize skill stub ${skill.path}:`, error);
398
+ }
399
+ }
400
+ }
401
+ // 2e. Add rule STUBS from registry/stubs/rules
402
+ const ruleStubs = allFiles.filter(f => f.type === 'rule' && f.path.includes('stubs/rules/'));
403
+ const existingRulePaths = new Set(files.filter(f => f.type === 'rule').map(f => f.path));
404
+ let ruleStubCount = 0;
405
+ for (const stub of ruleStubs) {
406
+ try {
407
+ const content = (0, fs_1.readFileSync)(stub.fullPath, 'utf8');
408
+ const relativePath = stub.path.replace('stubs/rules/', '');
409
+ if (existingRulePaths.has(relativePath)) {
410
+ continue;
411
+ }
412
+ files.push({
413
+ path: relativePath,
414
+ content,
415
+ type: 'rule'
416
+ });
417
+ existingRulePaths.add(relativePath);
418
+ ruleStubCount++;
419
+ }
420
+ catch (error) {
421
+ console.error(`⚠️ Failed to read rule stub ${stub.path}:`, error);
422
+ }
423
+ }
424
+ // 2f. Fallback: synthesize rule stubs
425
+ const rules = allFiles.filter(f => f.type === 'rule' && !f.path.includes('stubs/') && f.path.startsWith('rules/') && f.path.endsWith('.md'));
426
+ if (ruleStubCount < rules.length) {
427
+ for (const rule of rules) {
428
+ try {
429
+ const relativePath = rule.path.replace('rules/', '');
430
+ if (existingRulePaths.has(relativePath)) {
431
+ continue;
432
+ }
433
+ const content = (0, fs_1.readFileSync)(rule.fullPath, 'utf8');
434
+ const { intent } = (0, stub_generator_1.parseRegistryRule)(content);
435
+ const stub = (0, stub_generator_1.generateRuleStub)(rule.name, relativePath, intent);
436
+ files.push({
437
+ path: relativePath,
438
+ content: stub,
439
+ type: 'rule'
440
+ });
441
+ existingRulePaths.add(relativePath);
442
+ }
443
+ catch (error) {
444
+ console.error(`⚠️ Failed to synthesize rule stub ${rule.path}:`, error);
445
+ }
446
+ }
447
+ }
448
+ // 3. Add scripts
449
+ const scripts = allFiles.filter(f => f.type === 'script');
450
+ for (const script of scripts) {
451
+ const ext = (0, path_1.extname)(script.path);
452
+ // Only skip .js/.js.map files if a corresponding .ts file exists
453
+ // Allow all other file types (.css, .sh, .py, etc.) to pass through
454
+ if (ext === '.js' || ext === '.js.map') {
455
+ const tsPath = script.path.replace(/\.js(\.map)?$/, '.ts');
456
+ if (Array.from(this.fileIndex.values()).some(f => f.path === tsPath)) {
457
+ continue;
458
+ }
459
+ }
460
+ try {
461
+ const content = (0, fs_1.readFileSync)(script.fullPath, 'utf8');
462
+ const scriptRelativePath = script.path.startsWith('scripts/')
463
+ ? script.path.substring('scripts/'.length)
464
+ : script.name;
465
+ files.push({
466
+ path: scriptRelativePath,
467
+ content,
468
+ type: 'script'
469
+ });
470
+ }
471
+ catch (error) {
472
+ console.error(`⚠️ Failed to read script ${script.path}:`, error);
473
+ }
474
+ }
475
+ // 5. Add docs
476
+ const docsDir = (0, path_1.join)(this.registryPath, 'docs');
477
+ if ((0, fs_1.existsSync)(docsDir)) {
478
+ try {
479
+ const docsEntries = (0, fs_1.readdirSync)(docsDir, { withFileTypes: true });
480
+ for (const entry of docsEntries) {
481
+ if (entry.isFile() && entry.name.endsWith('.md')) {
482
+ const fullPath = (0, path_1.join)(docsDir, entry.name);
483
+ try {
484
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf8');
485
+ files.push({
486
+ path: entry.name,
487
+ content,
488
+ type: 'docs'
489
+ });
490
+ }
491
+ catch (error) {
492
+ console.error(`⚠️ Failed to read docs file ${entry.name}:`, error);
493
+ }
494
+ }
495
+ }
496
+ }
497
+ catch (err) {
498
+ console.warn(`⚠️ Failed to read docs directory: ${docsDir}`);
499
+ }
500
+ }
501
+ return files;
502
+ }
503
+ getGitHubWorkflowBundle() {
504
+ const workflowsDir = (0, path_1.join)(this.registryPath, 'github', 'workflows');
505
+ if (!(0, fs_1.existsSync)(workflowsDir)) {
506
+ return [];
507
+ }
508
+ const files = [];
509
+ const entries = (0, fs_1.readdirSync)(workflowsDir, { withFileTypes: true });
510
+ for (const entry of entries) {
511
+ if (!entry.isFile() || !entry.name.endsWith('.yml')) {
512
+ continue;
513
+ }
514
+ const fullPath = (0, path_1.join)(workflowsDir, entry.name);
515
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf8');
516
+ files.push({
517
+ path: entry.name,
518
+ content,
519
+ type: 'github-workflow',
520
+ digest: `sha256:${crypto_1.default.createHash('sha256').update(content).digest('hex')}`
521
+ });
522
+ }
523
+ return files.sort((a, b) => a.path.localeCompare(b.path));
524
+ }
525
+ getPersonalizedTopology() {
526
+ const personalizedRoot = (0, project_fraim_paths_1.getWorkspaceFraimPath)(process.cwd(), 'personalized-employee');
527
+ if (!(0, fs_1.existsSync)(personalizedRoot)) {
528
+ return { nodes: [], edges: [] };
529
+ }
530
+ const nodes = [];
531
+ const edges = [];
532
+ const seenNodeIds = new Set();
533
+ const normalize = (value) => value.split(/[\\/]/).join('/');
534
+ const addNode = (node) => {
535
+ if (seenNodeIds.has(node.id))
536
+ return;
537
+ seenNodeIds.add(node.id);
538
+ nodes.push(node);
539
+ };
540
+ const skillsRoot = (0, path_1.join)(personalizedRoot, 'skills');
541
+ if ((0, fs_1.existsSync)(skillsRoot)) {
542
+ for (const relativePath of this.collectMarkdownRelativePaths(skillsRoot)) {
543
+ const normalizedRelativePath = normalize(relativePath);
544
+ const skillId = normalizedRelativePath.replace(/\.md$/, '');
545
+ const category = skillId.split('/')[0] || 'uncategorized';
546
+ const baselinePath = (0, path_1.join)(this.registryPath, 'skills', normalizedRelativePath);
547
+ addNode({
548
+ id: skillId,
549
+ label: (0, path_1.basename)(skillId),
550
+ type: 'skill',
551
+ category,
552
+ group: skillId.includes('/') ? skillId.slice(0, skillId.lastIndexOf('/')) : skillId,
553
+ role: null,
554
+ path: (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`personalized-employee/skills/${normalizedRelativePath}`),
555
+ shadowing: (0, fs_1.existsSync)(baselinePath)
556
+ });
557
+ }
558
+ }
559
+ const jobsRoot = (0, path_1.join)(personalizedRoot, 'jobs');
560
+ if ((0, fs_1.existsSync)(jobsRoot)) {
561
+ for (const relativePath of this.collectMarkdownRelativePaths(jobsRoot)) {
562
+ const normalizedRelativePath = normalize(relativePath);
563
+ const jobPathWithoutExt = normalizedRelativePath.replace(/\.md$/, '');
564
+ const parts = jobPathWithoutExt.split('/');
565
+ const category = parts[0] || 'uncategorized';
566
+ const jobId = `job:${jobPathWithoutExt}`;
567
+ const baselinePath = (0, path_1.join)(this.registryPath, 'jobs', normalizedRelativePath);
568
+ const fullPath = (0, path_1.join)(jobsRoot, normalizedRelativePath);
569
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf8');
570
+ addNode({
571
+ id: jobId,
572
+ label: (0, path_1.basename)(jobPathWithoutExt),
573
+ type: 'job',
574
+ category,
575
+ group: parts.slice(0, -1).join('/') || category,
576
+ role: category === 'ai-manager' ? 'ai-manager' : 'ai-employee',
577
+ path: (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`personalized-employee/jobs/${normalizedRelativePath}`),
578
+ shadowing: (0, fs_1.existsSync)(baselinePath)
579
+ });
580
+ const includes = [...content.matchAll(/\{\{include:skills\/([^}]+)\.md\}\}/g)];
581
+ const seenSkills = new Set();
582
+ for (const match of includes) {
583
+ const skillId = match[1];
584
+ if (!skillId || seenSkills.has(skillId)) {
585
+ continue;
586
+ }
587
+ seenSkills.add(skillId);
588
+ edges.push({ source: jobId, target: skillId });
589
+ }
590
+ }
591
+ }
592
+ return { nodes, edges };
593
+ }
594
+ async getFile(path) {
595
+ const metadata = this.findFileByPath(path);
596
+ if (!metadata)
597
+ return null;
598
+ try {
599
+ return (0, fs_1.readFileSync)(metadata.fullPath, 'utf8');
600
+ }
601
+ catch (e) {
602
+ return null;
603
+ }
604
+ }
605
+ async getJob(name) {
606
+ const allMatches = Array.from(this.fileIndex.values()).filter(f => f.name === name && f.type === 'job');
607
+ if (allMatches.length === 0)
608
+ return null;
609
+ const metadata = allMatches.find(f => !f.isStub) || allMatches[0];
610
+ const def = job_parser_1.JobParser.parse(metadata.fullPath);
611
+ if (def && !def.metadata.type) {
612
+ def.metadata.type = 'job';
613
+ }
614
+ return def;
615
+ }
616
+ async listItems(type) {
617
+ return Array.from(this.fileIndex.values())
618
+ .filter(f => !f.isStub && f.type === 'job')
619
+ .map(f => ({
620
+ name: f.name,
621
+ path: f.path,
622
+ type: f.type,
623
+ category: f.category,
624
+ description: job_parser_1.JobParser.extractDescription(f.fullPath)
625
+ }));
626
+ }
627
+ }
628
+ exports.RegistryService = RegistryService;