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.
- package/dist/registry/scripts/learning/completion-state.js +80 -4
- package/dist/registry/scripts/learning/corpus-audit.js +7 -7
- package/dist/registry/scripts/learning/entry-authoring.js +181 -0
- package/dist/registry/scripts/learning/entry-format.js +10 -1
- package/dist/registry/scripts/learning/outcome-assessment.js +162 -0
- package/dist/registry/scripts/learning/outcome-types.js +92 -0
- package/dist/registry/scripts/learning/owner-identity.js +11 -0
- package/dist/registry/scripts/learning/proposal-queue.js +81 -0
- package/dist/registry/scripts/learning/run-application.js +1003 -0
- package/dist/registry/scripts/learning/run-publication.js +364 -0
- package/dist/registry/scripts/learning/run-record.js +895 -0
- package/dist/registry/scripts/learning-usage/learning-source-record.js +131 -0
- package/dist/src/cli/commands/learning.js +64 -0
- package/dist/src/cli/doctor/checks/scripts-checks.js +53 -1
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/learning/inventory.js +285 -0
- package/dist/src/cli/learning/queue-operations.js +199 -0
- package/dist/src/cli/learning/runtime.js +82 -0
- package/dist/src/cli/learning/usage.js +277 -0
- package/dist/src/core/fraim-config-schema.generated.js +0 -4
- package/dist/src/core/utils/local-registry-resolver.js +13 -3
- package/dist/src/local-mcp-server/learning-context-builder.js +109 -379
- package/dist/src/local-mcp-server/learning-entry-format.js +25 -0
- package/dist/src/local-mcp-server/learning-usage-analysis.js +12 -410
- package/dist/src/local-mcp-server/learning-usage-eligibility.js +8 -14
- package/dist/src/local-mcp-server/learning-usage-projection.js +5 -22
- package/dist/src/local-mcp-server/learning-usage-store.js +93 -393
- package/dist/src/local-mcp-server/learning-usage-verify.js +9 -40
- package/dist/src/local-mcp-server/stdio-server.js +50 -114
- package/dist/src/mcp/tool-schemas.js +2 -1
- package/package.json +9 -1
- package/dist/src/local-mcp-server/learning-firing-parser.js +0 -247
- package/dist/src/local-mcp-server/learning-usage-attestation.js +0 -238
- package/dist/src/local-mcp-server/learning-usage-command.js +0 -518
|
@@ -0,0 +1,199 @@
|
|
|
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.resolveQueueHome = resolveQueueHome;
|
|
7
|
+
exports.resolveReaderCorpusRoots = resolveReaderCorpusRoots;
|
|
8
|
+
exports.runQueueCommand = runQueueCommand;
|
|
9
|
+
/** Packaged learning queue operations and home resolvers. */
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
|
+
const runtime_1 = require("./runtime");
|
|
14
|
+
const completion_state_1 = require("../../../registry/scripts/learning/completion-state");
|
|
15
|
+
const artifact_paths_1 = require("../../../registry/scripts/artifact-paths");
|
|
16
|
+
const entry_authoring_1 = require("../../../registry/scripts/learning/entry-authoring");
|
|
17
|
+
const owner_identity_1 = require("../../../registry/scripts/learning/owner-identity");
|
|
18
|
+
const run_record_1 = require("../../../registry/scripts/learning/run-record");
|
|
19
|
+
const run_application_1 = require("../../../registry/scripts/learning/run-application");
|
|
20
|
+
const run_publication_1 = require("../../../registry/scripts/learning/run-publication");
|
|
21
|
+
const corpus_audit_1 = require("../../../registry/scripts/learning/corpus-audit");
|
|
22
|
+
const outcome_assessment_1 = require("../../../registry/scripts/learning/outcome-assessment");
|
|
23
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
24
|
+
const pack_home_1 = require("../utils/pack-home");
|
|
25
|
+
const user_config_1 = require("../utils/user-config");
|
|
26
|
+
const local_registry_resolver_1 = require("../../core/utils/local-registry-resolver");
|
|
27
|
+
function resolveQueueHome(projectPath, level) {
|
|
28
|
+
return (0, owner_identity_1.resolveOwnerHome)(projectPath, level, { runtimeModule: runtime_1.runtimeModuleStatic });
|
|
29
|
+
}
|
|
30
|
+
function resolveReaderCorpusRoots(projectPath) {
|
|
31
|
+
const manager = (0, pack_home_1.resolvePackHome)('manager', { migrateLegacy: false });
|
|
32
|
+
const portable = (0, project_fraim_paths_1.getConfiguredPortableLearningsDir)(projectPath);
|
|
33
|
+
const same = (left, right) => node_path_1.default.resolve(left) === node_path_1.default.resolve(right);
|
|
34
|
+
const roots = manager.synced ? [] : [node_path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'manager', 'learnings')];
|
|
35
|
+
if (same(portable, node_path_1.default.join(manager.contentRoot, 'learnings')) || !same(portable, (0, project_fraim_paths_1.getUserFraimLearningsDir)()))
|
|
36
|
+
roots.push(portable);
|
|
37
|
+
return [...new Set(roots)];
|
|
38
|
+
}
|
|
39
|
+
function evidenceDirectory(home) {
|
|
40
|
+
const directory = node_path_1.default.join((0, artifact_paths_1.resolveArtifactPaths)((0, owner_identity_1.ownerArtifactRoot)(home), home.contentRoot).evidence, 'sleep');
|
|
41
|
+
(0, owner_identity_1.configuredArtifactPath)(home, directory);
|
|
42
|
+
return directory;
|
|
43
|
+
}
|
|
44
|
+
const journalDirectory = (home) => (0, artifact_paths_1.resolveRunJournalDirectory)(home.root);
|
|
45
|
+
async function runQueueCommand(argv) {
|
|
46
|
+
const arg = (flag) => { const i = argv.indexOf(flag); return i >= 0 ? argv[i + 1] : undefined; };
|
|
47
|
+
const action = arg('--action');
|
|
48
|
+
const projectPath = node_path_1.default.resolve(arg('--projectPath') || process.cwd());
|
|
49
|
+
const principal = arg('--owner');
|
|
50
|
+
if (!principal)
|
|
51
|
+
throw new Error('session-principal-required: pass the authenticated FRAIM owner');
|
|
52
|
+
const level = (arg('--level') || 'Manager');
|
|
53
|
+
if (level === 'Project' && action !== 'list' && action !== 'show' && action !== 'assessments')
|
|
54
|
+
throw new Error('run-home-required: personal Sleep records belong to Manager; use Org for organizational synthesis');
|
|
55
|
+
const home = resolveQueueHome(projectPath, level);
|
|
56
|
+
const directory = journalDirectory(home);
|
|
57
|
+
const runId = arg('--runId');
|
|
58
|
+
const resolveHome = (owner) => { const found = resolveQueueHome(projectPath, owner.level); (0, owner_identity_1.assertSameOwner)(owner, found.owner); return found; };
|
|
59
|
+
const configuredHomes = () => {
|
|
60
|
+
const homes = [resolveQueueHome(projectPath, 'Project'), resolveQueueHome(projectPath, 'Manager')];
|
|
61
|
+
if ((0, user_config_1.readUserFraimConfig)().organization)
|
|
62
|
+
homes.push(resolveQueueHome(projectPath, 'Org'));
|
|
63
|
+
return homes;
|
|
64
|
+
};
|
|
65
|
+
if (action === 'assessments') {
|
|
66
|
+
const records = (0, outcome_assessment_1.readOutcomeAssessments)(level === 'Manager' ? configuredHomes() : [home], principal);
|
|
67
|
+
// Cross-participant read authority belongs to an explicit live Org run, never merely to the selected level.
|
|
68
|
+
if (level === 'Org' && runId) {
|
|
69
|
+
const state = new run_record_1.RunRecord(directory, runId, home.owner, principal, evidenceDirectory(home)).read();
|
|
70
|
+
for (const grant of state.sourceParticipants ?? [])
|
|
71
|
+
records.push(...(0, outcome_assessment_1.readOutcomeAssessments)([resolveHome(grant.owner)], grant.author));
|
|
72
|
+
}
|
|
73
|
+
return { assessments: [...new Map(records.map(record => [record.digest, record])).values()] };
|
|
74
|
+
}
|
|
75
|
+
if (action === 'list' && !runId)
|
|
76
|
+
return { runs: node_fs_1.default.existsSync(directory) ? node_fs_1.default.readdirSync(directory).filter(n => n.endsWith('.queue.jsonl')).flatMap(n => {
|
|
77
|
+
const state = (0, run_record_1.readSupportedRunRecord)(node_path_1.default.join(directory, n));
|
|
78
|
+
if (!state)
|
|
79
|
+
return [];
|
|
80
|
+
(0, owner_identity_1.assertSameOwner)(home.owner, state.owner);
|
|
81
|
+
if (state.reviewer !== principal)
|
|
82
|
+
throw new Error('run-context-mismatch');
|
|
83
|
+
return [state];
|
|
84
|
+
}) : [] };
|
|
85
|
+
if (!runId)
|
|
86
|
+
throw new Error('run-context-required: provide --runId; legacy queue mutations are disabled');
|
|
87
|
+
const inspecting = action === 'show' || action === 'list';
|
|
88
|
+
const recorded = inspecting && node_fs_1.default.existsSync(directory) && node_fs_1.default.readdirSync(directory).includes(runId + '.queue.jsonl')
|
|
89
|
+
? (0, run_record_1.readSupportedRunRecord)((0, owner_identity_1.configuredArtifactPath)(home, node_path_1.default.join(directory, runId + '.queue.jsonl'))) : null;
|
|
90
|
+
const completionDirectory = recorded?.completionDirectory ?? (level === 'Project' ? directory : evidenceDirectory(home));
|
|
91
|
+
(0, owner_identity_1.configuredArtifactPath)(home, completionDirectory);
|
|
92
|
+
const run = new run_record_1.RunRecord(directory, runId, home.owner, principal, completionDirectory);
|
|
93
|
+
const expectedSequence = Number(arg('--expectedSequence'));
|
|
94
|
+
const id = arg('--actionId') || '';
|
|
95
|
+
const revision = Number(arg('--revision'));
|
|
96
|
+
const inputFile = arg('--inputFile');
|
|
97
|
+
const input = inputFile ? JSON.parse(node_fs_1.default.readFileSync(inputFile, 'utf8')) : undefined;
|
|
98
|
+
if (action === 'create') {
|
|
99
|
+
const participants = input?.sourceParticipants === undefined ? undefined : (0, completion_state_1.normalizeSourceParticipants)(input.sourceParticipants, home.owner);
|
|
100
|
+
for (const participant of participants ?? [])
|
|
101
|
+
(0, owner_identity_1.assertSameOwner)(participant.owner, resolveQueueHome(projectPath, participant.owner.level).owner);
|
|
102
|
+
return { ...run.create(participants), evidencePath: run.completionFile.replace(/\\/g, '/') };
|
|
103
|
+
}
|
|
104
|
+
if (input?.sourceParticipants !== undefined)
|
|
105
|
+
throw new Error('source-participants-create-only');
|
|
106
|
+
if (action === 'recover')
|
|
107
|
+
return run.recoverTail(input?.expectedPrefixHash);
|
|
108
|
+
if (action === 'list' || action === 'show')
|
|
109
|
+
return node_fs_1.default.existsSync(run.file)
|
|
110
|
+
? { ...run.read(), evidencePath: run.completionFile.replace(/\\/g, '/') }
|
|
111
|
+
: { missing: true, runId };
|
|
112
|
+
if (action === 'propose' || action === 'edit') {
|
|
113
|
+
if (!input)
|
|
114
|
+
throw new Error('input-required');
|
|
115
|
+
if (input.kind === 'learning' && input.operation !== 'remove') {
|
|
116
|
+
if (!input.target.entryId)
|
|
117
|
+
input.target.entryId = 'L-' + (0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, 10);
|
|
118
|
+
const managerHome = (0, pack_home_1.resolvePackHome)('manager', { migrateLegacy: false });
|
|
119
|
+
const orgHome = (0, pack_home_1.resolvePackHome)('org', { migrateLegacy: false });
|
|
120
|
+
const resolver = new local_registry_resolver_1.LocalRegistryResolver({ workspaceRoot: projectPath, managerLocalRoot: managerHome.contentRoot, managerCacheRoot: managerHome.contentRoot, orgCacheRoot: orgHome.contentRoot, remoteContentResolver: async () => { throw new Error('job-definition-unavailable'); } });
|
|
121
|
+
if (input.content?.domain?.basis !== 'manager') {
|
|
122
|
+
for (const origin of input.content.trace.originatingJobs) {
|
|
123
|
+
origin.registryPath ||= await resolver.findRegistryPath('jobs', origin.name);
|
|
124
|
+
const definition = await resolver.resolveFile(origin.registryPath).catch(() => null);
|
|
125
|
+
if (definition?.content)
|
|
126
|
+
origin.content = definition.content;
|
|
127
|
+
}
|
|
128
|
+
input.content.domain = (0, entry_authoring_1.deriveDomain)(input.content.trace.originatingJobs);
|
|
129
|
+
for (const origin of input.content.trace.originatingJobs)
|
|
130
|
+
delete origin.content;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return action === 'propose' ? run.propose(input, expectedSequence) : run.edit(id, revision, input, expectedSequence);
|
|
134
|
+
}
|
|
135
|
+
if (action === 'decide' && input?.tuples)
|
|
136
|
+
return run.decideBatch(input.tuples, arg('--reviewer') || '', arg('--reason') || '', expectedSequence);
|
|
137
|
+
if (action === 'decide')
|
|
138
|
+
return run.decide(id, revision, { action: arg('--decision'), reviewer: arg('--reviewer') || '', reason: arg('--reason') || '', ...(arg('--revisitCondition') ? { revisitCondition: arg('--revisitCondition') } : {}) }, expectedSequence);
|
|
139
|
+
if (action === 'assess') {
|
|
140
|
+
if (!input)
|
|
141
|
+
throw new Error('input-required');
|
|
142
|
+
const homes = configuredHomes();
|
|
143
|
+
const runDirectories = homes.map(journalDirectory);
|
|
144
|
+
const coordinationFile = node_path_1.default.join(journalDirectory(homes.find(item => item.level === 'Manager')), 'owner-application');
|
|
145
|
+
return (0, outcome_assessment_1.recordOutcomeAssessment)(run, input, expectedSequence, { resolveHome, homes, runDirectories, coordinationFile });
|
|
146
|
+
}
|
|
147
|
+
if (action === 'audit-corpus') {
|
|
148
|
+
const state = run.read();
|
|
149
|
+
const current = (0, corpus_audit_1.collectLearningCorpus)(configuredHomes(), [principal, ...(state.sourceParticipants?.map(grant => grant.author) || [])], resolveReaderCorpusRoots(projectPath));
|
|
150
|
+
if (!current.complete && input?.allowDeferred !== true)
|
|
151
|
+
throw new Error('corpus-incomplete');
|
|
152
|
+
if (!input?.snapshot || input.snapshot.fingerprint !== current.fingerprint)
|
|
153
|
+
throw new Error('corpus-audit-stale');
|
|
154
|
+
return run.recordCorpusAudit({ snapshot: current, reviewer: input.reviewer, dispositions: input.dispositions, ...(input.allowDeferred === true ? { partial: true } : {}) }, expectedSequence);
|
|
155
|
+
}
|
|
156
|
+
if (action === 'apply') {
|
|
157
|
+
const state = run.read();
|
|
158
|
+
const proposed = state.actions[id];
|
|
159
|
+
const homes = configuredHomes();
|
|
160
|
+
const runDirectories = homes.map(journalDirectory);
|
|
161
|
+
const coordinationFile = node_path_1.default.join(journalDirectory(homes.find(item => item.level === 'Manager')), 'owner-application');
|
|
162
|
+
(0, run_application_1.reserveRunAction)(run, id, revision, expectedSequence, { resolveHome, runDirectories, coordinationFile, homes, readerRoots: () => resolveReaderCorpusRoots(projectPath) });
|
|
163
|
+
const applySequence = run.read().sequence;
|
|
164
|
+
const capability = proposed?.kind === 'capability' ? await (0, run_publication_1.verifyCapabilityDelivery)(proposed, { ...input, operationKey: `${runId}-${id}-${revision}` }, { projectPath, resolveHome, runtimeModule: runtime_1.runtimeModuleStatic, runtimeModulePath: runtime_1.runtimeModulePathStatic }) : undefined;
|
|
165
|
+
const capabilities = new Map();
|
|
166
|
+
if (capability)
|
|
167
|
+
capabilities.set(id, capability);
|
|
168
|
+
for (const dependencyId of proposed?.dependencies || []) {
|
|
169
|
+
const dependency = state.actions[dependencyId];
|
|
170
|
+
if (dependency?.kind !== 'capability')
|
|
171
|
+
continue;
|
|
172
|
+
capabilities.set(dependencyId, await (0, run_publication_1.verifyCapabilityDelivery)(dependency, { publicationRef: dependency.application?.publicationRef, operationKey: `${runId}-${dependencyId}-${dependency.revision}` }, { projectPath, resolveHome, runtimeModule: runtime_1.runtimeModuleStatic, runtimeModulePath: runtime_1.runtimeModulePathStatic }));
|
|
173
|
+
}
|
|
174
|
+
return (0, run_application_1.applyRunAction)(run, id, revision, applySequence, {
|
|
175
|
+
resolveHome, runDirectories, coordinationFile, homes, readerRoots: () => resolveReaderCorpusRoots(projectPath),
|
|
176
|
+
publish: (file, targetHome, key, hash, proof) => (0, run_publication_1.publishRunFile)(projectPath, file, targetHome, key, hash, runtime_1.runtimeModulePathStatic, proof),
|
|
177
|
+
verifyCapability: candidate => capabilities.get(candidate.actionId),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (action === 'complete') {
|
|
181
|
+
if (!Array.isArray(input?.analyzedSources))
|
|
182
|
+
throw new Error('analyzed-sources-required');
|
|
183
|
+
const homes = configuredHomes();
|
|
184
|
+
const capabilities = new Map();
|
|
185
|
+
if (node_fs_1.default.existsSync(run.file))
|
|
186
|
+
for (const candidate of Object.values(run.read().actions)) {
|
|
187
|
+
if (candidate.kind === 'capability' && candidate.application?.stage === 'applied')
|
|
188
|
+
capabilities.set(candidate.actionId, await (0, run_publication_1.verifyCapabilityDelivery)(candidate, { publicationRef: candidate.application.publicationRef, operationKey: `${runId}-${candidate.actionId}-${candidate.revision}` }, { projectPath, resolveHome, runtimeModule: runtime_1.runtimeModuleStatic, runtimeModulePath: runtime_1.runtimeModulePathStatic }));
|
|
189
|
+
}
|
|
190
|
+
return (0, run_application_1.completeRun)(run, input?.summary, homes, { resolveHome, homes, readerRoots: () => resolveReaderCorpusRoots(projectPath), runDirectories: homes.map(journalDirectory), retrospective: input?.retrospective, analyzedSources: input.analyzedSources,
|
|
191
|
+
allowDeferred: input?.allowDeferred === true, deferredSources: input?.deferredSources,
|
|
192
|
+
publish: (file, targetHome, key, hash, proof) => (0, run_publication_1.publishRunFile)(projectPath, file, targetHome, key, hash, runtime_1.runtimeModulePathStatic, proof),
|
|
193
|
+
verifyCapability: candidate => capabilities.get(candidate.actionId),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (action === 'reconcile-source')
|
|
197
|
+
return run.reconcileSource(input, resolveHome(input.owner), expectedSequence);
|
|
198
|
+
throw new Error('invalid-action');
|
|
199
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runtimeModuleStatic = runtimeModuleStatic;
|
|
40
|
+
exports.runtimeModulePathStatic = runtimeModulePathStatic;
|
|
41
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
42
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
43
|
+
const projectFraimPaths = __importStar(require("../../core/utils/project-fraim-paths"));
|
|
44
|
+
const packHome = __importStar(require("../utils/pack-home"));
|
|
45
|
+
const userConfig = __importStar(require("../utils/user-config"));
|
|
46
|
+
const localRegistryResolver = __importStar(require("../../core/utils/local-registry-resolver"));
|
|
47
|
+
const learningContextBuilder = __importStar(require("../../local-mcp-server/learning-context-builder"));
|
|
48
|
+
const learningUsageStore = __importStar(require("../../local-mcp-server/learning-usage-store"));
|
|
49
|
+
const MODULE_MAP = {
|
|
50
|
+
'core/utils/project-fraim-paths': projectFraimPaths,
|
|
51
|
+
'cli/utils/pack-home': packHome,
|
|
52
|
+
'cli/utils/user-config': userConfig,
|
|
53
|
+
'core/utils/local-registry-resolver': localRegistryResolver,
|
|
54
|
+
'local-mcp-server/learning-context-builder': learningContextBuilder,
|
|
55
|
+
'local-mcp-server/learning-usage-store': learningUsageStore,
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Static adapter satisfying the runtimeModule(projectPath, relative) interface.
|
|
59
|
+
* Resolves canonical packaged modules without installation-layout discovery.
|
|
60
|
+
*/
|
|
61
|
+
function runtimeModuleStatic(_projectPath, relative) {
|
|
62
|
+
const mod = MODULE_MAP[relative];
|
|
63
|
+
if (mod === undefined)
|
|
64
|
+
throw new Error(`runtime-unavailable: no static module for '${relative}' — add it to src/cli/learning/runtime.ts`);
|
|
65
|
+
return mod;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Static path resolver satisfying the runtimeModulePath(projectPath, relative) interface.
|
|
69
|
+
* Returns the absolute path to the compiled (or source) module file.
|
|
70
|
+
*
|
|
71
|
+
* __dirname is dist/src/cli/learning/ (compiled) or src/cli/learning/ (tsx).
|
|
72
|
+
* Two levels up lands at dist/src/ or src/, from which relative resolves correctly.
|
|
73
|
+
*/
|
|
74
|
+
function runtimeModulePathStatic(_projectPath, relative) {
|
|
75
|
+
const compiled = node_path_1.default.join(__dirname, '..', '..', relative + '.js');
|
|
76
|
+
if (node_fs_1.default.existsSync(compiled))
|
|
77
|
+
return compiled;
|
|
78
|
+
const source = node_path_1.default.join(__dirname, '..', '..', relative + '.ts');
|
|
79
|
+
if (node_fs_1.default.existsSync(source))
|
|
80
|
+
return source;
|
|
81
|
+
throw new Error(`runtime-unavailable: no static module path for '${relative}'`);
|
|
82
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
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.runUsageCommand = runUsageCommand;
|
|
7
|
+
/** Explicit historical diagnostics and maintenance through the canonical packaged runtime. */
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const learning_entry_format_1 = require("../../local-mcp-server/learning-entry-format");
|
|
11
|
+
const learning_usage_analysis_1 = require("../../local-mcp-server/learning-usage-analysis");
|
|
12
|
+
const learning_usage_store_1 = require("../../local-mcp-server/learning-usage-store");
|
|
13
|
+
const learning_usage_verify_1 = require("../../local-mcp-server/learning-usage-verify");
|
|
14
|
+
const pack_home_1 = require("../utils/pack-home");
|
|
15
|
+
const COMMANDS = {
|
|
16
|
+
report: runReport,
|
|
17
|
+
'stamp-ids': runStampIds,
|
|
18
|
+
prune: runPrune,
|
|
19
|
+
verify: runVerify,
|
|
20
|
+
};
|
|
21
|
+
/** Preserve matching snapshot, checkpoint and journal files during explicit recovery. */
|
|
22
|
+
const REBUILD_NOTE = "Keep `learning-usage.json`, its `.checkpoint` backup, `events/` and any conflict copies. After consolidation, the snapshot and checkpoint backup preserve lifetime counters and already-counted source markers; the remaining journal cannot rebuild that history. A missing or torn snapshot is read from its checkpoint backup automatically. Restore matching checkpoint and journal files from backup if both copies are damaged. Inspect split identities and conflict copies before repairing them; do not delete the snapshot to clear a warning.";
|
|
23
|
+
/** Historical retention bounds never participate in outcome assessment decisions. */
|
|
24
|
+
function readStore(workspaceRoot) {
|
|
25
|
+
return (0, learning_usage_store_1.readUsageStore)(undefined, { limits: (0, learning_usage_store_1.resolveUsageLimits)(workspaceRoot), refreshSnapshot: 'never' });
|
|
26
|
+
}
|
|
27
|
+
function parseArgv(argv) {
|
|
28
|
+
const [command, ...rest] = argv;
|
|
29
|
+
if (!command || command === '--help' || command === '-h') {
|
|
30
|
+
printHelp();
|
|
31
|
+
process.exit(command ? 0 : 1);
|
|
32
|
+
}
|
|
33
|
+
if (!Object.prototype.hasOwnProperty.call(COMMANDS, command)) {
|
|
34
|
+
throw new Error(`Unknown learning-usage command: ${command}`);
|
|
35
|
+
}
|
|
36
|
+
const options = {};
|
|
37
|
+
for (let i = 0; i < rest.length; i++) {
|
|
38
|
+
const raw = rest[i];
|
|
39
|
+
if (!raw.startsWith('--'))
|
|
40
|
+
throw new Error(`Unexpected positional argument: ${raw}`);
|
|
41
|
+
const eq = raw.indexOf('=');
|
|
42
|
+
const key = camelCase(eq >= 0 ? raw.slice(2, eq) : raw.slice(2));
|
|
43
|
+
const next = eq >= 0 ? raw.slice(eq + 1) : rest[i + 1];
|
|
44
|
+
const isBoolean = eq < 0 && (next === undefined || next.startsWith('--'));
|
|
45
|
+
const value = parseOptionValue(key, isBoolean ? true : next);
|
|
46
|
+
if (!isBoolean && eq < 0)
|
|
47
|
+
i++;
|
|
48
|
+
if (key === 'dir') {
|
|
49
|
+
options.dir = [...arrayOption(options.dir), String(value)];
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
options[key] = value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { command, options };
|
|
56
|
+
}
|
|
57
|
+
function parseOptionValue(key, value) {
|
|
58
|
+
if (key !== 'apply' && key !== 'json')
|
|
59
|
+
return value;
|
|
60
|
+
if (value === true || value === 'true')
|
|
61
|
+
return true;
|
|
62
|
+
if (value === false || value === 'false')
|
|
63
|
+
return false;
|
|
64
|
+
throw new Error(`Invalid boolean for --${key}: expected true or false.`);
|
|
65
|
+
}
|
|
66
|
+
function camelCase(value) {
|
|
67
|
+
return value.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
68
|
+
}
|
|
69
|
+
function arrayOption(value) {
|
|
70
|
+
return Array.isArray(value) ? value : value === undefined ? [] : [String(value)];
|
|
71
|
+
}
|
|
72
|
+
function resolveWorkspaceRoot(options) {
|
|
73
|
+
return node_path_1.default.resolve(options.workspaceRoot || options.root || process.cwd());
|
|
74
|
+
}
|
|
75
|
+
function emit(payload, json, text) {
|
|
76
|
+
process.stdout.write(json ? `${JSON.stringify(payload, null, 2)}\n` : `${text()}\n`);
|
|
77
|
+
}
|
|
78
|
+
function runReport(options) {
|
|
79
|
+
const workspaceRoot = resolveWorkspaceRoot(options);
|
|
80
|
+
const report = (0, learning_usage_analysis_1.buildUsageReport)(readStore(workspaceRoot));
|
|
81
|
+
emit(report, options.json, () => [
|
|
82
|
+
`Historical outcomes, last observed ${report.lastObservedAt ?? 'unknown'}.`,
|
|
83
|
+
...report.historicalOutcomes.map(entry => `${entry.title}: ${entry.fired} recorded uses; last recorded use ${entry.lastFired ?? 'unknown'}.`),
|
|
84
|
+
].join('\n'));
|
|
85
|
+
}
|
|
86
|
+
function stampFile(filePath, fileType, apply) {
|
|
87
|
+
const content = node_fs_1.default.readFileSync(filePath, 'utf8');
|
|
88
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
89
|
+
const { out, stamped, alreadyStamped } = stampLines(content.split(/\r?\n/), fileType);
|
|
90
|
+
if (apply && stamped > 0)
|
|
91
|
+
node_fs_1.default.writeFileSync(filePath, out.join(eol), 'utf8');
|
|
92
|
+
return { file: filePath, entriesStamped: stamped, entriesAlreadyStamped: alreadyStamped };
|
|
93
|
+
}
|
|
94
|
+
function entryHasId(lines, index) {
|
|
95
|
+
const headingRe = (0, learning_entry_format_1.learningEntryHeadingRegex)();
|
|
96
|
+
const idRe = (0, learning_entry_format_1.learningEntryIdLineRegex)();
|
|
97
|
+
for (let j = index + 1; j < lines.length; j++) {
|
|
98
|
+
if (headingRe.test(lines[j]))
|
|
99
|
+
break;
|
|
100
|
+
if (idRe.test(lines[j].trim()))
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
function stampLines(lines, fileType) {
|
|
106
|
+
const headingRe = (0, learning_entry_format_1.learningEntryHeadingRegex)();
|
|
107
|
+
const out = [];
|
|
108
|
+
let stamped = 0;
|
|
109
|
+
let alreadyStamped = 0;
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const line = lines[i];
|
|
112
|
+
out.push(line);
|
|
113
|
+
const header = line.match(headingRe);
|
|
114
|
+
if (!header)
|
|
115
|
+
continue;
|
|
116
|
+
if (entryHasId(lines, i)) {
|
|
117
|
+
alreadyStamped++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const title = header[2].trim();
|
|
121
|
+
if (!title)
|
|
122
|
+
continue;
|
|
123
|
+
if (lines[i + 1] !== undefined && lines[i + 1].trim() === '') {
|
|
124
|
+
out.push('');
|
|
125
|
+
out.push(`**Id**: ${(0, learning_entry_format_1.generateLearningEntryId)(fileType, title)}`);
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
out.push(`**Id**: ${(0, learning_entry_format_1.generateLearningEntryId)(fileType, title)}`);
|
|
130
|
+
}
|
|
131
|
+
stamped++;
|
|
132
|
+
}
|
|
133
|
+
return { out, stamped, alreadyStamped };
|
|
134
|
+
}
|
|
135
|
+
function fileTypeFromName(fileName) {
|
|
136
|
+
for (const type of ['mistake-patterns', 'preferences', 'validated-patterns', 'manager-coaching']) {
|
|
137
|
+
if (fileName.includes(type))
|
|
138
|
+
return type;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function defaultStampDirs(workspaceRoot) {
|
|
143
|
+
const dirs = [node_path_1.default.join(workspaceRoot, 'fraim', 'personalized-employee', 'learnings')];
|
|
144
|
+
for (const level of ['org', 'manager']) {
|
|
145
|
+
try {
|
|
146
|
+
dirs.push(node_path_1.default.join((0, pack_home_1.resolvePackHome)(level, { migrateLegacy: false }).contentRoot, 'learnings'));
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// A pack home that does not resolve is simply not stamped. Visible in the
|
|
150
|
+
// printed directory list rather than failing the command.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [...new Set(dirs)];
|
|
154
|
+
}
|
|
155
|
+
function runStampIds(options) {
|
|
156
|
+
const workspaceRoot = resolveWorkspaceRoot(options);
|
|
157
|
+
// Stamping is explicit; the dry run preserves every source byte.
|
|
158
|
+
const dirs = arrayOption(options.dir).length
|
|
159
|
+
? arrayOption(options.dir).map((d) => node_path_1.default.resolve(d))
|
|
160
|
+
: defaultStampDirs(workspaceRoot);
|
|
161
|
+
const results = [];
|
|
162
|
+
for (const dir of dirs) {
|
|
163
|
+
if (!node_fs_1.default.existsSync(dir))
|
|
164
|
+
continue;
|
|
165
|
+
for (const fileName of node_fs_1.default.readdirSync(dir)) {
|
|
166
|
+
if (!fileName.endsWith('.md'))
|
|
167
|
+
continue;
|
|
168
|
+
const fileType = fileTypeFromName(fileName);
|
|
169
|
+
if (!fileType)
|
|
170
|
+
continue;
|
|
171
|
+
results.push(stampFile(node_path_1.default.join(dir, fileName), fileType, Boolean(options.apply)));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const payload = {
|
|
175
|
+
apply: Boolean(options.apply),
|
|
176
|
+
filesScanned: results.length,
|
|
177
|
+
entriesStamped: results.reduce((sum, r) => sum + r.entriesStamped, 0),
|
|
178
|
+
entriesAlreadyStamped: results.reduce((sum, r) => sum + r.entriesAlreadyStamped, 0),
|
|
179
|
+
files: results,
|
|
180
|
+
};
|
|
181
|
+
emit(payload, options.json, () => [
|
|
182
|
+
`${payload.apply ? 'Stamped' : 'Would stamp'} ${payload.entriesStamped} entries across ${payload.filesScanned} files in ${dirs.length} directories:`,
|
|
183
|
+
...dirs.map((d) => ` ${d}`),
|
|
184
|
+
`${payload.entriesAlreadyStamped} entries already carry an id.`,
|
|
185
|
+
payload.apply ? '' : 'Re-run with --apply to write the changes.',
|
|
186
|
+
].filter(Boolean).join('\n'));
|
|
187
|
+
}
|
|
188
|
+
function runPrune(options) {
|
|
189
|
+
const workspaceRoot = resolveWorkspaceRoot(options);
|
|
190
|
+
const limits = (0, learning_usage_store_1.resolveUsageLimits)(workspaceRoot);
|
|
191
|
+
const result = (0, learning_usage_store_1.pruneUsageStore)(limits);
|
|
192
|
+
emit({ ...result, storePath: (0, learning_usage_store_1.resolveUsageStorePath)(), limits }, options.json, () => result.maintenanceStatus === 'deferred' ? `Maintenance deferred; history unchanged. ${result.evidence?.observations.join(', ') ?? ''}`
|
|
193
|
+
: `Pruned ${result.prunedOfferRecords} offer records and ${result.prunedFiringRecords} firing records across ${result.entries} entries. Aggregates unchanged.`);
|
|
194
|
+
}
|
|
195
|
+
function mb(bytes) {
|
|
196
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
197
|
+
}
|
|
198
|
+
function verificationStorageLines(report) {
|
|
199
|
+
const lines = [];
|
|
200
|
+
if (report.storeCopies.length === 0) {
|
|
201
|
+
lines.push(' one store file, no unmerged copies');
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
lines.push(` ${report.storeCopies.length} unmerged store copies beside the canonical snapshot:`);
|
|
205
|
+
for (const copy of report.storeCopies) {
|
|
206
|
+
lines.push(` ${copy.path}: ${copy.readable ? `${copy.entries} entries, ${copy.offers} offers, ${copy.firings} firings` : `unreadable (${copy.error ?? 'unknown error'})`}`);
|
|
207
|
+
}
|
|
208
|
+
lines.push(' Nothing reads these. Preserve them and the checkpoint backup for inspection; do not delete snapshots to clear this warning.');
|
|
209
|
+
}
|
|
210
|
+
lines.push(` event logs folded: ${report.eventLogs.length === 0 ? 'none' : report.eventLogs.map((l) => `${l.machineId} (${l.bytes} bytes)`).join(', ')}`);
|
|
211
|
+
for (const [machineId, lag] of Object.entries(report.checkpointLag)) {
|
|
212
|
+
lines.push(` checkpoint lag for ${machineId}: ${lag} bytes${lag > 0 ? ' behind the log head' : ''}`);
|
|
213
|
+
}
|
|
214
|
+
lines.push(report.compactionAdvised
|
|
215
|
+
? ` log size ${mb(report.totalLogBytes)} is past the ${mb(report.compactionAdvisoryBytes)} point where a cold fold stops fitting the context-injection budget. Run \`fraim learning usage prune\` to consolidate processed events into the snapshot and checkpoint backup, then clear them independently of retention.`
|
|
216
|
+
: ` log size ${mb(report.totalLogBytes)} of ${mb(report.compactionAdvisoryBytes)} before compaction is worth running.`);
|
|
217
|
+
if (report.rebuildAdvised)
|
|
218
|
+
lines.push('', REBUILD_NOTE);
|
|
219
|
+
return lines;
|
|
220
|
+
}
|
|
221
|
+
function verificationSummaryLines(report) {
|
|
222
|
+
return [
|
|
223
|
+
`Usage record: ${report.entries} entries at ${report.snapshotPath}`,
|
|
224
|
+
`Current model: ${report.currentModel ?? 'unknown'}`,
|
|
225
|
+
...(report.evidence ? [`Evidence: ${report.evidence.status}. ${report.evidence.observations.join(', ')}.`,
|
|
226
|
+
...(report.evidence.recovery ? [`Recovered from ${report.evidence.recovery}; primary replacement was not persisted by this read.`] : [])] : []),
|
|
227
|
+
'',
|
|
228
|
+
'Identity',
|
|
229
|
+
report.twinPairs.length === 0
|
|
230
|
+
? ' none: no lesson is tracked under two keys'
|
|
231
|
+
: ` ${report.twinPairs.length} lessons tracked under two keys. Its offers and its firings are on different records, so every count about it is partial.`,
|
|
232
|
+
report.unmatchedTitleKeys.length === 0
|
|
233
|
+
? ' every title-keyed record has been matched or has no twin'
|
|
234
|
+
: ` ${report.unmatchedTitleKeys.length} title-keyed records have no id twin. Expected for entries no tier has stamped; run \`stamp-ids\` to reduce it.`,
|
|
235
|
+
'',
|
|
236
|
+
'Counters',
|
|
237
|
+
' Offer accounting is retired; historical offer balance is not assessed.',
|
|
238
|
+
report.caseVariantModelBuckets.length === 0
|
|
239
|
+
? ' none: no model bucket differs only by case'
|
|
240
|
+
: ` ${report.caseVariantModelBuckets.length} model buckets differ only by case. Historical outcome attribution uses multiple spellings of one model; no current effectiveness conclusion is drawn.`,
|
|
241
|
+
report.unknownModelAttribution.length === 0
|
|
242
|
+
? ' none: every firing carries the model that attested it'
|
|
243
|
+
: ` ${report.unknownModelAttribution.length} entries have incomplete model attribution. Affected model conclusions are withheld. The cause is not established; unknown historical attribution cannot be inferred from the current model.`,
|
|
244
|
+
'',
|
|
245
|
+
'Storage',
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
function runVerify(options) {
|
|
249
|
+
const workspaceRoot = resolveWorkspaceRoot(options);
|
|
250
|
+
const report = (0, learning_usage_verify_1.buildVerifyReport)({
|
|
251
|
+
snapshotPath: (0, learning_usage_store_1.resolveUsageStorePath)(),
|
|
252
|
+
limits: (0, learning_usage_store_1.resolveUsageLimits)(workspaceRoot),
|
|
253
|
+
currentModel: options.model ?? undefined,
|
|
254
|
+
});
|
|
255
|
+
emit(report, options.json, () => {
|
|
256
|
+
const lines = verificationSummaryLines(report);
|
|
257
|
+
lines.push(...verificationStorageLines(report));
|
|
258
|
+
lines.push('', 'Nothing here blocks anything. This is what the record can and cannot currently prove.');
|
|
259
|
+
return lines.join('\n');
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function printHelp() {
|
|
263
|
+
process.stdout.write([
|
|
264
|
+
'Usage: fraim learning usage <command> [options]',
|
|
265
|
+
'',
|
|
266
|
+
'Commands:',
|
|
267
|
+
' report Show last-known historical outcomes',
|
|
268
|
+
' stamp-ids Add stable Id lines to learning entries',
|
|
269
|
+
' prune Apply usage-log retention limits',
|
|
270
|
+
' verify Report what the usage record can and cannot prove',
|
|
271
|
+
'',
|
|
272
|
+
].join('\n'));
|
|
273
|
+
}
|
|
274
|
+
function runUsageCommand(argv) {
|
|
275
|
+
const parsed = parseArgv(argv);
|
|
276
|
+
COMMANDS[parsed.command](parsed.options);
|
|
277
|
+
}
|
|
@@ -222,9 +222,6 @@ exports.FRAIM_CONFIG_SCHEMA = {
|
|
|
222
222
|
},
|
|
223
223
|
"offerLogLimit": {
|
|
224
224
|
"kind": "number"
|
|
225
|
-
},
|
|
226
|
-
"retirementOfferThreshold": {
|
|
227
|
-
"kind": "number"
|
|
228
225
|
}
|
|
229
226
|
}
|
|
230
227
|
}
|
|
@@ -709,7 +706,6 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
|
|
|
709
706
|
"learning.usage",
|
|
710
707
|
"learning.usage.firingLogLimit",
|
|
711
708
|
"learning.usage.offerLogLimit",
|
|
712
|
-
"learning.usage.retirementOfferThreshold",
|
|
713
709
|
"artifactPaths",
|
|
714
710
|
"artifactPaths.evidence",
|
|
715
711
|
"artifactPaths.rawLearnings",
|
|
@@ -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('/'));
|