fraim 2.0.171 → 2.0.174

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 (32) hide show
  1. package/dist/src/ai-hub/hosts.js +227 -6
  2. package/dist/src/ai-hub/server.js +1014 -35
  3. package/dist/src/cli/commands/add-ide.js +2 -0
  4. package/dist/src/cli/commands/cleanup-artifacts.js +39 -0
  5. package/dist/src/cli/commands/init-project.js +12 -5
  6. package/dist/src/cli/commands/sync.js +74 -7
  7. package/dist/src/cli/fraim.js +2 -0
  8. package/dist/src/cli/setup/ide-detector.js +6 -0
  9. package/dist/src/cli/utils/agent-adapters.js +40 -18
  10. package/dist/src/cli/utils/fraim-gitignore.js +13 -0
  11. package/dist/src/cli/utils/remote-sync.js +129 -53
  12. package/dist/src/cli/utils/user-config.js +12 -0
  13. package/dist/src/config/ai-manager-hiring.js +121 -0
  14. package/dist/src/config/compat.js +16 -0
  15. package/dist/src/config/feature-flags.js +25 -0
  16. package/dist/src/config/persona-capability-bundles.js +273 -0
  17. package/dist/src/config/persona-hiring.js +270 -0
  18. package/dist/src/config/portfolio-slug-overrides.js +17 -0
  19. package/dist/src/config/pricing.js +37 -0
  20. package/dist/src/config/stripe.js +43 -0
  21. package/dist/src/core/fraim-config-schema.generated.js +8 -2
  22. package/dist/src/core/utils/local-registry-resolver.js +26 -0
  23. package/dist/src/core/utils/project-fraim-paths.js +89 -2
  24. package/dist/src/first-run/session-service.js +9 -0
  25. package/dist/src/local-mcp-server/artifact-retention-cleanup.js +298 -0
  26. package/dist/src/local-mcp-server/learning-context-builder.js +41 -81
  27. package/dist/src/local-mcp-server/stdio-server.js +42 -7
  28. package/package.json +5 -1
  29. package/public/ai-hub/index.html +205 -89
  30. package/public/ai-hub/review.css +12 -0
  31. package/public/ai-hub/script.js +1720 -240
  32. package/public/ai-hub/styles.css +473 -6
@@ -275,7 +275,223 @@ function extractSignalFromArgs(args) {
275
275
  const jobName = typeof args.jobName === 'string' ? args.jobName : undefined;
276
276
  const jobId = typeof args.jobId === 'string' ? args.jobId : undefined;
277
277
  const reviewHandoff = extractReviewHandoffFromArgs(args);
278
- return { phaseId, phaseStatus, findingsText, discriminant, jobName, jobId, ...(reviewHandoff ? { reviewHandoff } : {}) };
278
+ const delegationLedger = extractDelegationLedgerFromArgs(args);
279
+ return {
280
+ phaseId,
281
+ phaseStatus,
282
+ findingsText,
283
+ discriminant,
284
+ jobName,
285
+ jobId,
286
+ ...(reviewHandoff ? { reviewHandoff } : {}),
287
+ ...(delegationLedger ? { delegationLedger } : {}),
288
+ };
289
+ }
290
+ function extractDelegationLedgerFromArgs(args) {
291
+ const direct = readDelegationLedgerCandidate(args.delegationGraph) ||
292
+ readDelegationLedgerCandidate(args.delegationLedger) ||
293
+ readDelegationLedgerCandidate(args.delegation_ledger);
294
+ if (direct)
295
+ return direct;
296
+ const evidence = args.evidence;
297
+ if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence))
298
+ return null;
299
+ const e = evidence;
300
+ const nested = readDelegationLedgerCandidate(e.delegationGraph) ||
301
+ readDelegationLedgerCandidate(e.delegationLedger) ||
302
+ readDelegationLedgerCandidate(e.delegation_ledger);
303
+ if (nested)
304
+ return nested;
305
+ const objective = stringValue(e.goal) || stringValue(e.objective) || stringValue(e.parentObjective) || stringValue(args.jobName) || 'Delegated manager objective';
306
+ return ledgerFromExecutionPlan(e.execution_plan, objective) || ledgerFromApprovedPlan(e.approved_plan, objective);
307
+ }
308
+ function readDelegationLedgerCandidate(value) {
309
+ let candidate = value;
310
+ if (typeof candidate === 'string') {
311
+ try {
312
+ candidate = JSON.parse(candidate);
313
+ }
314
+ catch {
315
+ return null;
316
+ }
317
+ }
318
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
319
+ return null;
320
+ const obj = candidate;
321
+ const rawJobs = Array.isArray(obj.jobs) ? obj.jobs : Array.isArray(obj.tasks) ? obj.tasks : [];
322
+ if (obj.delegationRequired !== true || !rawJobs.length)
323
+ return null;
324
+ const objective = stringValue(obj.objective) || 'Delegated manager objective';
325
+ const tasks = rawJobs.map((task, index) => normalizeDelegationTaskCandidate(task, index)).filter(Boolean);
326
+ if (!tasks.length)
327
+ return null;
328
+ return {
329
+ delegationRequired: true,
330
+ objective,
331
+ orchestratorPersonaKey: stringValue(obj.orchestratorPersonaKey) || 'mandy',
332
+ latestSummary: stringValue(obj.latestSummary) || undefined,
333
+ tasks,
334
+ };
335
+ }
336
+ function ledgerFromExecutionPlan(value, objective) {
337
+ if (!value || typeof value !== 'object' || Array.isArray(value))
338
+ return null;
339
+ const plan = value;
340
+ const tasks = [];
341
+ for (const [groupKey, groupValue] of Object.entries(plan)) {
342
+ if (!Array.isArray(groupValue))
343
+ continue;
344
+ for (const rawTask of groupValue) {
345
+ const task = normalizeExecutionPlanTask(rawTask, tasks.length, groupKey);
346
+ if (task)
347
+ tasks.push(task);
348
+ }
349
+ }
350
+ if (!tasks.length)
351
+ return null;
352
+ return {
353
+ delegationRequired: true,
354
+ objective,
355
+ orchestratorPersonaKey: 'mandy',
356
+ latestSummary: stringValue(plan.dependencies) || undefined,
357
+ tasks,
358
+ };
359
+ }
360
+ function ledgerFromApprovedPlan(value, objective) {
361
+ if (!Array.isArray(value))
362
+ return null;
363
+ const tasks = value.map((entry, index) => normalizeApprovedPlanEntry(entry, index)).filter(Boolean);
364
+ if (!tasks.length)
365
+ return null;
366
+ return {
367
+ delegationRequired: true,
368
+ objective,
369
+ orchestratorPersonaKey: 'mandy',
370
+ tasks,
371
+ };
372
+ }
373
+ function normalizeExecutionPlanTask(value, index, groupKey) {
374
+ if (!value || typeof value !== 'object' || Array.isArray(value))
375
+ return null;
376
+ const obj = value;
377
+ const personaKey = normalizePersonaKey(stringValue(obj.persona) || stringValue(obj.personaKey));
378
+ const jobId = normalizeJobId(stringValue(obj.job) || stringValue(obj.jobId));
379
+ const scope = stringValue(obj.scope) || stringValue(obj.instructions) || stringValue(obj.summary);
380
+ const title = stringValue(obj.title) || humanizeJobId(jobId || `workstream-${index + 1}`);
381
+ if (!jobId)
382
+ return null;
383
+ return {
384
+ taskId: stringValue(obj.jobKey) || stringValue(obj.taskId) || uniqueTaskId(`${personaKey || 'job'}-${jobId}`, index),
385
+ title,
386
+ status: 'planned',
387
+ personaKey,
388
+ jobId,
389
+ dependsOn: dependenciesForGroup(groupKey),
390
+ reviewJobId: normalizeJobId(stringValue(obj.reviewJobId) || stringValue(obj.review?.jobId || '')),
391
+ reviewType: stringValue(obj.reviewType) || stringValue(obj.review?.type || '') || null,
392
+ instructions: scope || `Complete ${title} and return a concise deliverable to Mandy for review.`,
393
+ };
394
+ }
395
+ function normalizeApprovedPlanEntry(value, index) {
396
+ const text = stringValue(value);
397
+ if (!text)
398
+ return null;
399
+ const match = text.match(/^\s*(?:(\w+)\s*[/]\s*)?([a-z0-9][a-z0-9-]*)\s*:?\s*(.*)$/i);
400
+ if (!match)
401
+ return null;
402
+ const personaKey = normalizePersonaKey(match[1] || '');
403
+ const jobId = normalizeJobId(match[2]);
404
+ const scope = match[3]?.trim() || '';
405
+ if (!jobId)
406
+ return null;
407
+ const title = scope ? titleFromScope(scope, jobId) : humanizeJobId(jobId);
408
+ return {
409
+ taskId: uniqueTaskId(`${personaKey || 'job'}-${jobId}`, index),
410
+ title,
411
+ status: 'planned',
412
+ personaKey,
413
+ jobId,
414
+ dependsOn: [],
415
+ instructions: scope || `Complete ${title} and return a concise deliverable to Mandy for review.`,
416
+ };
417
+ }
418
+ function normalizeDelegationTaskCandidate(value, index) {
419
+ if (!value || typeof value !== 'object' || Array.isArray(value))
420
+ return null;
421
+ const obj = value;
422
+ const title = stringValue(obj.title) || humanizeJobId(stringValue(obj.jobId) || stringValue(obj.job) || `workstream-${index + 1}`);
423
+ const personaKey = normalizePersonaKey(stringValue(obj.personaKey) || stringValue(obj.persona));
424
+ const jobId = normalizeJobId(stringValue(obj.jobId) || stringValue(obj.job));
425
+ if (!title || !jobId)
426
+ return null;
427
+ const review = obj.review && typeof obj.review === 'object' && !Array.isArray(obj.review)
428
+ ? obj.review
429
+ : {};
430
+ return {
431
+ taskId: stringValue(obj.jobKey) || stringValue(obj.taskId) || uniqueTaskId(`${personaKey || 'job'}-${jobId}`, index),
432
+ title,
433
+ status: 'planned',
434
+ personaKey,
435
+ jobId,
436
+ reviewJobId: normalizeJobId(stringValue(obj.reviewJobId) || stringValue(review.jobId)),
437
+ reviewType: stringValue(obj.reviewType) || stringValue(review.type) || null,
438
+ instructions: stringValue(obj.instructions) || stringValue(obj.scope) || undefined,
439
+ latestSummary: stringValue(obj.latestSummary) || undefined,
440
+ dependsOn: Array.isArray(obj.dependsOn) ? obj.dependsOn.map(stringValue).filter(Boolean) : [],
441
+ };
442
+ }
443
+ function stringValue(value) {
444
+ return typeof value === 'string' ? value.trim() : '';
445
+ }
446
+ function normalizePersonaKey(value) {
447
+ const cleaned = value.trim().replace(/^\[|\]$/g, '').toLowerCase();
448
+ if (!cleaned)
449
+ return null;
450
+ const aliases = {
451
+ beza: 'beza',
452
+ pam: 'pam',
453
+ gautam: 'gautam',
454
+ swen: 'swen',
455
+ qasm: 'qasm',
456
+ huxley: 'huxley',
457
+ recardo: 'recardo',
458
+ hari: 'hari',
459
+ mandy: 'mandy',
460
+ };
461
+ return aliases[cleaned] || cleaned.replace(/[^a-z0-9-]/g, '');
462
+ }
463
+ function normalizeJobId(value) {
464
+ const cleaned = value.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-+|-+$/g, '');
465
+ return cleaned || null;
466
+ }
467
+ function uniqueTaskId(base, index) {
468
+ const cleaned = normalizeJobId(base) || `task-${index + 1}`;
469
+ return `${cleaned}-${index + 1}`;
470
+ }
471
+ function humanizeJobId(value) {
472
+ return value
473
+ .split(/[-_\s]+/)
474
+ .filter(Boolean)
475
+ .map((part) => `${part[0]?.toUpperCase() || ''}${part.slice(1)}`)
476
+ .join(' ') || 'Delegated workstream';
477
+ }
478
+ function titleFromScope(scope, jobId) {
479
+ const trimmed = scope.trim();
480
+ if (!trimmed)
481
+ return humanizeJobId(jobId);
482
+ const sentence = trimmed.split(/[.;]/)[0]?.trim();
483
+ if (!sentence)
484
+ return humanizeJobId(jobId);
485
+ return sentence.length <= 80 ? sentence : sentence.slice(0, 77).trimEnd() + '...';
486
+ }
487
+ function dependenciesForGroup(groupKey) {
488
+ const match = groupKey.match(/group[_-\s]*(\d+)/i);
489
+ if (!match)
490
+ return [];
491
+ const group = Number(match[1]);
492
+ if (!Number.isFinite(group) || group <= 1)
493
+ return [];
494
+ return [`group-${group - 1}`];
279
495
  }
280
496
  function extractReviewHandoffFromArgs(args) {
281
497
  const direct = readReviewHandoffCandidate(args.reviewHandoff);
@@ -595,7 +811,7 @@ function buildStartPlan(hostId, message, sessionId) {
595
811
  // Gemini CLI creates the durable session id itself. Hub captures
596
812
  // that real id from Gemini's chat log after start; pre-seeded UUIDs
597
813
  // are not reliably accepted by `gemini --resume`.
598
- args: ['--yolo', '--skip-trust', '-p', ' ', ...browser.args],
814
+ args: ['--yolo', '--skip-trust', '-p', 'stdin', ...browser.args],
599
815
  stdin: prompt,
600
816
  env: browser.env,
601
817
  };
@@ -640,7 +856,7 @@ function buildContinuePlan(hostId, sessionId, message) {
640
856
  const browser = sharedBrowserHostConfig('gemini');
641
857
  return {
642
858
  command: executableName('gemini'),
643
- args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', ' ', ...browser.args],
859
+ args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', 'stdin', ...browser.args],
644
860
  stdin: prompt,
645
861
  env: browser.env,
646
862
  };
@@ -703,7 +919,7 @@ function buildDirectStartPlan(hostId, message, sessionId) {
703
919
  ensureGeminiApiKey();
704
920
  return {
705
921
  command: executableName('gemini'),
706
- args: ['--yolo', '--skip-trust', '-p', ' '],
922
+ args: ['--yolo', '--skip-trust', '-p', 'stdin'],
707
923
  stdin: DIRECT_PREAMBLE + message,
708
924
  };
709
925
  }
@@ -741,7 +957,7 @@ function buildDirectContinuePlan(hostId, sessionId, message) {
741
957
  ensureGeminiApiKey();
742
958
  return {
743
959
  command: executableName('gemini'),
744
- args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', ' '],
960
+ args: ['--resume', sessionId, '--yolo', '--skip-trust', '-p', 'stdin'],
745
961
  stdin: DIRECT_PREAMBLE + message,
746
962
  };
747
963
  }
@@ -818,7 +1034,8 @@ function parseHostLine(hostId, line) {
818
1034
  if (isGeminiCliNotice(trimmed)) {
819
1035
  return withSignal({ raw: trimmed });
820
1036
  }
821
- return withSignal({ message: trimmed, raw: trimmed });
1037
+ const message = scrubGeminiAssistantLine(trimmed);
1038
+ return withSignal(message ? { message, raw: trimmed } : { raw: trimmed });
822
1039
  }
823
1040
  }
824
1041
  // GitHub Copilot CLI output: JSON stream where each event carries a `type`
@@ -873,6 +1090,10 @@ function isGeminiCliNotice(line) {
873
1090
  return line === 'YOLO mode is enabled. All tool calls will be automatically approved.' ||
874
1091
  line === 'Ripgrep is not available. Falling back to GrepTool.';
875
1092
  }
1093
+ function scrubGeminiAssistantLine(line) {
1094
+ const scrubbed = line.replace(/\[Thought:\s*true\]\s*/gi, '').trim();
1095
+ return scrubbed.length > 0 ? scrubbed : null;
1096
+ }
876
1097
  function wireHostProcess(hostId, child, handlers) {
877
1098
  const wire = (buffer, channel) => {
878
1099
  let pending = '';