fraim 2.0.211 → 2.0.212
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.
|
@@ -130,7 +130,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
130
130
|
personaKey: 'ricardo',
|
|
131
131
|
bundleId: 'persona-ricardo-core',
|
|
132
132
|
catalogMetadata: buildCatalogMetadata('ricardo', ['role-intake-and-scorecard', 'job-posting-and-sourcing', 'candidate-pipeline-management']),
|
|
133
|
-
protectedJobs: ['role-intake-and-scorecard', 'job-posting-and-sourcing', 'candidate-pipeline-management', 'interview-coordination', 'offer-and-close-management'],
|
|
133
|
+
protectedJobs: ['role-intake-and-scorecard', 'job-posting-and-sourcing', 'candidate-pipeline-management', 'sourcing-funnel-recap', 'interview-coordination', 'offer-and-close-management'],
|
|
134
134
|
protectedAliases: ['recruiting', 'recruiter', 'hiring'],
|
|
135
135
|
defaultHireMode: 'job',
|
|
136
136
|
lockCopy: 'Hire RECardo to unlock full-cycle recruiting work for this request.'
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* workspace root on the user's machine.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.CATEGORY_TO_FILETYPE = exports.LEARNING_PRIORITIES = void 0;
|
|
9
|
+
exports.CATEGORY_TO_FILETYPE = exports.BRAIN_LEARNING_FILETYPE_TO_REGION = exports.LEARNING_PRIORITIES = void 0;
|
|
10
10
|
exports.learningEntryHeadingRegex = learningEntryHeadingRegex;
|
|
11
11
|
exports.computeEffectiveScore = computeEffectiveScore;
|
|
12
12
|
exports.buildLearningContextSection = buildLearningContextSection;
|
|
@@ -17,6 +17,7 @@ exports.resolveTeamContextFile = resolveTeamContextFile;
|
|
|
17
17
|
exports.readOrgBrand = readOrgBrand;
|
|
18
18
|
exports.resolveOrgBrandWriteDir = resolveOrgBrandWriteDir;
|
|
19
19
|
exports.countPreservedLearnings = countPreservedLearnings;
|
|
20
|
+
exports.collectBrainLearningDots = collectBrainLearningDots;
|
|
20
21
|
exports.readPreservedLearnings = readPreservedLearnings;
|
|
21
22
|
exports.applyLearningEntryChange = applyLearningEntryChange;
|
|
22
23
|
exports.isTruthyFlag = isTruthyFlag;
|
|
@@ -385,11 +386,14 @@ function buildLearningContextSection(workspaceRoot, userId, forJob, domain) {
|
|
|
385
386
|
coach: { ft: 'manager-coaching', gated: false, label: '(manager-facing; all entries)' },
|
|
386
387
|
validated: { ft: 'validated-patterns', gated: true, label: '(entries above score threshold)' },
|
|
387
388
|
};
|
|
388
|
-
// Domain axis (issue #806): startup
|
|
389
|
-
//
|
|
390
|
-
// files below the global files.
|
|
391
|
-
const
|
|
392
|
-
const
|
|
389
|
+
// Domain axis (issue #806): session startup stays broad enough for stable
|
|
390
|
+
// preferences plus L0, while job contexts load the full global set and may
|
|
391
|
+
// append the current job's domain files below the global files.
|
|
392
|
+
const sessionCats = ['pref'];
|
|
393
|
+
const jobL2Cats = ['mistake', 'pref', 'coach', 'validated'];
|
|
394
|
+
const jobL1Cats = ['pref', 'coach', 'mistake', 'validated'];
|
|
395
|
+
const l2Cats = forJob ? jobL2Cats : sessionCats;
|
|
396
|
+
const l1Cats = forJob ? jobL1Cats : sessionCats;
|
|
393
397
|
const activeDomain = forJob && domain ? domain : null;
|
|
394
398
|
const dormantOf = (meta, filePath) => meta.gated ? scanMistakePatternFile(filePath, threshold, meta.ft).dormant : 0;
|
|
395
399
|
// Resolve one tier (L2 org or L1 personal) into ordered global + domain files.
|
|
@@ -473,14 +477,14 @@ function buildLearningContextSection(workspaceRoot, userId, forJob, domain) {
|
|
|
473
477
|
const coachPresent = l1.coachPresent || l2.coachPresent;
|
|
474
478
|
if (forJob) {
|
|
475
479
|
if (hasL2 || hasL1) {
|
|
476
|
-
section += '
|
|
480
|
+
section += 'Read the listed synthesized learning files before continuing, then apply the relevant patterns and preferences in this job.\n';
|
|
477
481
|
if (coachPresent) {
|
|
478
482
|
section += 'Treat manager-coaching as feedback for how the manager should continue or improve managing AI, not as agent instruction.\n';
|
|
479
483
|
}
|
|
480
484
|
}
|
|
481
485
|
}
|
|
482
|
-
else {
|
|
483
|
-
section += '
|
|
486
|
+
else if (hasL2 || hasL1) {
|
|
487
|
+
section += 'Read the listed synthesized learning files before continuing, then use this synthesized learning context throughout the session.\n';
|
|
484
488
|
if (coachPresent) {
|
|
485
489
|
section += 'Manager-coaching entries are manager-facing feedback, not instructions for the AI to follow.\n';
|
|
486
490
|
}
|
|
@@ -840,6 +844,118 @@ function countPreservedLearnings(workspaceRoot, userId) {
|
|
|
840
844
|
rawSignals,
|
|
841
845
|
};
|
|
842
846
|
}
|
|
847
|
+
// ── Issue #833: Brain learning-dot projection ──────────────────────────────
|
|
848
|
+
// Personal learning signals the FRAIM brain overlays on the SVG. Pending L0
|
|
849
|
+
// signals (raw coaching moments + unsynthesized retros) cluster in the
|
|
850
|
+
// temporal-memory region; processed L1/L2 learnings map to a fixed brain region
|
|
851
|
+
// per learning type (the approved Option A mapping). A read projection over the
|
|
852
|
+
// same files buildLearningContextSection injects — no new storage.
|
|
853
|
+
/** Fixed learning-type → brain-region mapping (issue #833, approved spec). */
|
|
854
|
+
exports.BRAIN_LEARNING_FILETYPE_TO_REGION = {
|
|
855
|
+
'mistake-patterns': 'prefrontal-cortex', // rule following / constraint enforcement
|
|
856
|
+
'preferences': 'right-parietal-experience', // experience / audience fit
|
|
857
|
+
'validated-patterns': 'left-parietal-analysis', // engineering / evaluation judgment
|
|
858
|
+
'manager-coaching': 'right-frontal-strategy', // strategy / framing (reverse mentoring)
|
|
859
|
+
};
|
|
860
|
+
const BRAIN_LEARNING_FILETYPE_LABEL = {
|
|
861
|
+
'mistake-patterns': 'Mistake pattern',
|
|
862
|
+
'preferences': 'Preference',
|
|
863
|
+
'validated-patterns': 'Validated pattern',
|
|
864
|
+
'manager-coaching': 'Manager coaching',
|
|
865
|
+
};
|
|
866
|
+
const PENDING_KIND_LABEL = {
|
|
867
|
+
'coaching-moment': 'Coaching moment',
|
|
868
|
+
'retrospective': 'Retrospective',
|
|
869
|
+
};
|
|
870
|
+
// Parse `## [P-…] Title` entries out of a learning file WITH their effective
|
|
871
|
+
// score + last-seen (unlike parseLearningEntries, which drops that bookkeeping
|
|
872
|
+
// for the Hub text display). Used only by the brain-dot projection.
|
|
873
|
+
function parseBrainLearningEntries(filePath, fileType) {
|
|
874
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
875
|
+
return [];
|
|
876
|
+
let content;
|
|
877
|
+
try {
|
|
878
|
+
content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
return [];
|
|
882
|
+
}
|
|
883
|
+
const out = [];
|
|
884
|
+
const headingRe = learningEntryHeadingRegex();
|
|
885
|
+
const now = new Date();
|
|
886
|
+
let current = null;
|
|
887
|
+
const flush = () => {
|
|
888
|
+
if (!current)
|
|
889
|
+
return;
|
|
890
|
+
const rawScore = current.lastSeen
|
|
891
|
+
? computeEffectiveScore(current.severity, current.lastSeen, current.recurrences, fileType, now)
|
|
892
|
+
: null;
|
|
893
|
+
out.push({
|
|
894
|
+
title: current.title,
|
|
895
|
+
severity: current.severity,
|
|
896
|
+
score: rawScore === null ? null : Math.round(rawScore * 10) / 10,
|
|
897
|
+
lastSeen: current.lastSeen || null,
|
|
898
|
+
});
|
|
899
|
+
current = null;
|
|
900
|
+
};
|
|
901
|
+
for (const line of content.split(/\r?\n/)) {
|
|
902
|
+
const header = line.match(headingRe);
|
|
903
|
+
if (header) {
|
|
904
|
+
flush();
|
|
905
|
+
current = { severity: header[1], title: header[2].trim(), lastSeen: '', recurrences: 1 };
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
if (!current)
|
|
909
|
+
continue;
|
|
910
|
+
const lastSeenMatch = line.match(/^\*\*Last seen\*\*:\s*(.+)/);
|
|
911
|
+
if (lastSeenMatch) {
|
|
912
|
+
current.lastSeen = lastSeenMatch[1].trim();
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
const recurrenceMatch = line.match(/^\*\*Recurrences\*\*:\s*(\d+)/);
|
|
916
|
+
if (recurrenceMatch) {
|
|
917
|
+
current.recurrences = parseInt(recurrenceMatch[1], 10);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
flush();
|
|
921
|
+
return out;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Build the brain learning-dot projection (issue #833) for one user: pending L0
|
|
925
|
+
* signals + processed L1 (personal) and L2 (org) learnings grouped by file type
|
|
926
|
+
* and mapped to a fixed brain region. Reuses the same root/user resolution as
|
|
927
|
+
* buildLearningContextSection so the dots line up with the auto-loaded context.
|
|
928
|
+
*/
|
|
929
|
+
function collectBrainLearningDots(workspaceRoot, userId) {
|
|
930
|
+
const roots = getLearningRoots(workspaceRoot);
|
|
931
|
+
const resolvedUserId = resolveLearningUserId(workspaceRoot, userId, roots);
|
|
932
|
+
const pendingSignals = collectPendingL0SourceFiles(workspaceRoot, resolvedUserId, roots).map((s) => ({
|
|
933
|
+
type: s.kind,
|
|
934
|
+
typeLabel: PENDING_KIND_LABEL[s.kind],
|
|
935
|
+
fileName: s.displayPath.split('/').pop() || s.displayPath,
|
|
936
|
+
displayPath: s.displayPath,
|
|
937
|
+
}));
|
|
938
|
+
const resolvePersonal = (fileName) => resolvePersonalLearningFile(roots.repoLearningsBase, roots.managerCacheBase, roots.managerCacheDisplayBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, fileName);
|
|
939
|
+
const processed = [];
|
|
940
|
+
const fileTypes = ['mistake-patterns', 'preferences', 'validated-patterns', 'manager-coaching'];
|
|
941
|
+
for (const fileType of fileTypes) {
|
|
942
|
+
const region = exports.BRAIN_LEARNING_FILETYPE_TO_REGION[fileType];
|
|
943
|
+
const typeLabel = BRAIN_LEARNING_FILETYPE_LABEL[fileType];
|
|
944
|
+
// L1 personal
|
|
945
|
+
const personal = resolvePersonal(`${resolvedUserId}-${fileType}.md`);
|
|
946
|
+
const personalEntries = parseBrainLearningEntries(personal.path, fileType);
|
|
947
|
+
if (personalEntries.length) {
|
|
948
|
+
processed.push({ type: fileType, typeLabel, region, scope: 'personal', displayPath: personal.displayPath, entries: personalEntries });
|
|
949
|
+
}
|
|
950
|
+
// L2 org
|
|
951
|
+
const org = resolveOrgLearningFile(roots.repoLearningsBase, `org-${fileType}.md`);
|
|
952
|
+
const orgEntries = parseBrainLearningEntries(org.path, fileType);
|
|
953
|
+
if (orgEntries.length) {
|
|
954
|
+
processed.push({ type: fileType, typeLabel, region, scope: 'org', displayPath: org.displayPath, entries: orgEntries });
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return { pending: { count: pendingSignals.length, signals: pendingSignals }, processed };
|
|
958
|
+
}
|
|
843
959
|
exports.CATEGORY_TO_FILETYPE = {
|
|
844
960
|
avoid: 'mistake-patterns',
|
|
845
961
|
preference: 'preferences',
|
|
@@ -63,7 +63,7 @@ class TelemetryMiddleware {
|
|
|
63
63
|
// session-active gate is exempted.
|
|
64
64
|
// Issue #701/#744 - hosted AI Hub HTTP APIs are browser/gateway routes,
|
|
65
65
|
// not MCP tool calls. They are still authenticated by API key/session.
|
|
66
|
-
const exemptPaths = ['/health', '/admin', '/api/registry/sync', '/api/registry/github-workflows', '/api/registry/topology/personalized', '/api/registry/topology/usage-heatmap', '/api/org/', '/api/manager/', '/api/ai-hub/', '/api/payment/', '/api/sales/', '/api/signup', '/api/providers', '/api/analytics', '/analytics', '/api/auth/', '/api/account/'];
|
|
66
|
+
const exemptPaths = ['/health', '/admin', '/api/registry/sync', '/api/registry/github-workflows', '/api/registry/topology/personalized', '/api/registry/topology/usage-heatmap', '/api/registry/topology/learnings-dots', '/api/org/', '/api/manager/', '/api/ai-hub/', '/api/payment/', '/api/sales/', '/api/signup', '/api/providers', '/api/analytics', '/analytics', '/api/auth/', '/api/account/'];
|
|
67
67
|
if (exemptPaths.some(p => req.path.startsWith(p))) {
|
|
68
68
|
return next();
|
|
69
69
|
}
|