fraim 2.0.275 → 2.0.277

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.
@@ -42,7 +42,8 @@ function describeOnboardingInvocationSurfaces(installedIDEs) {
42
42
  /**
43
43
  * Install Claude FRAIM discovery artifacts at the user level.
44
44
  * Writes a skill to ~/.claude/skills/fraim/SKILL.md and a compatibility command
45
- * to ~/.claude/commands/fraim.md. Existing user files are preserved.
45
+ * to ~/.claude/commands/fraim.md. Stale content from prior FRAIM versions is
46
+ * refreshed; already-current content is left untouched.
46
47
  */
47
48
  async function installSlashCommands(homeDir) {
48
49
  const home = homeDir || os_1.default.homedir();
@@ -50,51 +51,59 @@ async function installSlashCommands(homeDir) {
50
51
  if (!fs_1.default.existsSync(claudeDir)) {
51
52
  return;
52
53
  }
53
- installFileIfMissing(path_1.default.join(claudeDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildClaudeSkillContent)(), 'Claude FRAIM skill (~/.claude/skills/fraim/SKILL.md)');
54
- installFileIfMissing(path_1.default.join(claudeDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildClaudeCommandShimContent)(), 'Claude compatibility command (~/.claude/commands/fraim.md)');
54
+ installOrUpdateManagedFile(path_1.default.join(claudeDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildClaudeSkillContent)(), 'Claude FRAIM skill (~/.claude/skills/fraim/SKILL.md)');
55
+ installOrUpdateManagedFile(path_1.default.join(claudeDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildClaudeCommandShimContent)(), 'Claude compatibility command (~/.claude/commands/fraim.md)');
55
56
  }
56
57
  /**
57
58
  * Install FRAIM invocation artifacts for non-Claude IDEs.
58
59
  * Supports: Cursor, Codex, Grok, Gemini CLI, Copilot CLI, Windsurf, Kiro
59
- * Does not overwrite existing files.
60
+ * Stale content from prior FRAIM versions is refreshed; already-current
61
+ * content is left untouched.
60
62
  */
61
63
  async function installGlobalRules(homeDir) {
62
64
  const home = homeDir || os_1.default.homedir();
63
65
  const cursorDir = path_1.default.join(home, '.cursor');
64
66
  if (fs_1.default.existsSync(cursorDir)) {
65
- installFileIfMissing(path_1.default.join(cursorDir, 'rules', 'fraim.mdc'), (0, ide_invocation_surfaces_1.buildCursorMentionRuleContent)(), 'Cursor FRAIM rule (~/.cursor/rules/fraim.mdc)');
67
+ installOrUpdateManagedFile(path_1.default.join(cursorDir, 'rules', 'fraim.mdc'), (0, ide_invocation_surfaces_1.buildCursorMentionRuleContent)(), 'Cursor FRAIM rule (~/.cursor/rules/fraim.mdc)');
66
68
  }
67
69
  const codexDir = path_1.default.join(home, '.codex');
68
70
  if (fs_1.default.existsSync(codexDir)) {
69
- installFileIfMissing(path_1.default.join(codexDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildCodexSkillContent)(), 'Codex FRAIM skill (~/.codex/skills/fraim/SKILL.md)');
71
+ installOrUpdateManagedFile(path_1.default.join(codexDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildCodexSkillContent)(), 'Codex FRAIM skill (~/.codex/skills/fraim/SKILL.md)');
70
72
  }
71
73
  const grokDir = path_1.default.join(home, '.grok');
72
74
  if (fs_1.default.existsSync(grokDir)) {
73
- installFileIfMissing(path_1.default.join(grokDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildGrokSkillContent)(), 'Grok FRAIM skill (~/.grok/skills/fraim/SKILL.md)');
75
+ installOrUpdateManagedFile(path_1.default.join(grokDir, 'skills', 'fraim', 'SKILL.md'), (0, ide_invocation_surfaces_1.buildGrokSkillContent)(), 'Grok FRAIM skill (~/.grok/skills/fraim/SKILL.md)');
74
76
  }
75
77
  const geminiDir = path_1.default.join(home, '.gemini');
76
78
  if (fs_1.default.existsSync(geminiDir)) {
77
- installFileIfMissing(path_1.default.join(geminiDir, 'commands', 'fraim.toml'), (0, ide_invocation_surfaces_1.buildGeminiCommandContent)(), 'Gemini CLI FRAIM command (~/.gemini/commands/fraim.toml)');
79
+ installOrUpdateManagedFile(path_1.default.join(geminiDir, 'commands', 'fraim.toml'), (0, ide_invocation_surfaces_1.buildGeminiCommandContent)(), 'Gemini CLI FRAIM command (~/.gemini/commands/fraim.toml)');
78
80
  }
79
81
  const antigravityDir = path_1.default.join(home, '.gemini', 'antigravity');
80
82
  if (fs_1.default.existsSync(antigravityDir)) {
81
- installFileIfMissing(path_1.default.join(antigravityDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildAntigravityCommandContent)(), 'Antigravity FRAIM command (~/.gemini/antigravity/commands/fraim.md)');
83
+ installOrUpdateManagedFile(path_1.default.join(antigravityDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildAntigravityCommandContent)(), 'Antigravity FRAIM command (~/.gemini/antigravity/commands/fraim.md)');
82
84
  }
83
85
  const windsurfDir = path_1.default.join(home, '.codeium', 'windsurf');
84
86
  if (fs_1.default.existsSync(windsurfDir)) {
85
- installFileIfMissing(path_1.default.join(windsurfDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildWindsurfCommandContent)(), 'Windsurf FRAIM command (~/.codeium/windsurf/commands/fraim.md)');
87
+ installOrUpdateManagedFile(path_1.default.join(windsurfDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildWindsurfCommandContent)(), 'Windsurf FRAIM command (~/.codeium/windsurf/commands/fraim.md)');
86
88
  }
87
89
  const kiroDir = path_1.default.join(home, '.kiro');
88
90
  if (fs_1.default.existsSync(kiroDir)) {
89
- installFileIfMissing(path_1.default.join(kiroDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildKiroCommandContent)(), 'Kiro FRAIM command (~/.kiro/commands/fraim.md)');
91
+ installOrUpdateManagedFile(path_1.default.join(kiroDir, 'commands', 'fraim.md'), (0, ide_invocation_surfaces_1.buildKiroCommandContent)(), 'Kiro FRAIM command (~/.kiro/commands/fraim.md)');
90
92
  }
91
93
  }
92
- function installFileIfMissing(filePath, content, displayName) {
93
- if (fs_1.default.existsSync(filePath)) {
94
- console.log(chalk_1.default.gray(` ${displayName.split('(')[0].trim()} already exists skipping`));
94
+ // These paths (~/.claude/skills/fraim/, ~/.codex/skills/fraim/, etc.) are
95
+ // FRAIM-exclusive — nothing else writes to them — so the file is always
96
+ // brought in line with the current generator output, the same contract
97
+ // `ensureAgentAdapterFiles()` already uses for the equivalent project-level
98
+ // files in agent-adapters.ts. Skipping on existence (issue #1242) permanently
99
+ // locked in stale content from older FRAIM releases with no way to self-heal.
100
+ function installOrUpdateManagedFile(filePath, content, displayName) {
101
+ const existing = fs_1.default.existsSync(filePath) ? fs_1.default.readFileSync(filePath, 'utf8') : null;
102
+ if (existing === content) {
103
+ console.log(chalk_1.default.gray(` ${displayName.split('(')[0].trim()} already up to date — skipping`));
95
104
  return;
96
105
  }
97
106
  fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
98
107
  fs_1.default.writeFileSync(filePath, content, 'utf8');
99
- console.log(chalk_1.default.green(` ✅ Installed ${displayName}`));
108
+ console.log(chalk_1.default.green(` ✅ ${existing === null ? 'Installed' : 'Updated'} ${displayName}`));
100
109
  }
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -119,5 +152,12 @@ async function syncUserLevelArtifacts(userFraimDir) {
119
152
  console.log(chalk_1.default.gray(` Target: ${baseDir}`));
120
153
  ensureUserLevelDirectories(baseDir);
121
154
  ensureUserLevelDependencies(baseDir);
155
+ // Issue #1201 (R4.2): repair global per-IDE FRAIM discovery artifacts
156
+ // (e.g. ~/.codex/skills/fraim/SKILL.md) on every `npx fraim sync --global`
157
+ // run, not only on `fraim add-ide` and first-run onboarding — closes the
158
+ // gap where a stale file written by an older generator was never repaired.
159
+ const { installGlobalRules, installSlashCommands } = await Promise.resolve().then(() => __importStar(require('./ide-global-integration')));
160
+ await installSlashCommands();
161
+ await installGlobalRules();
122
162
  console.log(chalk_1.default.green('✅ User-level FRAIM directory ready'));
123
163
  }
@@ -7,6 +7,7 @@ exports.packsDir = packsDir;
7
7
  exports.defaultCloneDir = defaultCloneDir;
8
8
  exports.resolvePackHome = resolvePackHome;
9
9
  exports.packReadRoots = packReadRoots;
10
+ exports.personalizedCapabilityReadRoots = personalizedCapabilityReadRoots;
10
11
  exports.migrateStrandedContent = migrateStrandedContent;
11
12
  exports.findStrandedLegacyContent = findStrandedLegacyContent;
12
13
  exports.gitUrlHasUserinfo = gitUrlHasUserinfo;
@@ -173,6 +174,45 @@ function resolvePackHome(layer) {
173
174
  function packReadRoots(layer) {
174
175
  return [resolvePackHome(layer).contentRoot];
175
176
  }
177
+ function displayPathForContentRoot(contentRoot) {
178
+ const userRoot = (0, project_fraim_paths_1.getUserFraimDirPath)();
179
+ const rel = path_1.default.relative(userRoot, contentRoot);
180
+ if (rel === '' || (rel && !rel.startsWith('..') && !path_1.default.isAbsolute(rel))) {
181
+ return (0, project_fraim_paths_1.getUserFraimDisplayPath)(rel);
182
+ }
183
+ return contentRoot.replace(/\\/g, '/');
184
+ }
185
+ /**
186
+ * Return personalized capability roots in merge order: org, manager, project.
187
+ *
188
+ * Callers that de-duplicate by key can iterate this array directly and let
189
+ * later entries overwrite earlier ones, matching capability resolution
190
+ * precedence where project overrides manager and manager overrides org.
191
+ */
192
+ function personalizedCapabilityReadRoots(projectRoot, capabilityDir) {
193
+ const roots = [];
194
+ for (const layer of ['org', 'manager']) {
195
+ const contentRoot = resolvePackHome(layer).contentRoot;
196
+ roots.push({
197
+ scope: layer,
198
+ contentRoot,
199
+ capabilityRoot: path_1.default.join(contentRoot, capabilityDir),
200
+ displayRoot: contentRoot,
201
+ displayPrefix: displayPathForContentRoot(contentRoot),
202
+ });
203
+ }
204
+ if (projectRoot) {
205
+ const contentRoot = (0, project_fraim_paths_1.getWorkspaceFraimPath)(projectRoot, 'personalized-employee');
206
+ roots.push({
207
+ scope: 'project',
208
+ contentRoot,
209
+ capabilityRoot: path_1.default.join(contentRoot, capabilityDir),
210
+ displayRoot: contentRoot,
211
+ displayPrefix: (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee'),
212
+ });
213
+ }
214
+ return roots;
215
+ }
176
216
  /**
177
217
  * Auto-migrate content stranded at the legacy standard path to the configured
178
218
  * contentRoot. Runs at most once per process per (layer + contentRoot) pair.
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildJobVisualization = buildJobVisualization;
4
+ const local_registry_resolver_1 = require("./utils/local-registry-resolver");
5
+ const job_parser_1 = require("./utils/job-parser");
6
+ function friendlyLabel(id) {
7
+ const PREFIXES = ['implement-', 'address-', 'spec-', 'design-', 'context-'];
8
+ let body = id;
9
+ for (const p of PREFIXES) {
10
+ if (body.startsWith(p) && body.length > p.length) {
11
+ body = body.slice(p.length);
12
+ break;
13
+ }
14
+ }
15
+ const spaced = body.replace(/-/g, ' ').trim();
16
+ if (!spaced)
17
+ return id;
18
+ return spaced[0].toUpperCase() + spaced.slice(1).toLowerCase();
19
+ }
20
+ function readSection(content, heading) {
21
+ const re = new RegExp(`(?:^|\\r?\\n)#{1,6} ${heading}[ \\t]*\\r?\\n([\\s\\S]*?)(?=\\r?\\n#{1,6} |\\r?\\n---|$)`);
22
+ const m = content.match(re);
23
+ if (!m)
24
+ return null;
25
+ const text = m[1]
26
+ .split(/\r?\n/)
27
+ .map((l) => l.trim())
28
+ .filter((l) => l && !l.startsWith('{{'))
29
+ .map((l) => l.replace(/^[-*]\s*/, ''))
30
+ .join(' ')
31
+ .trim();
32
+ return text || null;
33
+ }
34
+ function parseSkillIdFromInclude(line) {
35
+ const m = line.match(/\{\{include:skills\/([^}]+?)(?:\.md)?\}\}/);
36
+ if (!m)
37
+ return null;
38
+ return m[1].replace(/\.md$/, '');
39
+ }
40
+ function skillLabelFromId(skillId) {
41
+ const base = skillId.includes('/') ? skillId.split('/').pop() : skillId;
42
+ return base.replace(/-/g, ' ');
43
+ }
44
+ function isStub(content) {
45
+ return (content.includes('<!-- FRAIM_DISCOVERY_STUB -->') ||
46
+ content.includes('STUB:') ||
47
+ content.includes('<!-- STUB -->'));
48
+ }
49
+ function buildResolver(projectPath) {
50
+ // remoteContentResolver is required by LocalRegistryResolver but never reached
51
+ // for local repos: findRegistryPath now searches registry/ as a fallback layer,
52
+ // and readWorkspaceRegistryFile handles the direct path lookup.
53
+ return new local_registry_resolver_1.LocalRegistryResolver({
54
+ workspaceRoot: projectPath,
55
+ shouldFilter: isStub,
56
+ remoteContentResolver: async (registryPath) => {
57
+ throw new Error(`registry file not found locally: ${registryPath}`);
58
+ },
59
+ });
60
+ }
61
+ function toTitleCase(id) {
62
+ return id
63
+ .split('-')
64
+ .map((w) => w[0].toUpperCase() + w.slice(1).toLowerCase())
65
+ .join(' ');
66
+ }
67
+ function parseJobTitle(content, jobId) {
68
+ const m = content.match(/^#\s+(?:FRAIM Job:\s*)?(.+)/m);
69
+ if (m) {
70
+ const raw = m[1].trim();
71
+ if (/^[a-z0-9-]+$/.test(raw))
72
+ return toTitleCase(raw);
73
+ return raw;
74
+ }
75
+ return toTitleCase(jobId);
76
+ }
77
+ function parseJobLevelIntent(content) {
78
+ const m = content.match(/(?:^|\r?\n)## Intent[ \t]*\r?\n([\s\S]*?)(?=\r?\n#{1,6} |\r?\n---|$)/);
79
+ if (!m)
80
+ return null;
81
+ const lines = m[1].split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith('{{'));
82
+ const text = lines.map((l) => l.replace(/^[-*]\s*/, '')).join(' ').trim();
83
+ return text || null;
84
+ }
85
+ const EMPTY_RESPONSE = (jobId) => ({
86
+ jobId,
87
+ title: toTitleCase(jobId),
88
+ intent: null,
89
+ personalized: false,
90
+ phases: [],
91
+ });
92
+ async function buildJobVisualization(jobId, projectPath) {
93
+ const resolver = buildResolver(projectPath);
94
+ const registryPath = await resolver.findRegistryPath('jobs', jobId);
95
+ let jobFile;
96
+ try {
97
+ jobFile = await resolver.resolveFile(registryPath, {
98
+ includeMetadata: false,
99
+ stripMcpHeader: true,
100
+ });
101
+ }
102
+ catch {
103
+ return EMPTY_RESPONSE(jobId);
104
+ }
105
+ const content = jobFile.content;
106
+ if (!content?.trim())
107
+ return EMPTY_RESPONSE(jobId);
108
+ const title = parseJobTitle(content, jobId);
109
+ const intent = parseJobLevelIntent(content);
110
+ // Phase splitting reuses JobParser — the same parser the MCP proxy uses.
111
+ const jobDef = job_parser_1.JobParser.parseContent(content, jobId);
112
+ if (!jobDef || jobDef.phases.size === 0) {
113
+ return { jobId, title, intent, personalized: jobFile.personalized, scope: jobFile.scope, phases: [] };
114
+ }
115
+ const phases = await Promise.all([...jobDef.phases.entries()].map(async ([phaseId, phaseContent]) => {
116
+ const phaseIntent = readSection(phaseContent, 'Intent');
117
+ const phaseOutcome = readSection(phaseContent, 'Outcome');
118
+ const skillIds = [];
119
+ for (const line of phaseContent.split(/\r?\n/)) {
120
+ const id = parseSkillIdFromInclude(line);
121
+ if (id)
122
+ skillIds.push(id);
123
+ }
124
+ const skills = await Promise.all(skillIds.map(async (skillId) => {
125
+ try {
126
+ const skillFile = await resolver.resolveFile(`skills/${skillId}.md`, {
127
+ includeMetadata: false,
128
+ stripMcpHeader: true,
129
+ });
130
+ return {
131
+ id: skillId,
132
+ label: skillLabelFromId(skillId),
133
+ input: readSection(skillFile.content, 'Skill Input'),
134
+ output: readSection(skillFile.content, 'Skill Output'),
135
+ personalized: skillFile.personalized,
136
+ scope: skillFile.scope,
137
+ };
138
+ }
139
+ catch {
140
+ return { id: skillId, label: skillLabelFromId(skillId), input: null, output: null, personalized: false };
141
+ }
142
+ }));
143
+ return {
144
+ id: phaseId,
145
+ label: friendlyLabel(phaseId),
146
+ intent: phaseIntent,
147
+ outcome: phaseOutcome,
148
+ personalized: jobFile.personalized,
149
+ scope: jobFile.scope,
150
+ skills,
151
+ };
152
+ }));
153
+ return {
154
+ jobId,
155
+ title,
156
+ intent,
157
+ personalized: jobFile.personalized,
158
+ scope: jobFile.scope,
159
+ phases,
160
+ };
161
+ }
@@ -130,9 +130,12 @@ class JobParser {
130
130
  }
131
131
  static parseSimpleJob(filePath, content) {
132
132
  const jobName = (0, path_1.basename)(filePath, '.md');
133
- const metadata = {
134
- name: jobName
135
- };
133
+ const metadata = { name: jobName };
134
+ // If the content contains ## Phase: sections, parse them the same way as
135
+ // phase-based jobs so callers always get a populated phases Map.
136
+ if (/^##\s+Phase:/im.test(content)) {
137
+ return this.parsePhaseBasedJob(filePath, content, metadata, 0);
138
+ }
136
139
  return {
137
140
  metadata,
138
141
  overview: content.trim(),
@@ -101,6 +101,7 @@ class LocalRegistryResolver {
101
101
  (0, path_1.join)(this.managerLocalRoot, dir),
102
102
  (0, path_1.join)(this.managerCacheRoot, dir),
103
103
  (0, path_1.join)(this.orgCacheRoot, dir),
104
+ (0, path_1.join)(this.workspaceRoot, 'registry', dir),
104
105
  ];
105
106
  for (const root of searchRoots) {
106
107
  const found = this.searchFileRecursively(root, baseName);
@@ -2272,19 +2272,9 @@ class FraimLocalMCPServer {
2272
2272
  try {
2273
2273
  const projectRoot = this.findProjectRoot();
2274
2274
  const uniqueLocalItems = new Map();
2275
- // Issue #1002 R3: list the manager's own jobs too, not only the
2276
- // project's. A job authored at the manager level resolves by name
2277
- // from every project, so omitting it here made it invisible in the
2278
- // one place the manager looks to find out what their employee can do.
2279
2275
  // Lowest precedence first, so a project job of the same name wins.
2280
- const { resolvePackHome: _resolvePackHome } = require('../cli/utils/pack-home');
2281
- const managerJobsDir = (0, path_1.join)(_resolvePackHome('manager').contentRoot, 'jobs');
2282
- const jobDirsByLevel = [
2283
- { dir: managerJobsDir, level: 'manager' },
2284
- ...(projectRoot
2285
- ? [{ dir: (0, path_1.join)(projectRoot, 'fraim', 'personalized-employee', 'jobs'), level: 'project' }]
2286
- : []),
2287
- ];
2276
+ const jobDirsByLevel = (0, pack_home_1.personalizedCapabilityReadRoots)(projectRoot, 'jobs')
2277
+ .map((root) => ({ dir: root.capabilityRoot, level: root.scope }));
2288
2278
  for (const { dir: jobsDir, level } of jobDirsByLevel) {
2289
2279
  const personalizedJobsDir = jobsDir;
2290
2280
  if ((0, fs_1.existsSync)(personalizedJobsDir)) {
@@ -2335,7 +2325,9 @@ class FraimLocalMCPServer {
2335
2325
  : '';
2336
2326
  const levelWord = item.level === 'project'
2337
2327
  ? 'this project only'
2338
- : 'yours, every project';
2328
+ : item.level === 'org'
2329
+ ? 'everyone in the organization'
2330
+ : 'yours, every project';
2339
2331
  combinedText += `- **${item.name}**${categorySuffix} - ${levelWord}\n`;
2340
2332
  }
2341
2333
  response.result.content[0].text = combinedText;
@@ -160,7 +160,7 @@ class McpService {
160
160
  ? 'local-proxy'
161
161
  : 'remote';
162
162
  return {
163
- tools: (0, tool_schemas_1.getToolDefinitions)(this.getAvailableJobs(), { surface })
163
+ tools: (0, tool_schemas_1.getToolDefinitions)({ surface })
164
164
  };
165
165
  }
166
166
  handleListResources() {
@@ -794,9 +794,5 @@ class McpService {
794
794
  throw new Error(`Unknown raw tool: ${tool}`);
795
795
  }
796
796
  }
797
- getAvailableJobs() {
798
- const jobs = Array.from(this.registryService.getFileIndex().values()).filter(f => f.type === 'job' && !f.isStub);
799
- return Array.from(new Set(jobs.map(f => f.name.replace('.md', ''))));
800
- }
801
797
  }
802
798
  exports.McpService = McpService;
@@ -9,7 +9,7 @@ const path_1 = require("path");
9
9
  const crypto_1 = __importDefault(require("crypto"));
10
10
  const stub_generator_1 = require("../core/utils/stub-generator");
11
11
  const job_parser_1 = require("../core/utils/job-parser");
12
- const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
12
+ const pack_home_1 = require("../cli/utils/pack-home");
13
13
  class RegistryService {
14
14
  constructor() {
15
15
  this.fileIndex = new Map();
@@ -523,10 +523,6 @@ class RegistryService {
523
523
  return files.sort((a, b) => a.path.localeCompare(b.path));
524
524
  }
525
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
526
  const nodes = [];
531
527
  const edges = [];
532
528
  const seenNodeIds = new Set();
@@ -537,8 +533,12 @@ class RegistryService {
537
533
  seenNodeIds.add(node.id);
538
534
  nodes.push(node);
539
535
  };
540
- const skillsRoot = (0, path_1.join)(personalizedRoot, 'skills');
541
- if ((0, fs_1.existsSync)(skillsRoot)) {
536
+ const skillRoots = (0, pack_home_1.personalizedCapabilityReadRoots)(process.cwd(), 'skills');
537
+ for (const root of skillRoots) {
538
+ const skillsRoot = root.capabilityRoot;
539
+ if (!(0, fs_1.existsSync)(skillsRoot)) {
540
+ continue;
541
+ }
542
542
  for (const relativePath of this.collectMarkdownRelativePaths(skillsRoot)) {
543
543
  const normalizedRelativePath = normalize(relativePath);
544
544
  const skillId = normalizedRelativePath.replace(/\.md$/, '');
@@ -551,13 +551,17 @@ class RegistryService {
551
551
  category,
552
552
  group: skillId.includes('/') ? skillId.slice(0, skillId.lastIndexOf('/')) : skillId,
553
553
  role: null,
554
- path: (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`personalized-employee/skills/${normalizedRelativePath}`),
554
+ path: `${root.displayPrefix.replace(/\/$/, '')}/skills/${normalizedRelativePath}`,
555
555
  shadowing: (0, fs_1.existsSync)(baselinePath)
556
556
  });
557
557
  }
558
558
  }
559
- const jobsRoot = (0, path_1.join)(personalizedRoot, 'jobs');
560
- if ((0, fs_1.existsSync)(jobsRoot)) {
559
+ const jobRoots = (0, pack_home_1.personalizedCapabilityReadRoots)(process.cwd(), 'jobs');
560
+ for (const root of jobRoots) {
561
+ const jobsRoot = root.capabilityRoot;
562
+ if (!(0, fs_1.existsSync)(jobsRoot)) {
563
+ continue;
564
+ }
561
565
  for (const relativePath of this.collectMarkdownRelativePaths(jobsRoot)) {
562
566
  const normalizedRelativePath = normalize(relativePath);
563
567
  const jobPathWithoutExt = normalizedRelativePath.replace(/\.md$/, '');
@@ -574,7 +578,7 @@ class RegistryService {
574
578
  category,
575
579
  group: parts.slice(0, -1).join('/') || category,
576
580
  role: category === 'ai-manager' ? 'ai-manager' : 'ai-employee',
577
- path: (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`personalized-employee/jobs/${normalizedRelativePath}`),
581
+ path: `${root.displayPrefix.replace(/\/$/, '')}/jobs/${normalizedRelativePath}`,
578
582
  shadowing: (0, fs_1.existsSync)(baselinePath)
579
583
  });
580
584
  const includes = [...content.matchAll(/\{\{include:skills\/([^}]+)\.md\}\}/g)];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.275",
3
+ "version": "2.0.277",
4
4
  "description": "FRAIM core CLI and MCP package.",
5
5
  "main": "index.js",
6
6
  "bin": {