fraim 2.0.312 → 2.0.314

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.
@@ -292,10 +292,12 @@ const runSync = async (options) => {
292
292
  registryFiles: localRegistryFiles
293
293
  });
294
294
  if (result.success) {
295
- // Local-dev sync bypasses Layer 1 (machine-level sync) below, but scripts
296
- // and docs still need to land in ~/.fraim/ or the reported counts are a lie.
295
+ // Local-dev sync bypasses Layer 1 (machine-level sync) below, but scripts,
296
+ // docs, and job stubs still need to land in ~/.fraim/ or the reported counts
297
+ // are a lie.
297
298
  await (0, remote_sync_1.syncScriptsToUserDir)(localRegistryFiles);
298
299
  await (0, remote_sync_1.syncDocsToUserDir)(localRegistryFiles);
300
+ await (0, remote_sync_1.syncJobsToUserDir)(localRegistryFiles);
299
301
  const { ensureUserLevelDependencies: ensureLocalUserLevelDependencies } = await Promise.resolve().then(() => __importStar(require('../setup/user-level-sync')));
300
302
  ensureLocalUserLevelDependencies();
301
303
  console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from local server`));
@@ -363,6 +365,13 @@ const runSync = async (options) => {
363
365
  if (docsSynced > 0) {
364
366
  console.log(chalk_1.default.green(` Synced ${docsSynced} docs to ~/.fraim/docs/`));
365
367
  }
368
+ // 1b3. Sync ai-employee/ai-manager job stubs to ~/.fraim/ (Issue #1644: this
369
+ // machine-level baseline is what makes every project's job catalog current
370
+ // without its own project-level sync)
371
+ const { employeeJobsSynced: machineEmployeeJobsSynced, managerJobsSynced: machineManagerJobsSynced } = await (0, remote_sync_1.syncJobsToUserDir)(registryFiles);
372
+ if (machineEmployeeJobsSynced > 0 || machineManagerJobsSynced > 0) {
373
+ console.log(chalk_1.default.green(` Synced ${machineEmployeeJobsSynced} ai-employee jobs and ${machineManagerJobsSynced} ai-manager jobs to ~/.fraim/`));
374
+ }
366
375
  // 1c. Refresh org home
367
376
  await refreshOrgCache(remoteUrl, apiKey);
368
377
  // 1d. Refresh manager home
@@ -15,6 +15,7 @@ exports.SYNCED_CONTENT_BANNER_MARKER = void 0;
15
15
  exports.fetchRegistryFiles = fetchRegistryFiles;
16
16
  exports.syncScriptsToUserDir = syncScriptsToUserDir;
17
17
  exports.syncDocsToUserDir = syncDocsToUserDir;
18
+ exports.syncJobsToUserDir = syncJobsToUserDir;
18
19
  exports.syncFromRemote = syncFromRemote;
19
20
  const axios_1 = __importDefault(require("axios"));
20
21
  const fs_1 = require("fs");
@@ -235,6 +236,56 @@ async function syncScriptsToUserDir(files) {
235
236
  async function syncDocsToUserDir(files) {
236
237
  return syncTypedFilesToUserDir(files, 'docs', 'docs', 'docs');
237
238
  }
239
+ function stripJobsPrefix(rawPath) {
240
+ const normalized = rawPath.replace(/\\/g, '/');
241
+ return normalized.startsWith('jobs/') ? normalized.substring('jobs/'.length) : normalized;
242
+ }
243
+ /**
244
+ * Write one role's job stubs (ai-employee or ai-manager) into
245
+ * `~/.fraim/<role>/jobs/`. Shared by syncJobsToUserDir for both roles so the
246
+ * clean-then-write logic exists in exactly one place.
247
+ */
248
+ async function syncRoleJobsToUserDir(roleFiles, role) {
249
+ if (roleFiles.length === 0)
250
+ return 0;
251
+ const userDir = (0, script_sync_utils_1.getUserFraimDir)();
252
+ const targetDir = (0, path_1.join)(userDir, role, 'jobs');
253
+ if (!(0, fs_1.existsSync)(targetDir)) {
254
+ (0, fs_1.mkdirSync)(targetDir, { recursive: true });
255
+ }
256
+ cleanDirectory(targetDir, (candidatePath) => {
257
+ if ((0, path_1.resolve)(candidatePath) !== (0, path_1.resolve)(targetDir)) {
258
+ assertPathInsideDirectory(targetDir, candidatePath, `${role} job directory`);
259
+ }
260
+ });
261
+ const rolePrefix = new RegExp(`^${role}/`);
262
+ for (const file of roleFiles) {
263
+ const relPath = stripJobsPrefix(file.path).replace(rolePrefix, '');
264
+ const { filePath } = resolveUserRegistryFile(targetDir, relPath, `${role} job file`);
265
+ const fileDir = (0, path_1.dirname)(filePath);
266
+ if (!(0, fs_1.existsSync)(fileDir)) {
267
+ (0, fs_1.mkdirSync)(fileDir, { recursive: true });
268
+ }
269
+ (0, fs_1.writeFileSync)(filePath, applySyncedContentBanner(file), 'utf8');
270
+ }
271
+ return roleFiles.length;
272
+ }
273
+ /**
274
+ * Sync job stub files to the machine-level `~/.fraim/ai-employee/jobs/` and
275
+ * `~/.fraim/ai-manager/jobs/` directories. Mirrors syncScriptsToUserDir /
276
+ * syncDocsToUserDir so every FRAIM project on the machine sees the current
277
+ * job catalog without needing its own project-level sync (issue #1644).
278
+ */
279
+ async function syncJobsToUserDir(files) {
280
+ const allJobFiles = files.filter((f) => f.type === 'job');
281
+ const managerJobFiles = allJobFiles.filter((f) => stripJobsPrefix(f.path).startsWith('ai-manager/'));
282
+ const employeeJobFiles = allJobFiles.filter((f) => !stripJobsPrefix(f.path).startsWith('ai-manager/'));
283
+ const [employeeJobsSynced, managerJobsSynced] = await Promise.all([
284
+ syncRoleJobsToUserDir(employeeJobFiles, 'ai-employee'),
285
+ syncRoleJobsToUserDir(managerJobFiles, 'ai-manager'),
286
+ ]);
287
+ return { employeeJobsSynced, managerJobsSynced };
288
+ }
238
289
  /**
239
290
  * Sync jobs and scripts from remote FRAIM server
240
291
  */
@@ -792,9 +792,15 @@ class FraimLocalMCPServer {
792
792
  (0, fs_1.writeFileSync)(metadataPath, JSON.stringify(metadata, null, 2), 'utf8');
793
793
  }
794
794
  getLocalCatalogSyncReason(projectRoot) {
795
+ // Issue #1644: baseline job stubs are machine-level content (Layer 1 of
796
+ // `fraim sync` always writes them to ~/.fraim/), so both the existence
797
+ // check and the freshness metadata below must target the same
798
+ // machine-level home. Checking a project-level path here while reading
799
+ // machine-level metadata let a project whose own stub dirs merely existed
800
+ // (even if stale) pass on the strength of a sync some *other* project ran.
795
801
  const catalogRoots = [
796
- (0, path_1.join)(projectRoot, 'fraim', 'ai-employee', 'jobs'),
797
- (0, path_1.join)(projectRoot, 'fraim', 'ai-manager', 'jobs')
802
+ (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'ai-employee', 'jobs'),
803
+ (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'ai-manager', 'jobs')
798
804
  ];
799
805
  if (catalogRoots.some((catalogRoot) => !(0, fs_1.existsSync)(catalogRoot))) {
800
806
  return 'local job catalog is missing';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.312",
3
+ "version": "2.0.314",
4
4
  "description": "FRAIM core CLI and MCP package.",
5
5
  "main": "index.js",
6
6
  "bin": {