fraim 2.0.162 → 2.0.164

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 (33) hide show
  1. package/dist/src/ai-hub/desktop-main.js +4 -1
  2. package/dist/src/ai-hub/hosts.js +97 -12
  3. package/dist/src/ai-hub/preferences.js +1 -1
  4. package/dist/src/ai-hub/server.js +49 -123
  5. package/dist/src/cli/commands/init-project.js +15 -14
  6. package/dist/src/cli/commands/sync.js +38 -0
  7. package/dist/src/cli/doctor/check-runner.js +3 -1
  8. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +261 -2
  9. package/dist/src/cli/utils/git-org-sync.js +56 -0
  10. package/dist/src/cli/utils/org-migration.js +50 -0
  11. package/dist/src/cli/utils/org-pack-sync.js +208 -0
  12. package/dist/src/cli/utils/project-bootstrap.js +20 -7
  13. package/dist/src/cli/utils/user-config.js +68 -0
  14. package/dist/src/core/fraim-config-schema.generated.js +10 -0
  15. package/dist/src/first-run/types.js +8 -0
  16. package/dist/src/local-mcp-server/agent-token-prices.js +30 -0
  17. package/dist/src/local-mcp-server/learning-context-builder.js +78 -29
  18. package/dist/src/local-mcp-server/stdio-server.js +30 -0
  19. package/index.js +1 -1
  20. package/package.json +2 -3
  21. package/public/ai-hub/index.html +5 -5
  22. package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
  23. package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
  24. package/public/ai-hub/review.css +15 -15
  25. package/public/ai-hub/script.js +254 -195
  26. package/public/ai-hub/styles.css +206 -16
  27. package/public/first-run/styles.css +73 -73
  28. package/dist/src/ai-hub/word-sideload.js +0 -95
  29. package/dist/src/cli/commands/test-mcp.js +0 -171
  30. package/dist/src/cli/setup/first-run.js +0 -242
  31. package/dist/src/core/config-writer.js +0 -75
  32. package/dist/src/core/utils/job-aliases.js +0 -47
  33. package/dist/src/core/utils/workflow-parser.js +0 -174
@@ -9,7 +9,11 @@ exports.buildInitProjectSummary = buildInitProjectSummary;
9
9
  exports.printInitProjectSummary = printInitProjectSummary;
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
11
  const ONBOARDING_VIDEO_PLAYLIST_URL = 'https://fraimworks.ai/resources.html#videos';
12
- function formatModeLabel(mode) {
12
+ function formatModeLabel(result) {
13
+ if (result.mode === 'conversational' && result.repositoryDetected) {
14
+ return 'Pending project onboarding';
15
+ }
16
+ const mode = result.mode;
13
17
  switch (mode) {
14
18
  case 'conversational':
15
19
  return 'Conversational';
@@ -19,7 +23,11 @@ function formatModeLabel(mode) {
19
23
  return 'Integrated';
20
24
  }
21
25
  }
22
- function getModeSpecificNextStep(mode) {
26
+ function getModeSpecificNextStep(result) {
27
+ if (result.mode === 'conversational' && result.repositoryDetected) {
28
+ return `The agent will create fraim/config.json during onboarding, preserve the detected repository identity, then focus on project context, validation commands, durable project rules, and any optional automation choices. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
29
+ }
30
+ const mode = result.mode;
23
31
  switch (mode) {
24
32
  case 'conversational':
25
33
  return `The agent will create fraim/config.json during onboarding, then focus on project context, validation commands, and durable project rules. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
@@ -53,7 +61,8 @@ function buildInitProjectSummary(result) {
53
61
  return {
54
62
  status: 'FRAIM project initialized.',
55
63
  fields: {
56
- mode: formatModeLabel(result.mode),
64
+ mode: formatModeLabel(result),
65
+ projectContext: result.repositoryDetected ? 'repository-backed' : 'local-folder',
57
66
  project: result.projectName,
58
67
  repositoryDetection: result.repositoryDetected ? 'detected' : 'not detected',
59
68
  issueTracking: result.issueTrackingDetected ? 'detected' : 'not detected',
@@ -64,19 +73,23 @@ function buildInitProjectSummary(result) {
64
73
  warnings: [...result.warnings],
65
74
  nextStep: {
66
75
  prompt: 'Tell your AI agent "Onboard this project".',
67
- explanation: getModeSpecificNextStep(result.mode)
76
+ explanation: getModeSpecificNextStep(result)
68
77
  }
69
78
  };
70
79
  }
71
80
  function printInitProjectSummary(result) {
72
81
  const summary = buildInitProjectSummary(result);
73
- const showRepositoryDetails = result.mode !== 'conversational';
82
+ const showRepositoryDetection = result.mode !== 'conversational' || result.repositoryDetected;
83
+ const showIssueTrackingDetails = result.mode !== 'conversational';
74
84
  console.log(chalk_1.default.green(`\n${summary.status}`));
75
85
  console.log(chalk_1.default.blue('Project summary:'));
76
- console.log(chalk_1.default.gray(` Mode: ${summary.fields.mode}`));
86
+ console.log(chalk_1.default.gray(` Automation mode: ${summary.fields.mode}`));
87
+ console.log(chalk_1.default.gray(` Project context: ${summary.fields.projectContext}`));
77
88
  console.log(chalk_1.default.gray(` Project: ${summary.fields.project}`));
78
- if (showRepositoryDetails) {
89
+ if (showRepositoryDetection) {
79
90
  console.log(chalk_1.default.gray(` Repository detection: ${summary.fields.repositoryDetection}`));
91
+ }
92
+ if (showIssueTrackingDetails) {
80
93
  console.log(chalk_1.default.gray(` Issue tracking: ${summary.fields.issueTracking}`));
81
94
  }
82
95
  console.log(chalk_1.default.gray(` Sync: ${summary.fields.sync}`));
@@ -0,0 +1,68 @@
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.readUserFraimConfig = readUserFraimConfig;
7
+ exports.writeUserFraimConfig = writeUserFraimConfig;
8
+ exports.getOrganizationConfig = getOrganizationConfig;
9
+ /**
10
+ * Typed read/write helpers for the user-level FRAIM config (~/.fraim/config.json),
11
+ * including the organization block introduced by issue #563.
12
+ *
13
+ * The user-level config is machine-scoped and distinct from the workspace
14
+ * config (fraim/config.json). Honors the FRAIM_USER_DIR override used by tests.
15
+ */
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
19
+ function getUserConfigPath() {
20
+ return path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'config.json');
21
+ }
22
+ function readUserFraimConfig() {
23
+ try {
24
+ const configPath = getUserConfigPath();
25
+ if (!fs_1.default.existsSync(configPath))
26
+ return {};
27
+ const parsed = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
28
+ return parsed && typeof parsed === 'object' ? parsed : {};
29
+ }
30
+ catch {
31
+ return {};
32
+ }
33
+ }
34
+ /**
35
+ * Shallow-merge a patch into the user config and persist it. Existing keys
36
+ * not named in the patch are preserved. Setting a key to undefined removes it.
37
+ */
38
+ function writeUserFraimConfig(patch) {
39
+ const current = readUserFraimConfig();
40
+ const merged = { ...current, ...patch };
41
+ for (const key of Object.keys(patch)) {
42
+ if (patch[key] === undefined)
43
+ delete merged[key];
44
+ }
45
+ const dir = (0, project_fraim_paths_1.getUserFraimDirPath)();
46
+ fs_1.default.mkdirSync(dir, { recursive: true });
47
+ fs_1.default.writeFileSync(getUserConfigPath(), JSON.stringify(merged, null, 2));
48
+ return merged;
49
+ }
50
+ /**
51
+ * Resolve the validated organization configuration, or null when no valid
52
+ * organization is configured on this machine (R1.1).
53
+ */
54
+ function getOrganizationConfig() {
55
+ const raw = readUserFraimConfig().organization;
56
+ if (!raw || typeof raw !== 'object')
57
+ return null;
58
+ if (raw.backend === 'git') {
59
+ const gitUrl = typeof raw.gitUrl === 'string' ? raw.gitUrl.trim() : '';
60
+ if (!gitUrl)
61
+ return null;
62
+ return { backend: 'git', gitUrl, id: raw.id };
63
+ }
64
+ if (raw.backend === 'fraim-cloud') {
65
+ return { backend: 'fraim-cloud', id: raw.id };
66
+ }
67
+ return null;
68
+ }
@@ -296,6 +296,9 @@ exports.FRAIM_CONFIG_SCHEMA = {
296
296
  },
297
297
  "instanceUrl": {
298
298
  "kind": "string"
299
+ },
300
+ "accessScript": {
301
+ "kind": "string"
299
302
  }
300
303
  }
301
304
  },
@@ -390,6 +393,12 @@ exports.FRAIM_CONFIG_SCHEMA = {
390
393
  },
391
394
  "instructions": {
392
395
  "kind": "string"
396
+ },
397
+ "accessScript": {
398
+ "kind": "string"
399
+ },
400
+ "purpose": {
401
+ "kind": "string"
393
402
  }
394
403
  }
395
404
  }
@@ -588,6 +597,7 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
588
597
  "integrations.itsm",
589
598
  "integrations.itsm.provider",
590
599
  "integrations.itsm.instanceUrl",
600
+ "integrations.itsm.accessScript",
591
601
  "integrations.identity",
592
602
  "integrations.identity.provider",
593
603
  "automation",
@@ -50,6 +50,14 @@ exports.FIRST_RUN_AGENT_OPTIONS = [
50
50
  launchCommand: 'gemini',
51
51
  installPackage: '@google/gemini-cli',
52
52
  },
53
+ {
54
+ id: 'copilot-cli',
55
+ label: 'GitHub Copilot',
56
+ detectAliases: ['copilot'],
57
+ loginCommand: 'copilot login',
58
+ launchCommand: 'copilot',
59
+ installPackage: '@github/copilot',
60
+ },
53
61
  ];
54
62
  /**
55
63
  * The canonical row set, in display order. Each row starts in `pending`;
@@ -109,6 +109,36 @@ exports.AGENT_TOKEN_PRICES = [
109
109
  source: 'https://ai.google.dev/gemini-api/docs/pricing',
110
110
  verifiedOn: '2026-06-02',
111
111
  },
112
+ // GitHub Copilot CLI. The CLI's agentic mode uses GPT-4.1 (also branded
113
+ // "gpt-4.1" in the Copilot API stream). GitHub charges for Copilot via
114
+ // premium-request quota rather than per-token billing on most plans; the
115
+ // per-token rates below apply to the Copilot API / enterprise API billing
116
+ // path (pay-per-token, not included-requests) as published at
117
+ // https://docs.github.com/en/copilot/about-github-copilot/plans-for-github-copilot
118
+ // and the underlying OpenAI model pricing for gpt-4.1 at openai.com/api/pricing/.
119
+ // Coverage is 'partial' for Copilot runs where the CLI does not emit a cost
120
+ // field — the table backstops cost computation when a token count is present
121
+ // but no direct costUsd is in the stream.
122
+ {
123
+ agent: 'copilot',
124
+ model: 'gpt-4.1',
125
+ inputPerMTok: 2.00,
126
+ outputPerMTok: 8.00,
127
+ cacheReadPerMTok: 0.50,
128
+ cacheCreationPerMTok: 0,
129
+ source: 'https://openai.com/api/pricing/ (gpt-4.1 row; GitHub Copilot CLI agentic mode)',
130
+ verifiedOn: '2026-06-09',
131
+ },
132
+ {
133
+ agent: 'copilot',
134
+ model: 'gpt-4o',
135
+ inputPerMTok: 2.50,
136
+ outputPerMTok: 10.00,
137
+ cacheReadPerMTok: 1.25,
138
+ cacheCreationPerMTok: 0,
139
+ source: 'https://openai.com/api/pricing/ (gpt-4o row; GitHub Copilot CLI fallback model)',
140
+ verifiedOn: '2026-06-09',
141
+ },
112
142
  ];
113
143
  /**
114
144
  * Look up the price entry for an agent + model. Agent is matched
@@ -341,20 +341,40 @@ function computeOldestL0AgeDays(workspaceRoot, userId) {
341
341
  }
342
342
  return oldest;
343
343
  }
344
+ /**
345
+ * Resolve an L2 org-scope learning file (issue #563): a repo-local override
346
+ * (`fraim/personalized-employee/learnings/`) wins, then the synced org cache
347
+ * (`~/.fraim/org/learnings/`). Org learnings are part of the shared org pack,
348
+ * so a synced copy must be readable by the agent even with no repo-local copy.
349
+ * Mirrors the org context/rules resolution order. Returns `present:false` with
350
+ * the repo path/display when neither tier has the file.
351
+ */
352
+ function resolveOrgLearningFile(repoLearningsBase, fileName) {
353
+ const repoPath = (0, path_1.join)(repoLearningsBase, fileName);
354
+ if ((0, fs_1.existsSync)(repoPath)) {
355
+ return { present: true, path: repoPath, displayPath: `${REPO_LEARNINGS_REL}/${fileName}` };
356
+ }
357
+ const cachePath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'org', 'learnings', fileName);
358
+ if ((0, fs_1.existsSync)(cachePath)) {
359
+ return { present: true, path: cachePath, displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`org/learnings/${fileName}`) };
360
+ }
361
+ return { present: false, path: repoPath, displayPath: `${REPO_LEARNINGS_REL}/${fileName}` };
362
+ }
344
363
  function buildLearningContextSection(workspaceRoot, userId, forJob) {
345
364
  const roots = getLearningRoots(workspaceRoot);
346
365
  const resolvedUserId = resolveLearningUserId(workspaceRoot, userId, roots);
347
366
  const threshold = getScoreThreshold(workspaceRoot);
348
- const l2MistakePath = (0, path_1.join)(roots.repoLearningsBase, 'org-mistake-patterns.md');
349
- const l2PrefPath = (0, path_1.join)(roots.repoLearningsBase, 'org-preferences.md');
350
- const l2CoachPath = (0, path_1.join)(roots.repoLearningsBase, 'org-manager-coaching.md');
351
- const l2ValidatedPath = (0, path_1.join)(roots.repoLearningsBase, 'org-validated-patterns.md');
352
- const l2MistakePresent = (0, fs_1.existsSync)(l2MistakePath);
353
- const l2PrefPresent = (0, fs_1.existsSync)(l2PrefPath);
354
- const l2CoachPresent = (0, fs_1.existsSync)(l2CoachPath);
355
- const l2ValidatedPresent = (0, fs_1.existsSync)(l2ValidatedPath);
356
- const l2MistakeStats = l2MistakePresent ? scanMistakePatternFile(l2MistakePath, threshold, 'mistake-patterns') : null;
357
- const l2ValidatedStats = l2ValidatedPresent ? scanMistakePatternFile(l2ValidatedPath, threshold, 'validated-patterns') : null;
367
+ // L2 org learnings resolve repo-local first, then the synced org cache (#563).
368
+ const l2Mistake = resolveOrgLearningFile(roots.repoLearningsBase, 'org-mistake-patterns.md');
369
+ const l2Pref = resolveOrgLearningFile(roots.repoLearningsBase, 'org-preferences.md');
370
+ const l2Coach = resolveOrgLearningFile(roots.repoLearningsBase, 'org-manager-coaching.md');
371
+ const l2Validated = resolveOrgLearningFile(roots.repoLearningsBase, 'org-validated-patterns.md');
372
+ const l2MistakePresent = l2Mistake.present;
373
+ const l2PrefPresent = l2Pref.present;
374
+ const l2CoachPresent = l2Coach.present;
375
+ const l2ValidatedPresent = l2Validated.present;
376
+ const l2MistakeStats = l2MistakePresent ? scanMistakePatternFile(l2Mistake.path, threshold, 'mistake-patterns') : null;
377
+ const l2ValidatedStats = l2ValidatedPresent ? scanMistakePatternFile(l2Validated.path, threshold, 'validated-patterns') : null;
358
378
  const l1Mistake = resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, `${resolvedUserId}-mistake-patterns.md`);
359
379
  const l1Pref = resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, `${resolvedUserId}-preferences.md`);
360
380
  const l1Coach = resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, `${resolvedUserId}-manager-coaching.md`);
@@ -394,13 +414,13 @@ function buildLearningContextSection(workspaceRoot, userId, forJob) {
394
414
  if (hasL2) {
395
415
  section += '### L2 - Org patterns\n';
396
416
  if (l2MistakePresent)
397
- section += `\`${REPO_LEARNINGS_REL}/org-mistake-patterns.md\` (entries above score threshold)\n`;
417
+ section += `\`${l2Mistake.displayPath}\` (entries above score threshold)\n`;
398
418
  if (l2PrefPresent)
399
- section += `\`${REPO_LEARNINGS_REL}/org-preferences.md\` (all entries)\n`;
419
+ section += `\`${l2Pref.displayPath}\` (all entries)\n`;
400
420
  if (l2CoachPresent)
401
- section += `\`${REPO_LEARNINGS_REL}/org-manager-coaching.md\` (manager-facing; all entries)\n`;
421
+ section += `\`${l2Coach.displayPath}\` (manager-facing; all entries)\n`;
402
422
  if (l2ValidatedPresent)
403
- section += `\`${REPO_LEARNINGS_REL}/org-validated-patterns.md\` (entries above score threshold)\n`;
423
+ section += `\`${l2Validated.displayPath}\` (entries above score threshold)\n`;
404
424
  const l2DormantTotal = (l2MistakeStats?.dormant || 0) + (l2ValidatedStats?.dormant || 0);
405
425
  if (l2DormantTotal > 0) {
406
426
  section += `Dormant: ${l2DormantTotal} org pattern${l2DormantTotal !== 1 ? 's' : ''} below threshold\n`;
@@ -465,12 +485,13 @@ function buildLearningContextSection(workspaceRoot, userId, forJob) {
465
485
  return section;
466
486
  }
467
487
  /**
468
- * Resolve an organization/manager-scope context file. These are user-level and
469
- * portable across repos, but a repo-local copy (under `fraim/…`) wins when
470
- * presentmirroring how personal learnings layer (repo-local shadows
471
- * user-level) in `resolvePersonalLearningFile`.
488
+ * Resolve an organization/manager-scope context file (issue #563 order, R3.1):
489
+ * 1. repo-local override (`fraim/personalized-employee/…`) wins when present
490
+ * 2. managed org cache (`~/.fraim/org/…`) the synced shared org layer;
491
+ * eligible for org files only, never manager files (R3.3)
492
+ * 3. legacy machine-local (`~/.fraim/personalized-employee/…`)
472
493
  */
473
- function resolveOrgContextFile(workspaceRoot, relativePath) {
494
+ function resolveOrgContextFile(workspaceRoot, relativePath, orgCacheEligible = true) {
474
495
  try {
475
496
  const repoPath = (0, path_1.join)((0, project_fraim_paths_1.getWorkspaceFraimDir)(workspaceRoot), 'personalized-employee', relativePath);
476
497
  if ((0, fs_1.existsSync)(repoPath)) {
@@ -479,6 +500,15 @@ function resolveOrgContextFile(workspaceRoot, relativePath) {
479
500
  displayPath: (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`personalized-employee/${relativePath}`)
480
501
  };
481
502
  }
503
+ if (orgCacheEligible) {
504
+ const cachePath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'org', relativePath);
505
+ if ((0, fs_1.existsSync)(cachePath)) {
506
+ return {
507
+ present: true,
508
+ displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`org/${relativePath}`)
509
+ };
510
+ }
511
+ }
482
512
  const userPath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'personalized-employee', relativePath);
483
513
  if ((0, fs_1.existsSync)(userPath)) {
484
514
  return {
@@ -518,11 +548,12 @@ function resolveProjectContextFile(workspaceRoot, relativePath) {
518
548
  * lists only present files, and returns '' when none exist.
519
549
  */
520
550
  function buildTeamContextSection(workspaceRoot, forJob) {
521
- // Organization layer (user-level, portable; repo-local override wins).
551
+ // Organization layer: repo override, then org cache, then legacy machine-local.
552
+ // Manager files are personal and never resolve from the shared org cache (R3.3).
522
553
  const orgContext = resolveOrgContextFile(workspaceRoot, 'context/org_context.md');
523
- const managerContext = resolveOrgContextFile(workspaceRoot, 'context/manager_context.md');
554
+ const managerContext = resolveOrgContextFile(workspaceRoot, 'context/manager_context.md', false);
524
555
  const orgRules = resolveOrgContextFile(workspaceRoot, 'rules/org_rules.md');
525
- const managerRules = resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md');
556
+ const managerRules = resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md', false);
526
557
  // Project layer (repo-local only).
527
558
  const projectContext = resolveProjectContextFile(workspaceRoot, 'context/project_context.md');
528
559
  const projectBrief = resolveProjectContextFile(workspaceRoot, 'context/project_brief.md');
@@ -571,9 +602,9 @@ function buildTeamContextSection(workspaceRoot, forJob) {
571
602
  function resolveTeamContextFiles(workspaceRoot) {
572
603
  return {
573
604
  orgContext: resolveOrgContextFile(workspaceRoot, 'context/org_context.md'),
574
- managerContext: resolveOrgContextFile(workspaceRoot, 'context/manager_context.md'),
605
+ managerContext: resolveOrgContextFile(workspaceRoot, 'context/manager_context.md', false),
575
606
  orgRules: resolveOrgContextFile(workspaceRoot, 'rules/org_rules.md'),
576
- managerRules: resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md'),
607
+ managerRules: resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md', false),
577
608
  projectContext: resolveProjectContextFile(workspaceRoot, 'context/project_context.md'),
578
609
  projectBrief: resolveProjectContextFile(workspaceRoot, 'context/project_brief.md'),
579
610
  projectQa: resolveProjectContextFile(workspaceRoot, 'context/project_qa.md'),
@@ -627,6 +658,23 @@ function resolveTeamContextFile(workspaceRoot, key) {
627
658
  scope
628
659
  };
629
660
  }
661
+ // Synced org cache (org files only, never manager files — R3.3). Reads
662
+ // resolve here, but the cache is managed content: it is never a write
663
+ // destination (R5.1), so writePath stays empty and the caller must route
664
+ // edits through the org backend's propose-and-approve flow.
665
+ if (key === 'org' || key === 'orgRules') {
666
+ const cachePath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'org', relativePath);
667
+ if ((0, fs_1.existsSync)(cachePath)) {
668
+ return {
669
+ present: true,
670
+ readPath: cachePath,
671
+ writePath: '',
672
+ displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`org/${relativePath}`),
673
+ scope,
674
+ managedByOrgSync: true
675
+ };
676
+ }
677
+ }
630
678
  const userPath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'personalized-employee', relativePath);
631
679
  const userDisplay = (0, project_fraim_paths_1.getUserFraimDisplayPath)(`personalized-employee/${relativePath}`);
632
680
  return {
@@ -674,11 +722,12 @@ function countLearningEntries(filePath) {
674
722
  function countPreservedLearnings(workspaceRoot, userId) {
675
723
  const roots = getLearningRoots(workspaceRoot);
676
724
  const resolvedUserId = resolveLearningUserId(workspaceRoot, userId, roots);
677
- // L2 organization-scope preserved files (repo-local org-* files).
678
- const organization = countLearningEntries((0, path_1.join)(roots.repoLearningsBase, 'org-mistake-patterns.md')) +
679
- countLearningEntries((0, path_1.join)(roots.repoLearningsBase, 'org-preferences.md')) +
680
- countLearningEntries((0, path_1.join)(roots.repoLearningsBase, 'org-manager-coaching.md')) +
681
- countLearningEntries((0, path_1.join)(roots.repoLearningsBase, 'org-validated-patterns.md'));
725
+ // L2 organization-scope preserved files: repo-local override, then the
726
+ // synced org cache (#563), matching what buildLearningContextSection injects.
727
+ const organization = countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-mistake-patterns.md').path) +
728
+ countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-preferences.md').path) +
729
+ countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-manager-coaching.md').path) +
730
+ countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-validated-patterns.md').path);
682
731
  const resolve = (fileName) => resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, fileName);
683
732
  // L1 manager-facing reverse-mentoring file.
684
733
  const manager = countLearningEntries(resolve(`${resolvedUserId}-manager-coaching.md`).path);
@@ -415,6 +415,7 @@ class FraimLocalMCPServer {
415
415
  this.mentoringResponseCache = null;
416
416
  this.connectSyncInFlight = null;
417
417
  this.latestConnectSyncWarning = null;
418
+ this.orgCacheRefreshInFlight = false;
418
419
  this.writer = writer || process.stdout.write.bind(process.stdout);
419
420
  this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
420
421
  this.apiKey = this.loadApiKey();
@@ -748,6 +749,35 @@ class FraimLocalMCPServer {
748
749
  finally {
749
750
  this.connectSyncInFlight = null;
750
751
  }
752
+ this.maybeRefreshOrgCache(String(requestId));
753
+ }
754
+ /**
755
+ * Issue #563 (R4.2): opportunistically refresh the managed org cache
756
+ * (~/.fraim/org/) at session bootstrap when it is missing or older than
757
+ * 24 hours. Fire-and-forget: session start never blocks on the org
758
+ * backend, and a failed refresh leaves the existing cache serving stale
759
+ * with its age (R4.3).
760
+ */
761
+ maybeRefreshOrgCache(requestId) {
762
+ if (this.orgCacheRefreshInFlight)
763
+ return;
764
+ this.orgCacheRefreshInFlight = true;
765
+ void Promise.resolve().then(() => __importStar(require('../cli/utils/org-pack-sync'))).then(async ({ getOrgCacheAgeHours, syncOrgCache }) => {
766
+ const ageHours = getOrgCacheAgeHours();
767
+ if (ageHours !== null && ageHours < 24)
768
+ return;
769
+ const outcome = await syncOrgCache();
770
+ if (outcome.status !== 'disabled') {
771
+ const version = outcome.metadata ? ` (version ${outcome.metadata.version.slice(0, 12)})` : '';
772
+ this.log(`[req:${requestId}] Org cache refresh at connect: ${outcome.status}${version}`);
773
+ }
774
+ })
775
+ .catch((error) => {
776
+ this.logError(`[req:${requestId}] Org cache refresh failed: ${error?.message || error}`);
777
+ })
778
+ .finally(() => {
779
+ this.orgCacheRefreshInFlight = false;
780
+ });
751
781
  }
752
782
  /**
753
783
  * Automatically detect machine information
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM Framework - Smart Entry Point
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.162",
3
+ "version": "2.0.164",
4
4
  "description": "FRAIM CLI - Framework for Rigor-based AI Management (alias for fraim-framework)",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "build:fraim-brain": "node scripts/generate-fraim-brain.js",
16
16
  "test-all": "npm run test && npm run test:isolated tests/isolated/test-*.ts && npm run test:ui",
17
17
  "test": "node scripts/test-with-server.js",
18
- "test:isolated": "npx tsx --test --test-concurrency=1 --test-reporter=spec ",
18
+ "test:isolated": "node scripts/test-isolated.js",
19
19
  "test:smoke": "node scripts/test-with-server.js --tags=smoke",
20
20
  "test:coverage": "node scripts/test-with-server.js --tags=smoke --coverage",
21
21
  "test:stripe": "node scripts/test-with-server.js tests/test-stripe-payment-complete.ts",
@@ -104,7 +104,6 @@
104
104
  "@types/prompts": "^2.4.9",
105
105
  "@types/semver": "^7.7.1",
106
106
  "fast-glob": "^3.3.3",
107
- "html-to-docx": "^1.8.0",
108
107
  "markdown-it": "^14.1.1",
109
108
  "markdown-it-highlightjs": "^4.3.0",
110
109
  "playwright": "^1.58.2",
@@ -9,7 +9,7 @@
9
9
  <!-- Detect Electron + host platform before CSS paints so html.electron / html.mac /
10
10
  html.win / html.linux rules apply on first render (no flash, correct native chrome). -->
11
11
  <script>(function(){var h=document.documentElement,ua=navigator.userAgent;if(/Electron/.test(ua))h.classList.add('electron');var p=(navigator.userAgentData&&navigator.userAgentData.platform)||navigator.platform||'';if(/mac/i.test(p)||/Mac|iPhone|iPad|iPod/.test(ua))h.classList.add('mac');else if(/win/i.test(p)||/Windows/.test(ua))h.classList.add('win');else if(/linux/i.test(p)||/Linux|X11/.test(ua))h.classList.add('linux');})();</script>
12
- <link rel="stylesheet" href="./styles.css">
12
+ <link rel="stylesheet" href="./styles.css?v=conv-panels-20260611b">
13
13
  <link rel="stylesheet" href="./review.css">
14
14
  </head>
15
15
  <body>
@@ -329,7 +329,7 @@
329
329
  </span>
330
330
  <span class="active-employee-row" onclick="event.stopPropagation()">
331
331
  <span class="active-employee-label">Maestro</span>
332
- <select id="active-employee-select" class="employee-select inline" aria-label="Employee"></select>
332
+ <select id="active-employee-select" class="employee-select inline" aria-label="Agent Tool"></select>
333
333
  </span>
334
334
  </summary>
335
335
  <div class="panel-body">
@@ -363,7 +363,7 @@
363
363
  <summary>
364
364
  <span class="panel-summary-copy">
365
365
  <span class="panel-kicker">Micro-manage</span>
366
- <span class="panel-summary-text">Raw host details</span>
366
+ <span class="panel-summary-text">Raw host events and tool calls</span>
367
367
  </span>
368
368
  </summary>
369
369
  <pre class="micro-log" id="micro-log"></pre>
@@ -476,8 +476,8 @@
476
476
  <div class="word-ctx-card-body" id="word-ctx-card-body" hidden></div>
477
477
  </div>
478
478
  <div class="employee-line">
479
- <span class="employee-label">Employee:</span>
480
- <select id="employee-select" class="employee-select"></select>
479
+ <span class="employee-label">Agent Tool:</span>
480
+ <select id="employee-select" class="employee-select" aria-label="Agent Tool"></select>
481
481
  </div>
482
482
  <div id="agent-install-panel"></div>
483
483
  <div id="ab-toggle-wrap" hidden>