fraim 2.0.348 → 2.0.349
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.
|
@@ -50,6 +50,7 @@ class LocalRegistryResolver {
|
|
|
50
50
|
constructor(options) {
|
|
51
51
|
this.workspaceRoot = options.workspaceRoot;
|
|
52
52
|
this.effectiveFraimDir = options.effectiveFraimDir;
|
|
53
|
+
this.userLevelOnly = options.userLevelOnly ?? false;
|
|
53
54
|
this.remoteContentResolver = options.remoteContentResolver;
|
|
54
55
|
this.parser = new inheritance_parser_1.InheritanceParser(options.maxDepth);
|
|
55
56
|
this.shouldFilter = options.shouldFilter;
|
|
@@ -87,7 +88,14 @@ class LocalRegistryResolver {
|
|
|
87
88
|
* Check if a local override exists for the given path
|
|
88
89
|
*/
|
|
89
90
|
hasLocalOverride(path) {
|
|
90
|
-
return (0, fs_1.existsSync)(this.getOverridePath(path));
|
|
91
|
+
return !this.userLevelOnly && (0, fs_1.existsSync)(this.getOverridePath(path));
|
|
92
|
+
}
|
|
93
|
+
/** Whether an effective personalized layer should replace a remote baseline response. */
|
|
94
|
+
hasPersonalizedOverride(path) {
|
|
95
|
+
return this.hasLocalOverride(path)
|
|
96
|
+
|| this.readManagerLocalOverlayFile(path) !== null
|
|
97
|
+
|| this.readManagerOverlayFile(path) !== null
|
|
98
|
+
|| this.readOrgOverlayFile(path) !== null;
|
|
91
99
|
}
|
|
92
100
|
/**
|
|
93
101
|
* Find the actual relative path for a registry item by searching subdirectories.
|
|
@@ -108,11 +116,11 @@ class LocalRegistryResolver {
|
|
|
108
116
|
// `jobs/<category>/<name>.md`, so the job was reported not found even
|
|
109
117
|
// though the file existed.
|
|
110
118
|
const searchRoots = [
|
|
111
|
-
this.getFraimPath('personalized-employee', dir),
|
|
119
|
+
...(!this.userLevelOnly ? [this.getFraimPath('personalized-employee', dir)] : []),
|
|
112
120
|
(0, path_1.join)(this.managerLocalRoot, dir),
|
|
113
121
|
(0, path_1.join)(this.managerCacheRoot, dir),
|
|
114
122
|
(0, path_1.join)(this.orgCacheRoot, dir),
|
|
115
|
-
(0, path_1.join)(this.workspaceRoot, 'registry', dir),
|
|
123
|
+
...(!this.userLevelOnly ? [(0, path_1.join)(this.workspaceRoot, 'registry', dir)] : []),
|
|
116
124
|
// Installed projects discover categorized capabilities in machine catalogs.
|
|
117
125
|
this.getSyncedCatalogRoot('ai-employee', dir),
|
|
118
126
|
...(type !== 'skills' ? [this.getSyncedCatalogRoot('ai-manager', dir)] : []),
|
|
@@ -326,6 +334,8 @@ class LocalRegistryResolver {
|
|
|
326
334
|
}
|
|
327
335
|
}
|
|
328
336
|
readWorkspaceRegistryFile(path) {
|
|
337
|
+
if (this.userLevelOnly)
|
|
338
|
+
return null;
|
|
329
339
|
const normalizedPath = path.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
330
340
|
const registryRoot = (0, path_1.join)(this.workspaceRoot, 'registry');
|
|
331
341
|
const registryPath = (0, path_1.join)(registryRoot, ...normalizedPath.split('/'));
|
|
@@ -1535,6 +1535,7 @@ function readRejectedProposals(workspaceRoot, userId) {
|
|
|
1535
1535
|
return [];
|
|
1536
1536
|
const out = [];
|
|
1537
1537
|
const seen = new Set();
|
|
1538
|
+
const projectOwnerPath = (0, owner_identity_1.ownerPath)((0, project_fraim_paths_1.getWorkspaceFraimDir)(workspaceRoot));
|
|
1538
1539
|
const headingRe = /^## (Rejected|Superseded): (.+)$/m;
|
|
1539
1540
|
const reasonRe = /^Reason: (.+)$/m;
|
|
1540
1541
|
const revisitRe = /^Revisit when: (.+)$/m;
|
|
@@ -1567,7 +1568,9 @@ function readRejectedProposals(workspaceRoot, userId) {
|
|
|
1567
1568
|
let proposal = {};
|
|
1568
1569
|
if (json) {
|
|
1569
1570
|
try {
|
|
1570
|
-
|
|
1571
|
+
const parsed = JSON.parse(json[1]);
|
|
1572
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
1573
|
+
proposal = parsed;
|
|
1571
1574
|
}
|
|
1572
1575
|
catch {
|
|
1573
1576
|
// Keep the human-readable half; the JSON only enriches it.
|
|
@@ -1576,12 +1579,24 @@ function readRejectedProposals(workspaceRoot, userId) {
|
|
|
1576
1579
|
const decision = (proposal.decision || {});
|
|
1577
1580
|
const content = (proposal.content || {});
|
|
1578
1581
|
const trace = (content.trace || {});
|
|
1582
|
+
let targetOwner;
|
|
1583
|
+
try {
|
|
1584
|
+
const owner = proposal.target?.owner;
|
|
1585
|
+
(0, owner_identity_1.validateOwner)(owner);
|
|
1586
|
+
targetOwner = { level: owner.level, path: owner.path };
|
|
1587
|
+
}
|
|
1588
|
+
catch {
|
|
1589
|
+
// Older/damaged targets remain visible as unknown; never infer
|
|
1590
|
+
// their project from the shared history file's location.
|
|
1591
|
+
}
|
|
1592
|
+
if (targetOwner?.level === 'Project' && targetOwner.path !== projectOwnerPath)
|
|
1593
|
+
continue;
|
|
1579
1594
|
const claimKey = typeof proposal.claimKey === 'string' && proposal.claimKey
|
|
1580
1595
|
? proposal.claimKey
|
|
1581
1596
|
: parts[1] || heading[2];
|
|
1582
1597
|
// The same claim can appear in more than one configured base. The first
|
|
1583
1598
|
// base supplies display metadata for that same recorded decision.
|
|
1584
|
-
const key =
|
|
1599
|
+
const key = JSON.stringify([claimKey, String(decision.at || ''), targetOwner?.level, targetOwner?.path]);
|
|
1585
1600
|
if (seen.has(key))
|
|
1586
1601
|
continue;
|
|
1587
1602
|
seen.add(key);
|
|
@@ -1596,6 +1611,7 @@ function readRejectedProposals(workspaceRoot, userId) {
|
|
|
1596
1611
|
rejectedAt: typeof decision.at === 'string' ? decision.at : '',
|
|
1597
1612
|
...(typeof trace.recurrences === 'number' ? { recurrencesAtRejection: trace.recurrences } : {}),
|
|
1598
1613
|
displayPath,
|
|
1614
|
+
...(targetOwner ? { targetOwner } : {}),
|
|
1599
1615
|
});
|
|
1600
1616
|
}
|
|
1601
1617
|
}
|
|
@@ -1657,8 +1657,7 @@ class FraimLocalMCPServer {
|
|
|
1657
1657
|
if (request.method !== 'tools/call' || typeof toolName !== 'string') {
|
|
1658
1658
|
return this.processResponseWithHydration(response, requestSessionId);
|
|
1659
1659
|
}
|
|
1660
|
-
const
|
|
1661
|
-
const resolver = projectRoot ? this.getRegistryResolver(requestSessionId) : null;
|
|
1660
|
+
const resolver = this.getRegistryResolver(requestSessionId);
|
|
1662
1661
|
let finalizedResponse = response;
|
|
1663
1662
|
// 1. Resolve top-level override if this tool maps to a specific file.
|
|
1664
1663
|
if (resolver) {
|
|
@@ -1670,14 +1669,14 @@ class FraimLocalMCPServer {
|
|
|
1670
1669
|
else if (toolName === 'get_fraim_job')
|
|
1671
1670
|
registryPath = await resolver.findRegistryPath('jobs', args.job);
|
|
1672
1671
|
if (registryPath) {
|
|
1673
|
-
const localOverride = resolver.
|
|
1672
|
+
const localOverride = resolver.hasPersonalizedOverride(registryPath);
|
|
1674
1673
|
const remoteFailed = Boolean(response.error);
|
|
1675
1674
|
if (response.error) {
|
|
1676
1675
|
this.log(`[req:${requestId}] Remote error for ${registryPath}: code=${response.error.code}`);
|
|
1677
1676
|
}
|
|
1678
1677
|
if (localOverride || remoteFailed) {
|
|
1679
1678
|
try {
|
|
1680
|
-
// personalized
|
|
1679
|
+
// Every configured personalized layer takes priority over the remote baseline.
|
|
1681
1680
|
const resolved = await resolver.resolveFile(registryPath).catch(() => null);
|
|
1682
1681
|
if (resolved?.content) {
|
|
1683
1682
|
const newResponse = {
|
|
@@ -1990,75 +1989,60 @@ class FraimLocalMCPServer {
|
|
|
1990
1989
|
const { getEffectiveFraimDir } = require('../core/utils/project-fraim-paths');
|
|
1991
1990
|
const effectiveFraimDir = projectRoot
|
|
1992
1991
|
? getEffectiveFraimDir(projectRoot)
|
|
1993
|
-
:
|
|
1992
|
+
: (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
1994
1993
|
if (effectiveFraimDir) {
|
|
1995
1994
|
this.log(`📂 Effective FRAIM dir: ${effectiveFraimDir}`);
|
|
1996
1995
|
}
|
|
1997
|
-
if
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
})
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
shouldFilter: (content) => this.isStub(content),
|
|
2026
|
-
remoteContentResolver: async (path) => {
|
|
2027
|
-
// Fetch parent content from remote for inheritance
|
|
2028
|
-
this.log(`🔄 Remote content resolver: fetching ${path}`);
|
|
2029
|
-
this.log(`🔄 Fetching raw file content: ${path}`);
|
|
2030
|
-
const request = {
|
|
2031
|
-
jsonrpc: '2.0',
|
|
2032
|
-
id: (0, crypto_1.randomUUID)(),
|
|
2033
|
-
method: 'tools/call',
|
|
2034
|
-
params: {
|
|
2035
|
-
name: 'get_fraim_file',
|
|
2036
|
-
arguments: {
|
|
2037
|
-
path,
|
|
2038
|
-
raw: true
|
|
2039
|
-
}
|
|
1996
|
+
// Determine if we need effectiveFraimDir override.
|
|
1997
|
+
// If the effective dir is user-level (~/.fraim/), it's NOT under projectRoot/fraim/,
|
|
1998
|
+
// so we pass it as effectiveFraimDir to bypass the getWorkspaceFraimPath logic.
|
|
1999
|
+
const workspaceRoot = projectRoot || (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
2000
|
+
const projectFraimDir = projectRoot ? (0, path_1.join)(projectRoot, 'fraim') : '';
|
|
2001
|
+
const needsEffectiveDirOverride = effectiveFraimDir && effectiveFraimDir !== projectFraimDir;
|
|
2002
|
+
return new local_registry_resolver_1.LocalRegistryResolver({
|
|
2003
|
+
workspaceRoot,
|
|
2004
|
+
userLevelOnly: !projectRoot,
|
|
2005
|
+
...(needsEffectiveDirOverride ? { effectiveFraimDir } : {}),
|
|
2006
|
+
// Issue #1043 round 2: overlay roots are the layers' homes.
|
|
2007
|
+
managerLocalRoot: (0, pack_home_1.resolvePackHome)('manager', { migrateLegacy: false }).contentRoot,
|
|
2008
|
+
managerCacheRoot: (0, pack_home_1.resolvePackHome)('manager', { migrateLegacy: false }).contentRoot,
|
|
2009
|
+
orgCacheRoot: (0, pack_home_1.resolvePackHome)('org', { migrateLegacy: false }).contentRoot,
|
|
2010
|
+
shouldFilter: (content) => this.isStub(content),
|
|
2011
|
+
remoteContentResolver: async (path) => {
|
|
2012
|
+
// Fetch parent content from remote for inheritance
|
|
2013
|
+
this.log(`🔄 Remote content resolver: fetching ${path}`);
|
|
2014
|
+
this.log(`🔄 Fetching raw file content: ${path}`);
|
|
2015
|
+
const request = {
|
|
2016
|
+
jsonrpc: '2.0',
|
|
2017
|
+
id: (0, crypto_1.randomUUID)(),
|
|
2018
|
+
method: 'tools/call',
|
|
2019
|
+
params: {
|
|
2020
|
+
name: 'get_fraim_file',
|
|
2021
|
+
arguments: {
|
|
2022
|
+
path,
|
|
2023
|
+
raw: true
|
|
2040
2024
|
}
|
|
2041
|
-
};
|
|
2042
|
-
this.applyRequestSessionId(request, requestSessionId);
|
|
2043
|
-
const response = await this._doProxyToRemote(request);
|
|
2044
|
-
if (response.error) {
|
|
2045
|
-
this.logError(`❌ Remote content resolver failed: ${response.error.message}`);
|
|
2046
|
-
throw new Error(`Failed to fetch parent: ${response.error.message}`);
|
|
2047
2025
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2026
|
+
};
|
|
2027
|
+
this.applyRequestSessionId(request, requestSessionId);
|
|
2028
|
+
const response = await this._doProxyToRemote(request);
|
|
2029
|
+
if (response.error) {
|
|
2030
|
+
this.logError(`❌ Remote content resolver failed: ${response.error.message}`);
|
|
2031
|
+
throw new Error(`Failed to fetch parent: ${response.error.message}`);
|
|
2032
|
+
}
|
|
2033
|
+
// Extract content from MCP response format
|
|
2034
|
+
if (response.result?.content && Array.isArray(response.result.content)) {
|
|
2035
|
+
const textContent = response.result.content.find((c) => c.type === 'text');
|
|
2036
|
+
if (textContent?.text) {
|
|
2037
|
+
this.log(`✅ Remote content resolver: fetched ${textContent.text.length} chars`);
|
|
2038
|
+
return textContent.text;
|
|
2055
2039
|
}
|
|
2056
|
-
this.logError(`❌ Remote content resolver: no content in response for ${path}`);
|
|
2057
|
-
this.logError(`Response: ${JSON.stringify(response, null, 2)}`);
|
|
2058
|
-
throw new Error(`No content in remote response for ${path}`);
|
|
2059
2040
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2041
|
+
this.logError(`❌ Remote content resolver: no content in response for ${path}`);
|
|
2042
|
+
this.logError(`Response: ${JSON.stringify(response, null, 2)}`);
|
|
2043
|
+
throw new Error(`No content in remote response for ${path}`);
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2062
2046
|
}
|
|
2063
2047
|
/**
|
|
2064
2048
|
* Check if content is a "stub" indicating local version should be ignored.
|
|
@@ -201,6 +201,7 @@ Example:
|
|
|
201
201
|
description: `Get a specific skill, rule, or reference file from the FRAIM registry by path.
|
|
202
202
|
|
|
203
203
|
For running FRAIM jobs, use get_fraim_job instead — do NOT call get_fraim_file for job execution.
|
|
204
|
+
For read-only diagnosis or delivery verification, use get_fraim_file({ path: "jobs/<category>/<job>.md", raw: true }). This does not start a job; the local proxy resolves effective overrides, inheritance, includes, and template values.
|
|
204
205
|
|
|
205
206
|
Examples:
|
|
206
207
|
- get_fraim_file({ path: "skills/communication/active-listening.md" })
|
|
@@ -226,7 +227,7 @@ Examples:
|
|
|
226
227
|
name: 'get_fraim_job',
|
|
227
228
|
description: `Execute a named FRAIM job after explicit FRAIM activation. Call this tool whenever the user asks to run, start, or execute a named FRAIM job. Returns phased instructions — follow each phase and call seekMentoring to advance phases.
|
|
228
229
|
|
|
229
|
-
|
|
230
|
+
Use this tool to start job execution, not to inspect candidate jobs. Read-only inspection uses get_fraim_file with the catalog job path and raw: true.
|
|
230
231
|
|
|
231
232
|
Examples:
|
|
232
233
|
- get_fraim_job({ job: "feature-specification" })
|