fraim 2.0.346 → 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.
Files changed (34) hide show
  1. package/dist/registry/scripts/learning/completion-state.js +80 -4
  2. package/dist/registry/scripts/learning/corpus-audit.js +7 -7
  3. package/dist/registry/scripts/learning/entry-authoring.js +181 -0
  4. package/dist/registry/scripts/learning/entry-format.js +10 -1
  5. package/dist/registry/scripts/learning/outcome-assessment.js +162 -0
  6. package/dist/registry/scripts/learning/outcome-types.js +92 -0
  7. package/dist/registry/scripts/learning/owner-identity.js +11 -0
  8. package/dist/registry/scripts/learning/proposal-queue.js +81 -0
  9. package/dist/registry/scripts/learning/run-application.js +1003 -0
  10. package/dist/registry/scripts/learning/run-publication.js +364 -0
  11. package/dist/registry/scripts/learning/run-record.js +895 -0
  12. package/dist/registry/scripts/learning-usage/learning-source-record.js +131 -0
  13. package/dist/src/cli/commands/learning.js +64 -0
  14. package/dist/src/cli/doctor/checks/scripts-checks.js +53 -1
  15. package/dist/src/cli/fraim.js +2 -0
  16. package/dist/src/cli/learning/inventory.js +285 -0
  17. package/dist/src/cli/learning/queue-operations.js +199 -0
  18. package/dist/src/cli/learning/runtime.js +82 -0
  19. package/dist/src/cli/learning/usage.js +277 -0
  20. package/dist/src/core/fraim-config-schema.generated.js +0 -4
  21. package/dist/src/core/utils/local-registry-resolver.js +13 -3
  22. package/dist/src/local-mcp-server/learning-context-builder.js +109 -379
  23. package/dist/src/local-mcp-server/learning-entry-format.js +25 -0
  24. package/dist/src/local-mcp-server/learning-usage-analysis.js +12 -410
  25. package/dist/src/local-mcp-server/learning-usage-eligibility.js +8 -14
  26. package/dist/src/local-mcp-server/learning-usage-projection.js +5 -22
  27. package/dist/src/local-mcp-server/learning-usage-store.js +93 -393
  28. package/dist/src/local-mcp-server/learning-usage-verify.js +9 -40
  29. package/dist/src/local-mcp-server/stdio-server.js +50 -114
  30. package/dist/src/mcp/tool-schemas.js +2 -1
  31. package/package.json +9 -1
  32. package/dist/src/local-mcp-server/learning-firing-parser.js +0 -247
  33. package/dist/src/local-mcp-server/learning-usage-attestation.js +0 -238
  34. package/dist/src/local-mcp-server/learning-usage-command.js +0 -518
@@ -0,0 +1,364 @@
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.publishRunFile = publishRunFile;
7
+ exports.verifyCapabilityDelivery = verifyCapabilityDelivery;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_child_process_1 = require("node:child_process");
11
+ const proposal_queue_1 = require("./proposal-queue");
12
+ const owner_identity_1 = require("./owner-identity");
13
+ const run_record_1 = require("./run-record");
14
+ const { learningEntryBlocks } = require('./entry-format');
15
+ function matchesLearningPublication(body, proof) {
16
+ body = body.replace(/\r\n/g, '\n');
17
+ if (/^<{7}/m.test(body))
18
+ return false;
19
+ const entries = learningEntryBlocks(body);
20
+ if (entries.some(entry => !entry.id) || new Set(entries.map(entry => entry.id)).size !== entries.length)
21
+ return false;
22
+ const matches = entries.filter(entry => entry.id === proof.entryId);
23
+ return proof.after === null ? matches.length === 0 : matches.length === 1 && matches[0].text === proof.after.replace(/\r\n/g, '\n');
24
+ }
25
+ /** Fetch a named remote snapshot; never infer effective delivery from local HEAD. */
26
+ function fetchRemoteHead(root) {
27
+ const head = (0, node_child_process_1.spawnSync)('git', ['ls-remote', 'origin', 'HEAD'], { cwd: root, encoding: 'utf8', timeout: 30000, windowsHide: true });
28
+ const oid = head.status === 0 ? head.stdout.trim().match(/^([a-f0-9]{40,64})\s+HEAD$/)?.[1] : undefined;
29
+ if (!oid)
30
+ return undefined;
31
+ const fetched = (0, node_child_process_1.spawnSync)('git', ['fetch', 'origin', oid], { cwd: root, encoding: 'utf8', timeout: 30000, windowsHide: true });
32
+ return fetched.status === 0 ? oid : undefined;
33
+ }
34
+ /** A shared source capability is delivered only after its real project PR has merged with this content. */
35
+ function verifySharedProjectPublication(projectPath, file, expectedHash, publicationRef, callers = []) {
36
+ if (!publicationRef)
37
+ return { published: false, reference: file };
38
+ const remote = (0, node_child_process_1.spawnSync)('git', ['config', '--get', 'remote.origin.url'], { cwd: projectPath, encoding: 'utf8', timeout: 10000, windowsHide: true });
39
+ if (remote.status !== 0 || !remote.stdout.trim())
40
+ throw new Error('project-publication-repository-unavailable');
41
+ const repository = new URL(remote.stdout.trim().replace(/^git@([^:]+):/, 'https://$1/').replace(/\.git$/, ''));
42
+ if (!/^\d+$/.test(publicationRef)) {
43
+ const reference = new URL(publicationRef);
44
+ if (reference.host !== repository.host || !reference.pathname.startsWith(repository.pathname.replace(/\/$/, '') + '/pull/') || !/\/pull\/\d+$/.test(reference.pathname))
45
+ throw new Error('publication-reference-owner-mismatch');
46
+ }
47
+ const found = (0, node_child_process_1.spawnSync)('gh', ['pr', 'view', publicationRef, '--repo', remote.stdout.trim(), '--json', 'state,url,mergeCommit'], { encoding: 'utf8', timeout: 30000, windowsHide: true });
48
+ if (found.status !== 0)
49
+ return { published: false, reference: publicationRef };
50
+ const pr = JSON.parse(found.stdout);
51
+ if (pr.state !== 'MERGED' || !/^[a-f0-9]{40,64}$/.test(pr.mergeCommit?.oid || ''))
52
+ return { published: false, reference: pr.url || publicationRef };
53
+ const fetched = (0, node_child_process_1.spawnSync)('git', ['fetch', 'origin', pr.mergeCommit.oid], { cwd: projectPath, encoding: 'utf8', timeout: 30000, windowsHide: true });
54
+ if (fetched.status !== 0)
55
+ return { published: false, reference: pr.url };
56
+ const effectiveHead = fetchRemoteHead(projectPath);
57
+ if (!effectiveHead)
58
+ return { published: false, reference: pr.url };
59
+ for (const artifact of [{ file, expectedHash }, ...callers]) {
60
+ const relative = node_path_1.default.relative(projectPath, artifact.file).replace(/\\/g, '/');
61
+ if (relative === '..' || relative.startsWith('../') || node_path_1.default.isAbsolute(relative))
62
+ throw new Error('publication-caller-outside-project');
63
+ const delivered = (0, node_child_process_1.spawnSync)('git', ['show', `${pr.mergeCommit.oid}:${relative}`], { cwd: projectPath, encoding: 'utf8', timeout: 30000, windowsHide: true });
64
+ const effective = (0, node_child_process_1.spawnSync)('git', ['show', `${effectiveHead}:${relative}`], { cwd: projectPath, encoding: 'utf8', timeout: 30000, windowsHide: true });
65
+ const current = node_fs_1.default.readFileSync(artifact.file, 'utf8');
66
+ if (delivered.status !== 0 || effective.status !== 0 || (0, proposal_queue_1.hashContent)(current) !== artifact.expectedHash
67
+ || (0, proposal_queue_1.hashContent)(delivered.stdout.replace(/\r\n/g, '\n')) !== (0, proposal_queue_1.hashContent)(current.replace(/\r\n/g, '\n'))
68
+ || (0, proposal_queue_1.hashContent)(effective.stdout.replace(/\r\n/g, '\n')) !== (0, proposal_queue_1.hashContent)(current.replace(/\r\n/g, '\n')))
69
+ return { published: false, reference: pr.url };
70
+ }
71
+ return { published: true, reference: pr.url };
72
+ }
73
+ /** Existing publishers run in a bounded process. Cloud confirmation reads the actual pack without syncing it. */
74
+ function publishRunFile(projectPath, file, home, operationKey, expectedHash, runtimeModulePath, entryProof) {
75
+ const relative = home.level === 'Project' && node_path_1.default.resolve(file) === node_path_1.default.join(home.root, 'config.json')
76
+ ? 'config.json' : node_path_1.default.relative(home.contentRoot, file).replace(/\\/g, '/');
77
+ if ((0, owner_identity_1.ownerArtifactPath)(home, relative) !== node_path_1.default.resolve(file))
78
+ throw new Error('invalid-publication-path');
79
+ if (!/^[a-f0-9]{64}$/.test(expectedHash) || (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(file, 'utf8')) !== expectedHash)
80
+ throw new Error('publication-content-conflict');
81
+ if (entryProof && (!/^L-[a-f0-9]{10}$/.test(entryProof.entryId) || (entryProof.after !== null && typeof entryProof.after !== 'string')
82
+ || !matchesLearningPublication(node_fs_1.default.readFileSync(file, 'utf8'), entryProof)))
83
+ throw new Error('publication-entry-conflict');
84
+ if (!/^[a-zA-Z0-9_.-]{1,240}$/.test(operationKey))
85
+ throw new Error('invalid-publication-operation');
86
+ if (!home.requiresPublish)
87
+ return { published: true, reference: file };
88
+ const layer = home.level.toLowerCase();
89
+ if (!['manager', 'org'].includes(layer))
90
+ throw new Error('project-publisher-unavailable');
91
+ const branchSuffix = `sleep-${operationKey}`;
92
+ if (home.backend === 'git' && home.gitUrl) {
93
+ const branch = `fraim-${layer}-update-${branchSuffix}`;
94
+ const found = (0, node_child_process_1.spawnSync)('gh', ['pr', 'view', branch, '--repo', home.gitUrl, '--json', 'state,url,mergeCommit'], { encoding: 'utf8', timeout: 30000, windowsHide: true });
95
+ if (found.status === 0) {
96
+ const pr = JSON.parse(found.stdout);
97
+ if (pr.state !== 'MERGED' || !/^[a-f0-9]{40,64}$/.test(pr.mergeCommit?.oid || ''))
98
+ return { published: false, reference: pr.url || branch };
99
+ const fetched = (0, node_child_process_1.spawnSync)('git', ['fetch', 'origin', pr.mergeCommit.oid], { cwd: home.root, encoding: 'utf8', timeout: 30000, windowsHide: true });
100
+ const packRelative = node_path_1.default.relative(home.root, file).replace(/\\/g, '/');
101
+ const delivered = (0, node_child_process_1.spawnSync)('git', ['show', `${pr.mergeCommit.oid}:${packRelative}`], { cwd: home.root, encoding: 'utf8', timeout: 30000, windowsHide: true });
102
+ const current = node_fs_1.default.readFileSync(file, 'utf8');
103
+ const matches = (body) => entryProof ? matchesLearningPublication(body, entryProof)
104
+ : (0, proposal_queue_1.hashContent)(body.replace(/\r\n/g, '\n')) === (0, proposal_queue_1.hashContent)(current.replace(/\r\n/g, '\n'));
105
+ if ((0, proposal_queue_1.hashContent)(current) !== expectedHash || fetched.status !== 0 || delivered.status !== 0 || !matches(delivered.stdout))
106
+ return { published: false, reference: pr.url };
107
+ // A merged snapshot proves this operation, but a later revert must
108
+ // not authorize retirement of a still-needed source.
109
+ const oid = fetchRemoteHead(home.root);
110
+ if (!oid)
111
+ return { published: false, reference: pr.url };
112
+ const effective = (0, node_child_process_1.spawnSync)('git', ['show', `${oid}:${packRelative}`], { cwd: home.root, encoding: 'utf8', timeout: 30000, windowsHide: true });
113
+ return { published: effective.status === 0 && matches(effective.stdout)
114
+ && (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(file, 'utf8')) === expectedHash, reference: pr.url };
115
+ }
116
+ }
117
+ const publisherPath = runtimeModulePath(projectPath, `cli/utils/${layer}-publish`);
118
+ const userConfigPath = runtimeModulePath(projectPath, 'cli/utils/user-config');
119
+ const script = `
120
+ const fs=require('node:fs');
121
+ const crypto=require('node:crypto');
122
+ const hash=value=>crypto.createHash('sha256').update(value).digest('hex');
123
+ const [publisherPath,userConfigPath,file,relative,layer,branchSuffix,expectedHash,backend,entryProofJson,entryFormatPath]=process.argv.slice(1);
124
+ const entryProof=JSON.parse(entryProofJson);
125
+ const {learningEntryBlocks}=require(entryFormatPath);
126
+ const matchesLearningPublication=${matchesLearningPublication.toString()};
127
+ const publisher=require(publisherPath);
128
+ const readConfig=require(userConfigPath).readUserFraimConfig;
129
+ const remote=(process.env.FRAIM_REMOTE_URL||'https://fraim.wellnessatwork.me').replace(/\\/$/,'');
130
+ async function confirmedCloud(){
131
+ const key=readConfig().apiKey;if(!key)throw Error('api-key-unavailable');
132
+ const response=await fetch(remote+'/api/'+layer+'/pack',{headers:{'x-api-key':key},signal:AbortSignal.timeout(30000)});
133
+ if(!response.ok)throw Error('pack-read-failed');
134
+ const pack=await response.json();
135
+ const matches=Array.isArray(pack.files)?pack.files.filter(item=>item.relativePath===relative):[];
136
+ return {published:matches.length===1&&typeof matches[0].content==='string'&&(entryProof?matchesLearningPublication(matches[0].content,entryProof):hash(matches[0].content)===expectedHash),reference:remote+'/api/'+layer+'/pack#'+encodeURIComponent(relative)};
137
+ }
138
+ (async()=>{
139
+ if(hash(fs.readFileSync(file,'utf8'))!==expectedHash)throw Error('publication-content-conflict');
140
+ if(backend==='fraim-cloud'){
141
+ const existing=await confirmedCloud();
142
+ if(existing.published){console.log('FRAIM_PUBLISH_RESULT='+JSON.stringify(existing));return;}
143
+ }
144
+ const publish=layer==='manager'?publisher.publishManagerArtifacts:publisher.publishOrgArtifacts;
145
+ const result=await publish([{relativePath:relative,content:fs.readFileSync(file,'utf8')}],{branchSuffix});
146
+ const confirmed=backend==='fraim-cloud'?await confirmedCloud():{published:false,reference:result.prUrl||result.branch||file};
147
+ if(hash(fs.readFileSync(file,'utf8'))!==expectedHash)throw Error('publication-content-conflict');
148
+ console.log('FRAIM_PUBLISH_RESULT='+JSON.stringify(confirmed));
149
+ })().catch(()=>{process.exitCode=1;});`;
150
+ const result = (0, node_child_process_1.spawnSync)(process.execPath, [...process.execArgv, '-e', script, publisherPath, userConfigPath, file, relative, layer, branchSuffix, expectedHash, home.backend, JSON.stringify(entryProof || null), require.resolve('./entry-format')], { cwd: projectPath, encoding: 'utf8', timeout: 120000, windowsHide: true });
151
+ const encoded = String(result.stdout || '').match(/^FRAIM_PUBLISH_RESULT=(.+)$/m)?.[1];
152
+ if (result.status !== 0 || !encoded)
153
+ throw new Error('publication-pending: delivery or read-back has not been confirmed');
154
+ if ((0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(file, 'utf8')) !== expectedHash)
155
+ throw new Error('publication-content-conflict');
156
+ return JSON.parse(encoded);
157
+ }
158
+ /** Verify actual resolver precedence and include traversal; a textual mention is not a loaded caller. */
159
+ async function verifyCapabilityDelivery(action, input, options) {
160
+ if (!action.delivery || !/^[a-f0-9]{64}$/.test(action.delivery.expectedHash) || !Array.isArray(action.delivery.callers))
161
+ throw new Error('capability-delivery-review-required');
162
+ const reader = action.delivery.reader;
163
+ if (action.target.owner.level === 'Project' && action.target.relativePath === 'config.json' && reader?.kind !== 'workspace-config')
164
+ throw new Error('workspace-config-reader-required');
165
+ if (reader) {
166
+ (0, run_record_1.validateDeliveryReader)(reader);
167
+ if (action.delivery.callers.length)
168
+ throw new Error('invalid-capability-reader-callers');
169
+ }
170
+ else if (!action.delivery.callers.length)
171
+ throw new Error('capability-delivery-review-required');
172
+ if ((input.expectedHash !== undefined && input.expectedHash !== action.delivery.expectedHash)
173
+ || (input.reader !== undefined && (0, run_record_1.canonical)(input.reader) !== (0, run_record_1.canonical)(action.delivery.reader))
174
+ || (input.callers !== undefined && (0, run_record_1.canonical)(input.callers) !== (0, run_record_1.canonical)(action.delivery.callers)))
175
+ throw new Error('capability-delivery-review-mismatch');
176
+ input = { ...input, ...action.delivery };
177
+ const home = options.resolveHome(action.target.owner);
178
+ (0, owner_identity_1.assertSameOwner)(action.target.owner, home.owner);
179
+ const target = (0, owner_identity_1.ownerArtifactPath)(home, action.target.relativePath);
180
+ if (!/^[a-f0-9]{64}$/.test(input?.expectedHash || '') || !Array.isArray(input.callers) || !node_fs_1.default.existsSync(target))
181
+ throw new Error('capability-verification-required');
182
+ const contentHash = (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(target, 'utf8'));
183
+ if (contentHash !== input.expectedHash)
184
+ return { published: false, reference: target, contentHash, reachable: false };
185
+ const load = (relative) => options.runtimeModule(options.projectPath, relative);
186
+ if (reader) {
187
+ const snapshots = new Map();
188
+ const capture = (file) => {
189
+ file = node_path_1.default.resolve(file);
190
+ let stat;
191
+ try {
192
+ stat = node_fs_1.default.lstatSync(file);
193
+ }
194
+ catch (error) {
195
+ if (error.code !== 'ENOENT')
196
+ throw error;
197
+ snapshots.set(file, { file, realPath: null, sha256: null });
198
+ return;
199
+ }
200
+ if (!stat.isFile() && !stat.isSymbolicLink())
201
+ throw new Error('invalid-capability-reader-file');
202
+ snapshots.set(file, { file, realPath: node_fs_1.default.realpathSync(file), sha256: (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(file, 'utf8')) });
203
+ };
204
+ const paths = load('core/utils/project-fraim-paths');
205
+ capture(target);
206
+ capture(paths.getWorkspaceConfigPath(options.projectPath));
207
+ if (reader.kind === 'workspace-config') {
208
+ if (home.level !== 'Project' || action.target.relativePath !== 'config.json'
209
+ || node_path_1.default.resolve(paths.getWorkspaceConfigPath(options.projectPath)) !== target)
210
+ throw new Error('invalid-workspace-config-target');
211
+ // Parse independently: the runtime readers intentionally fall back on malformed JSON.
212
+ // A malformed delivered file must never be certified solely because its fallback matches.
213
+ const raw = JSON.parse(node_fs_1.default.readFileSync(target, 'utf8'));
214
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
215
+ throw new Error('invalid-workspace-config');
216
+ const value = reader.key === 'learning.scoreThreshold'
217
+ ? load('local-mcp-server/learning-context-builder').getScoreThreshold(options.projectPath)
218
+ : load('local-mcp-server/learning-usage-store').resolveUsageLimits(options.projectPath)[reader.key.split('.').pop()];
219
+ if (value !== reader.expectedValue)
220
+ return { published: false, reference: target, contentHash, reachable: false };
221
+ }
222
+ else {
223
+ capture(node_path_1.default.join(paths.getUserFraimDirPath(), 'config.json'));
224
+ // Capture absent overrides too: creating one after verification invalidates delivery.
225
+ capture(node_path_1.default.join(paths.getWorkspaceFraimPath(options.projectPath), 'personalized-employee', action.target.relativePath));
226
+ const location = load('local-mcp-server/learning-context-builder').resolveTeamContextFile(options.projectPath, reader.key, { migrateLegacy: false });
227
+ if (!location.present || node_path_1.default.resolve(location.readPath) !== target || node_fs_1.default.realpathSync(location.readPath) !== node_fs_1.default.realpathSync(target))
228
+ return { published: false, reference: target, contentHash, reachable: false };
229
+ }
230
+ const localFiles = [...snapshots.values()].sort((a, b) => a.file.localeCompare(b.file));
231
+ const verifySnapshots = () => {
232
+ const currentHome = options.resolveHome(action.target.owner);
233
+ (0, owner_identity_1.assertSameOwner)(action.target.owner, currentHome.owner);
234
+ if ((0, owner_identity_1.ownerArtifactPath)(currentHome, action.target.relativePath) !== target)
235
+ throw new Error('capability-owner-home-changed');
236
+ for (const item of localFiles) {
237
+ if (item.sha256 === null) {
238
+ try {
239
+ node_fs_1.default.lstatSync(item.file);
240
+ }
241
+ catch (error) {
242
+ if (error.code === 'ENOENT')
243
+ continue;
244
+ throw error;
245
+ }
246
+ throw new Error('capability-local-content-changed');
247
+ }
248
+ if (node_fs_1.default.realpathSync(item.file) !== item.realPath || (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(item.file, 'utf8')) !== item.sha256)
249
+ throw new Error('capability-local-content-changed');
250
+ }
251
+ };
252
+ if (snapshots.get(target)?.sha256 !== contentHash)
253
+ throw new Error('capability-local-content-changed');
254
+ verifySnapshots();
255
+ if (home.requiresPublish && (!options.runtimeModulePath || !input.operationKey))
256
+ throw new Error('capability-publication-required');
257
+ const publication = home.requiresPublish ? publishRunFile(options.projectPath, target, home, input.operationKey, contentHash, options.runtimeModulePath) : { published: true, reference: target };
258
+ verifySnapshots();
259
+ return { ...publication, contentHash, reachable: true, localFiles };
260
+ }
261
+ const { LocalRegistryResolver } = load('core/utils/local-registry-resolver');
262
+ const { resolvePackHome } = load('cli/utils/pack-home');
263
+ const manager = resolvePackHome('manager', { migrateLegacy: false });
264
+ const org = resolvePackHome('org', { migrateLegacy: false });
265
+ const resolver = new LocalRegistryResolver({ workspaceRoot: options.projectPath,
266
+ managerLocalRoot: manager.contentRoot, managerCacheRoot: manager.contentRoot, orgCacheRoot: org.contentRoot,
267
+ remoteContentResolver: async () => { throw new Error('capability-not-available-locally'); },
268
+ });
269
+ const originalResolve = resolver.resolveFile.bind(resolver);
270
+ const snapshots = new Map();
271
+ const snapshot = (file, expectedContent) => {
272
+ const absolute = node_path_1.default.resolve(file);
273
+ const body = node_fs_1.default.readFileSync(absolute, 'utf8');
274
+ if (expectedContent !== undefined && (0, proposal_queue_1.hashContent)(body) !== (0, proposal_queue_1.hashContent)(expectedContent))
275
+ throw new Error('capability-local-content-changed');
276
+ const current = { file: absolute, realPath: node_fs_1.default.realpathSync(absolute), sha256: (0, proposal_queue_1.hashContent)(body) };
277
+ const prior = snapshots.get(absolute);
278
+ if (prior && (prior.sha256 !== current.sha256 || prior.realPath !== current.realPath))
279
+ throw new Error('capability-local-content-changed');
280
+ snapshots.set(absolute, current);
281
+ };
282
+ const snapshotResolvedInclude = (relative, resolved) => {
283
+ if (resolved.source !== 'local')
284
+ return;
285
+ const normalized = relative.replace(/\\/g, '/');
286
+ if (node_path_1.default.isAbsolute(normalized) || normalized.split('/').some(part => !part || part === '..' || part === '.'))
287
+ throw new Error('invalid-capability-include');
288
+ let candidates;
289
+ if (resolved.scope === 'manager')
290
+ candidates = [node_path_1.default.join(manager.contentRoot, normalized)];
291
+ else if (resolved.scope === 'org')
292
+ candidates = [node_path_1.default.join(org.contentRoot, normalized)];
293
+ else if (resolved.scope === 'project')
294
+ candidates = [node_path_1.default.join(options.projectPath, 'fraim/personalized-employee', normalized)];
295
+ else {
296
+ const parts = normalized.split('/');
297
+ const rolePath = ['jobs', 'workflows'].includes(parts[0]) && ['ai-employee', 'ai-manager'].includes(parts[1])
298
+ ? node_path_1.default.join(parts[1], parts[0], ...parts.slice(2)) : undefined;
299
+ candidates = [...(rolePath ? [node_path_1.default.join(options.projectPath, 'fraim', rolePath)] : []),
300
+ node_path_1.default.join(options.projectPath, 'fraim/ai-employee', normalized), node_path_1.default.join(options.projectPath, 'fraim/ai-manager', normalized),
301
+ node_path_1.default.join(options.projectPath, 'fraim', normalized), node_path_1.default.join(options.projectPath, 'registry', normalized)];
302
+ }
303
+ const source = candidates.find(file => node_fs_1.default.existsSync(file) && node_fs_1.default.statSync(file).isFile() && (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(file, 'utf8')) === (0, proposal_queue_1.hashContent)(resolved.content));
304
+ if (source)
305
+ snapshot(source, resolved.content);
306
+ };
307
+ snapshot(target);
308
+ const shared = action.target.owner.level === 'Project' && action.target.relativePath.startsWith('registry/');
309
+ const targetPath = shared ? action.target.relativePath.slice('registry/'.length) : action.target.relativePath;
310
+ const targetScope = shared ? undefined : action.target.owner.level.toLowerCase();
311
+ const loadedTarget = await originalResolve(targetPath, { includeMetadata: false, stripMcpHeader: true });
312
+ if (loadedTarget.scope !== targetScope || (0, proposal_queue_1.hashContent)(loadedTarget.content) !== contentHash)
313
+ return { published: false, reference: target, contentHash, reachable: false };
314
+ let reachable = true;
315
+ const sharedCallers = [];
316
+ for (const caller of input.callers) {
317
+ const callerHome = options.resolveHome(caller.owner);
318
+ (0, owner_identity_1.assertSameOwner)(caller.owner, callerHome.owner);
319
+ const callerFile = (0, owner_identity_1.ownerArtifactPath)(callerHome, caller.relativePath);
320
+ const sharedCaller = caller.owner.level === 'Project' && caller.relativePath.startsWith('registry/');
321
+ const callerPath = sharedCaller ? caller.relativePath.slice('registry/'.length) : caller.relativePath;
322
+ const loadedCaller = await originalResolve(callerPath, { includeMetadata: false, stripMcpHeader: true });
323
+ if (!node_fs_1.default.existsSync(callerFile) || loadedCaller.scope !== (sharedCaller ? undefined : caller.owner.level.toLowerCase())
324
+ || (0, proposal_queue_1.hashContent)(loadedCaller.content) !== (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(callerFile, 'utf8'))) {
325
+ reachable = false;
326
+ break;
327
+ }
328
+ snapshot(callerFile, loadedCaller.content);
329
+ if (sharedCaller)
330
+ sharedCallers.push({ file: callerFile, expectedHash: (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(callerFile, 'utf8')) });
331
+ let traversed = false;
332
+ resolver.resolveFile = async (relative, resolveOptions) => {
333
+ const resolved = await originalResolve(relative, resolveOptions);
334
+ snapshotResolvedInclude(relative, resolved);
335
+ if (relative === targetPath && resolved.scope === targetScope && (0, proposal_queue_1.hashContent)(resolved.content) === contentHash)
336
+ traversed = true;
337
+ return resolved;
338
+ };
339
+ const hydrated = await resolver.resolveIncludes(loadedCaller.content);
340
+ if (!traversed || /\{\{include:[^}]+\}\}/.test(hydrated)) {
341
+ reachable = false;
342
+ break;
343
+ }
344
+ }
345
+ resolver.resolveFile = originalResolve;
346
+ if (!reachable || (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(target, 'utf8')) !== contentHash)
347
+ return { published: false, reference: target, contentHash, reachable: false };
348
+ const localFiles = [...snapshots.values()].sort((a, b) => a.file.localeCompare(b.file));
349
+ const verifySnapshots = () => {
350
+ for (const item of localFiles)
351
+ if (node_fs_1.default.realpathSync(item.file) !== item.realPath || (0, proposal_queue_1.hashContent)(node_fs_1.default.readFileSync(item.file, 'utf8')) !== item.sha256)
352
+ throw new Error('capability-local-content-changed');
353
+ };
354
+ if (shared) {
355
+ const publication = verifySharedProjectPublication(options.projectPath, target, contentHash, input.publicationRef, sharedCallers);
356
+ verifySnapshots();
357
+ return { ...publication, contentHash, reachable, localFiles };
358
+ }
359
+ if (home.requiresPublish && (!options.runtimeModulePath || !input.operationKey))
360
+ throw new Error('capability-publication-required');
361
+ const publication = home.requiresPublish ? publishRunFile(options.projectPath, target, home, input.operationKey, contentHash, options.runtimeModulePath) : { published: true, reference: target };
362
+ verifySnapshots();
363
+ return { ...publication, contentHash, reachable, localFiles };
364
+ }