fraim 2.0.108 β 2.0.114
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/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# π FRAIM
|
|
2
|
-
**"FRAIM
|
|
1
|
+
# π FRAIM β AI Workforce Infrastructure
|
|
2
|
+
**"Brilliant AI isn't how industries get built. Organizations are."** β FRAIM transforms every layer of the AI-powered company at once: **AI agents** become an accountable, improving workforce; **their operators** become capable AI managers who hold the line on quality, delegation, and evidence; and **executives** gain clear optics on AI proficiency across the entire organization.
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
π **The Problem with AI Coding Today**
|
|
@@ -36,7 +36,7 @@ R - Retrospectives: Continuous learning from experience
|
|
|
36
36
|
|
|
37
37
|
π€ **Works with any AI agent** (Cursor, Claude, Windsurf) - no vendor lock-in.
|
|
38
38
|
|
|
39
|
-
**The bottom line:** FRAIM isn't just about using AIβit
|
|
39
|
+
**The bottom line:** FRAIM isn't just about using AI β it transforms every layer of your AI-powered company: agents become an accountable workforce, operators become capable AI managers, and executives gain clear optics on AI proficiency across the whole org.
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
|
|
@@ -448,9 +448,9 @@ gh issue edit 123 --remove-label "phase:impl" --add-label "phase:tests"
|
|
|
448
448
|
|
|
449
449
|
## π― **The Bottom Line**
|
|
450
450
|
|
|
451
|
-
**FRAIM isn't just about using AIβit
|
|
451
|
+
**FRAIM isn't just about using AI β it transforms every layer of your AI-powered company: agents become an accountable workforce, operators become capable AI managers, and executives gain clear optics on AI proficiency across the whole org.**
|
|
452
452
|
|
|
453
|
-
Stop fighting with AI agents. Start
|
|
453
|
+
Stop fighting with AI agents. Start building your AI organization.
|
|
454
454
|
|
|
455
455
|
**This is the future of how we work.**
|
|
456
456
|
|
|
@@ -637,10 +637,10 @@ const runSetup = async (options) => {
|
|
|
637
637
|
console.log(chalk_1.default.green('\nββββββββββββββββββββββββββββββββββββββββ'));
|
|
638
638
|
console.log(chalk_1.default.green(' FRAIM is ready!'));
|
|
639
639
|
console.log(chalk_1.default.green('ββββββββββββββββββββββββββββββββββββββββ'));
|
|
640
|
-
console.log(chalk_1.default.white('\n FRAIM is
|
|
641
|
-
console.log(chalk_1.default.white('
|
|
642
|
-
console.log(chalk_1.default.white('
|
|
643
|
-
console.log(chalk_1.default.white('
|
|
640
|
+
console.log(chalk_1.default.white('\n FRAIM is AI Workforce Infrastructure. It transforms every'));
|
|
641
|
+
console.log(chalk_1.default.white(' layer of your AI-powered work at once: AI agents become an'));
|
|
642
|
+
console.log(chalk_1.default.white(' accountable workforce, you become a capable AI manager, and'));
|
|
643
|
+
console.log(chalk_1.default.white(' your leadership gains clear optics on AI proficiency.'));
|
|
644
644
|
console.log(chalk_1.default.gray('\n 60+ jobs across engineering, marketing, fundraising,'));
|
|
645
645
|
console.log(chalk_1.default.gray(' legal, product, hiring, customer development, and more.'));
|
|
646
646
|
// Show which IDEs were configured and how to use FRAIM in each
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Quality Evidence Validation (Issue #251)
|
|
4
|
+
*
|
|
5
|
+
* Pure helpers for validating the `evidence.quality` object that
|
|
6
|
+
* quality-producing FRAIM jobs must emit on their final seekMentoring
|
|
7
|
+
* completion. Lives in src/core so both the local MCP proxy and the
|
|
8
|
+
* remote server can import the same contract without cross-layer
|
|
9
|
+
* dependencies.
|
|
10
|
+
*
|
|
11
|
+
* Two call sites currently:
|
|
12
|
+
* 1. src/local-mcp-server/stdio-server.ts β enforces on local
|
|
13
|
+
* seekMentoring completion and POSTs valid scores to the remote
|
|
14
|
+
* via /api/analytics/quality-score.
|
|
15
|
+
* 2. src/routes/analytics.ts β defense-in-depth validation on the
|
|
16
|
+
* /api/analytics/quality-score endpoint.
|
|
17
|
+
*
|
|
18
|
+
* A third (rare) call site lives in src/services/mcp-service.ts for
|
|
19
|
+
* the case where the local proxy falls through to the remote's own
|
|
20
|
+
* seekMentoring handler (e.g., when the local AIMentor errors).
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.REQUIRED_QUALITY_FIELDS = exports.ALL_STAGE_CATEGORIES = exports.STAGE_DISPLAY_NAMES = exports.STAGE_CATEGORY_MAP = exports.QUALITY_PRODUCING_JOBS = void 0;
|
|
24
|
+
exports.getRequiredFieldsForJob = getRequiredFieldsForJob;
|
|
25
|
+
exports.validateQualityEvidence = validateQualityEvidence;
|
|
26
|
+
exports.buildQualityRejectionMessage = buildQualityRejectionMessage;
|
|
27
|
+
/**
|
|
28
|
+
* Jobs that produce a quality assessment and MUST emit a quality score
|
|
29
|
+
* via `evidence.quality` on their final seekMentoring completion.
|
|
30
|
+
*/
|
|
31
|
+
exports.QUALITY_PRODUCING_JOBS = [
|
|
32
|
+
// Customer Development
|
|
33
|
+
'process-interview-notes',
|
|
34
|
+
'triage-customer-needs',
|
|
35
|
+
// Business Strategy
|
|
36
|
+
'business-plan-creation',
|
|
37
|
+
'pricing-strategy-definition',
|
|
38
|
+
'founder-market-fit-analysis',
|
|
39
|
+
'review-business-strategy',
|
|
40
|
+
// Product Quality
|
|
41
|
+
'code-quality-assessment',
|
|
42
|
+
// Test Quality
|
|
43
|
+
'test-standards-evaluation',
|
|
44
|
+
'test-coverage-evaluation',
|
|
45
|
+
'review-test-quality',
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Maps job names to their founder journey stage category.
|
|
49
|
+
* Used to auto-populate `stageCategory` on quality score records.
|
|
50
|
+
*/
|
|
51
|
+
exports.STAGE_CATEGORY_MAP = {
|
|
52
|
+
'process-interview-notes': 'customer-development',
|
|
53
|
+
'triage-customer-needs': 'customer-development',
|
|
54
|
+
'business-plan-creation': 'business-strategy',
|
|
55
|
+
'pricing-strategy-definition': 'business-strategy',
|
|
56
|
+
'founder-market-fit-analysis': 'business-strategy',
|
|
57
|
+
'review-business-strategy': 'business-strategy',
|
|
58
|
+
'code-quality-assessment': 'product-quality',
|
|
59
|
+
'test-standards-evaluation': 'test-quality',
|
|
60
|
+
'test-coverage-evaluation': 'test-quality',
|
|
61
|
+
'review-test-quality': 'test-quality',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Human-readable stage names for dashboard display.
|
|
65
|
+
*/
|
|
66
|
+
exports.STAGE_DISPLAY_NAMES = {
|
|
67
|
+
'customer-development': 'Customer Development',
|
|
68
|
+
'business-strategy': 'Business Strategy',
|
|
69
|
+
'product-quality': 'Product Quality',
|
|
70
|
+
'test-quality': 'Test Quality',
|
|
71
|
+
'fundraising': 'Fundraising',
|
|
72
|
+
'go-to-market': 'Go-to-Market',
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* All known stage categories, in display order for the tile grid.
|
|
76
|
+
*/
|
|
77
|
+
exports.ALL_STAGE_CATEGORIES = [
|
|
78
|
+
'customer-development',
|
|
79
|
+
'business-strategy',
|
|
80
|
+
'product-quality',
|
|
81
|
+
'test-quality',
|
|
82
|
+
'fundraising',
|
|
83
|
+
'go-to-market',
|
|
84
|
+
];
|
|
85
|
+
/**
|
|
86
|
+
* Required fields for customer-development jobs (legacy V1 schema).
|
|
87
|
+
* These jobs require participant and evidence sub-dimensions.
|
|
88
|
+
*/
|
|
89
|
+
const CUSTOMER_DEV_REQUIRED_FIELDS = [
|
|
90
|
+
{ path: 'composite', type: 'number' },
|
|
91
|
+
{ path: 'participant.fit', type: 'number' },
|
|
92
|
+
{ path: 'participant.urgency', type: 'number' },
|
|
93
|
+
{ path: 'participant.authority', type: 'number' },
|
|
94
|
+
{ path: 'evidence.quoteSpecificityAvg', type: 'number' }
|
|
95
|
+
];
|
|
96
|
+
/**
|
|
97
|
+
* Required fields for all other quality-producing jobs.
|
|
98
|
+
* Only composite and coaching are universally required β sub-dimensions
|
|
99
|
+
* vary by stage and are validated holistically, not mechanically.
|
|
100
|
+
*/
|
|
101
|
+
const UNIVERSAL_REQUIRED_FIELDS = [
|
|
102
|
+
{ path: 'composite', type: 'number' },
|
|
103
|
+
];
|
|
104
|
+
/**
|
|
105
|
+
* Returns the required quality fields for a given job.
|
|
106
|
+
* Customer-development jobs retain the strict V1 schema.
|
|
107
|
+
* All other jobs require only a composite score (principle-based, not prescriptive).
|
|
108
|
+
*/
|
|
109
|
+
function getRequiredFieldsForJob(jobName) {
|
|
110
|
+
const stage = exports.STAGE_CATEGORY_MAP[jobName];
|
|
111
|
+
if (stage === 'customer-development') {
|
|
112
|
+
return CUSTOMER_DEV_REQUIRED_FIELDS;
|
|
113
|
+
}
|
|
114
|
+
return UNIVERSAL_REQUIRED_FIELDS;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @deprecated Use getRequiredFieldsForJob() for stage-aware validation.
|
|
118
|
+
* Kept for backward compatibility with existing imports.
|
|
119
|
+
*/
|
|
120
|
+
exports.REQUIRED_QUALITY_FIELDS = CUSTOMER_DEV_REQUIRED_FIELDS;
|
|
121
|
+
/**
|
|
122
|
+
* Validate an `evidence.quality` object against the required fields for
|
|
123
|
+
* a given job. If no jobName is provided, falls back to the universal
|
|
124
|
+
* schema (composite only).
|
|
125
|
+
*
|
|
126
|
+
* Returns null if valid, or an array of human-readable error strings.
|
|
127
|
+
*/
|
|
128
|
+
function validateQualityEvidence(quality, jobName) {
|
|
129
|
+
const errors = [];
|
|
130
|
+
const isMissing = quality === undefined || quality === null;
|
|
131
|
+
if (isMissing) {
|
|
132
|
+
errors.push('evidence.quality is missing');
|
|
133
|
+
}
|
|
134
|
+
else if (typeof quality !== 'object' || Array.isArray(quality)) {
|
|
135
|
+
errors.push('evidence.quality must be an object');
|
|
136
|
+
}
|
|
137
|
+
const effective = isMissing || typeof quality !== 'object' || Array.isArray(quality)
|
|
138
|
+
? undefined
|
|
139
|
+
: quality;
|
|
140
|
+
const requiredFields = jobName ? getRequiredFieldsForJob(jobName) : UNIVERSAL_REQUIRED_FIELDS;
|
|
141
|
+
for (const { path, type } of requiredFields) {
|
|
142
|
+
const parts = path.split('.');
|
|
143
|
+
let cursor = effective;
|
|
144
|
+
for (const part of parts) {
|
|
145
|
+
if (cursor && typeof cursor === 'object' && part in cursor) {
|
|
146
|
+
cursor = cursor[part];
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
cursor = undefined;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (typeof cursor !== type || (type === 'number' && Number.isNaN(cursor))) {
|
|
154
|
+
const got = cursor === undefined ? 'missing' : (Number.isNaN(cursor) ? 'NaN' : typeof cursor);
|
|
155
|
+
errors.push(`evidence.quality.${path} must be a ${type} (got ${got})`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return errors.length > 0 ? errors : null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Build the rejection message sent back to the agent when a quality-producing
|
|
162
|
+
* job tries to complete without a valid `evidence.quality` object. The message
|
|
163
|
+
* lists the specific errors and the required minimum schema so the agent can
|
|
164
|
+
* fix everything in one round.
|
|
165
|
+
*/
|
|
166
|
+
function buildQualityRejectionMessage(jobName, currentPhase, errors) {
|
|
167
|
+
const errorBullets = errors.map(e => `- ${e}`).join('\n');
|
|
168
|
+
const stage = exports.STAGE_CATEGORY_MAP[jobName];
|
|
169
|
+
const isCustomerDev = stage === 'customer-development';
|
|
170
|
+
const schemaExample = isCustomerDev
|
|
171
|
+
? [
|
|
172
|
+
'```javascript',
|
|
173
|
+
'evidence: {',
|
|
174
|
+
' quality: {',
|
|
175
|
+
' composite: <number 0-10>,',
|
|
176
|
+
' participant: { fit: <number 1-10>, urgency: <number 1-10>, authority: <number 1-10> },',
|
|
177
|
+
' evidence: {',
|
|
178
|
+
' quoteSpecificityAvg: <number 1-10>',
|
|
179
|
+
' // other fields are allowed and encouraged but not required',
|
|
180
|
+
' },',
|
|
181
|
+
' coaching: "<actionable recommendation>",',
|
|
182
|
+
' interviewee: "<name>",',
|
|
183
|
+
' company: "<company>"',
|
|
184
|
+
' }',
|
|
185
|
+
'}',
|
|
186
|
+
'```'
|
|
187
|
+
]
|
|
188
|
+
: [
|
|
189
|
+
'```javascript',
|
|
190
|
+
'evidence: {',
|
|
191
|
+
' quality: {',
|
|
192
|
+
' composite: <number 0-10>,',
|
|
193
|
+
' coaching: "<actionable recommendation>",',
|
|
194
|
+
' // include per-dimension scores with rationale fields',
|
|
195
|
+
' // additional fields are encouraged β schema is flexible per stage',
|
|
196
|
+
' }',
|
|
197
|
+
'}',
|
|
198
|
+
'```'
|
|
199
|
+
];
|
|
200
|
+
return [
|
|
201
|
+
`β **Job completion rejected** for \`${jobName}\`.`,
|
|
202
|
+
'',
|
|
203
|
+
`This job is required to emit a quality score on its final \`seekMentoring\` call so the result is captured in \`fraim_quality_scores\` for the quality dashboard. The following problems were found in \`evidence.quality\`:`,
|
|
204
|
+
'',
|
|
205
|
+
errorBullets,
|
|
206
|
+
'',
|
|
207
|
+
`Required minimum schema for \`${stage ?? 'unknown'}\` stage:`,
|
|
208
|
+
'',
|
|
209
|
+
...schemaExample,
|
|
210
|
+
'',
|
|
211
|
+
`You are still in phase \`${currentPhase}\`. The job is **not** marked complete. Build the \`evidence.quality\` object from your quality assessment work and resubmit this final phase.`
|
|
212
|
+
].join('\n');
|
|
213
|
+
}
|
|
@@ -29,6 +29,7 @@ const provider_utils_1 = require("../core/utils/provider-utils");
|
|
|
29
29
|
const object_utils_1 = require("../core/utils/object-utils");
|
|
30
30
|
const local_registry_resolver_1 = require("../core/utils/local-registry-resolver");
|
|
31
31
|
const ai_mentor_1 = require("../core/ai-mentor");
|
|
32
|
+
const quality_evidence_1 = require("../core/quality-evidence");
|
|
32
33
|
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
33
34
|
const usage_collector_js_1 = require("./usage-collector.js");
|
|
34
35
|
const otlp_metrics_receiver_js_1 = require("./otlp-metrics-receiver.js");
|
|
@@ -1664,6 +1665,40 @@ class FraimLocalMCPServer {
|
|
|
1664
1665
|
const mentor = this.getMentor(requestSessionId);
|
|
1665
1666
|
const tutoringResponse = await mentor.handleMentoringRequest(args);
|
|
1666
1667
|
this.log(`β
Local seekMentoring succeeded for ${args.jobName}:${args.currentPhase}`);
|
|
1668
|
+
// Quality enforcement (Issue #251).
|
|
1669
|
+
//
|
|
1670
|
+
// The local proxy owns seekMentoring for personalized-job support.
|
|
1671
|
+
// That means the server-side enforcement in mcp-service.handleSeekMentoring
|
|
1672
|
+
// rarely fires in practice, so this is the primary enforcement point.
|
|
1673
|
+
//
|
|
1674
|
+
// When a QUALITY_PRODUCING_JOBS job reaches its final phase
|
|
1675
|
+
// (nextPhase === null) with status === 'complete', the agent MUST
|
|
1676
|
+
// include a valid `evidence.quality` object. If invalid, swap the
|
|
1677
|
+
// accomplishment message for a rejection and DO NOT emit. If valid,
|
|
1678
|
+
// fire-and-forget POST to /api/analytics/quality-score so the row
|
|
1679
|
+
// lands in fraim_quality_scores.
|
|
1680
|
+
const isQualityJob = quality_evidence_1.QUALITY_PRODUCING_JOBS.includes(args.jobName);
|
|
1681
|
+
const isFinalCompletion = args.status === 'complete' &&
|
|
1682
|
+
(tutoringResponse.nextPhase === null || tutoringResponse.nextPhase === undefined);
|
|
1683
|
+
if (isQualityJob && isFinalCompletion) {
|
|
1684
|
+
const qualityErrors = (0, quality_evidence_1.validateQualityEvidence)(args.evidence?.quality, args.jobName);
|
|
1685
|
+
if (qualityErrors) {
|
|
1686
|
+
this.log(`β Quality enforcement rejected ${args.jobName} completion: ${qualityErrors.join('; ')}`);
|
|
1687
|
+
const rejection = (0, quality_evidence_1.buildQualityRejectionMessage)(args.jobName, args.currentPhase, qualityErrors);
|
|
1688
|
+
return await this.finalizeLocalToolTextResponse(request, requestSessionId, requestId, rejection);
|
|
1689
|
+
}
|
|
1690
|
+
// Valid payload. Emit to the remote asynchronously.
|
|
1691
|
+
this.emitQualityScoreToRemote({
|
|
1692
|
+
jobName: args.jobName,
|
|
1693
|
+
jobId: args.jobId,
|
|
1694
|
+
sessionId: requestSessionId || args.sessionId || 'unknown',
|
|
1695
|
+
quality: args.evidence.quality,
|
|
1696
|
+
artifactPath: args.evidence.quality.artifactPath
|
|
1697
|
+
}).catch((err) => {
|
|
1698
|
+
// Best-effort: log but never fail the user-facing response.
|
|
1699
|
+
this.log(`β οΈ Quality score emission failed: ${err?.message || err}`);
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1667
1702
|
return await this.finalizeLocalToolTextResponse(request, requestSessionId, requestId, tutoringResponse.message);
|
|
1668
1703
|
}
|
|
1669
1704
|
catch (error) {
|
|
@@ -1971,6 +2006,33 @@ class FraimLocalMCPServer {
|
|
|
1971
2006
|
this.log(`β οΈ Skipping usage collection: toolName=${toolName}, sessionId=${requestSessionId}`);
|
|
1972
2007
|
}
|
|
1973
2008
|
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Emit a quality score to the remote FRAIM server (Issue #251).
|
|
2011
|
+
*
|
|
2012
|
+
* Called from the seekMentoring short-circuit when a QUALITY_PRODUCING_JOBS
|
|
2013
|
+
* job reaches its final phase with a validated `evidence.quality` payload.
|
|
2014
|
+
* Fire-and-forget from the caller's perspective: a failure here is logged
|
|
2015
|
+
* but does not block the user-facing response (analytics capture is best
|
|
2016
|
+
* effort, not blocking).
|
|
2017
|
+
*
|
|
2018
|
+
* On success the remote writes a row to the `fraim_quality_scores` MongoDB
|
|
2019
|
+
* collection which powers the manager dashboard trajectory chart.
|
|
2020
|
+
*/
|
|
2021
|
+
async emitQualityScoreToRemote(payload) {
|
|
2022
|
+
const url = `${this.remoteUrl}/api/analytics/quality-score`;
|
|
2023
|
+
this.log(`π Emitting quality score β ${url} (job=${payload.jobName}, jobId=${payload.jobId})`);
|
|
2024
|
+
const response = await axios_1.default.post(url, payload, {
|
|
2025
|
+
headers: {
|
|
2026
|
+
'Content-Type': 'application/json',
|
|
2027
|
+
'x-api-key': this.apiKey
|
|
2028
|
+
},
|
|
2029
|
+
timeout: 10000
|
|
2030
|
+
});
|
|
2031
|
+
if (response.status < 200 || response.status >= 300) {
|
|
2032
|
+
throw new Error(`quality-score endpoint returned ${response.status}: ${JSON.stringify(response.data)}`);
|
|
2033
|
+
}
|
|
2034
|
+
this.log(`π β
Quality score accepted by remote (composite=${payload.quality.composite})`);
|
|
2035
|
+
}
|
|
1974
2036
|
/**
|
|
1975
2037
|
* Flush collected usage data to the remote server
|
|
1976
2038
|
*/
|