fraim 2.0.109 β 2.0.116
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 +5 -5
- package/dist/src/cli/commands/setup.js +4 -4
- package/dist/src/core/quality-evidence.js +137 -37
- package/dist/src/core/utils/git-utils.js +53 -0
- package/dist/src/local-mcp-server/stdio-server.js +5 -1
- package/dist/src/local-mcp-server/usage-collector.js +6 -0
- package/package.json +2 -1
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
|
|
@@ -20,23 +20,70 @@
|
|
|
20
20
|
* seekMentoring handler (e.g., when the local AIMentor errors).
|
|
21
21
|
*/
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.REQUIRED_QUALITY_FIELDS = exports.QUALITY_PRODUCING_JOBS = void 0;
|
|
23
|
+
exports.REQUIRED_QUALITY_FIELDS = exports.ALL_STAGE_CATEGORIES = exports.STAGE_DISPLAY_NAMES = exports.STAGE_CATEGORY_MAP = exports.QUALITY_PRODUCING_JOBS = exports.QUALITY_REGISTRY = void 0;
|
|
24
|
+
exports.getRequiredFieldsForJob = getRequiredFieldsForJob;
|
|
24
25
|
exports.validateQualityEvidence = validateQualityEvidence;
|
|
25
26
|
exports.buildQualityRejectionMessage = buildQualityRejectionMessage;
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* Central registry of FRAIM jobs that interact with the quality telemetry system.
|
|
29
|
+
* This is the SINGLE SOURCE OF TRUTH for:
|
|
30
|
+
* 1. Stage mapping (where it appears on the dashboard).
|
|
31
|
+
* 2. Enforcement (whether it MUST emit a quality score to complete).
|
|
29
32
|
*/
|
|
30
|
-
exports.
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
+
exports.QUALITY_REGISTRY = {
|
|
34
|
+
// Customer Development
|
|
35
|
+
'process-interview-notes': { stage: 'customer-development', enforced: true },
|
|
36
|
+
'triage-customer-needs': { stage: 'customer-development', enforced: true },
|
|
37
|
+
'interview-preparation': { stage: 'customer-discovery', enforced: false },
|
|
38
|
+
// Business Strategy
|
|
39
|
+
'review-business-strategy': { stage: 'business-strategy', enforced: true },
|
|
40
|
+
'business-plan-creation': { stage: 'business-strategy', enforced: false },
|
|
41
|
+
// Product Quality
|
|
42
|
+
'code-quality-assessment': { stage: 'product-quality', enforced: true },
|
|
43
|
+
// Test Quality
|
|
44
|
+
'test-quality-assessment': { stage: 'test-quality', enforced: true },
|
|
45
|
+
// Fundraising
|
|
46
|
+
'investor-pitch-preparation': { stage: 'fundraising', enforced: false },
|
|
47
|
+
// Go-to-Market
|
|
48
|
+
'marketing-strategy-definition': { stage: 'go-to-market', enforced: false },
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated Use QUALITY_REGISTRY for new code.
|
|
52
|
+
* Derived enforcement list for backward compatibility.
|
|
53
|
+
*/
|
|
54
|
+
exports.QUALITY_PRODUCING_JOBS = Object.keys(exports.QUALITY_REGISTRY).filter((job) => exports.QUALITY_REGISTRY[job].enforced);
|
|
55
|
+
/**
|
|
56
|
+
* @deprecated Use QUALITY_REGISTRY for new code.
|
|
57
|
+
* Derived stage mappings for backward compatibility.
|
|
58
|
+
*/
|
|
59
|
+
exports.STAGE_CATEGORY_MAP = Object.keys(exports.QUALITY_REGISTRY).reduce((acc, job) => ({ ...acc, [job]: exports.QUALITY_REGISTRY[job].stage }), {});
|
|
60
|
+
/**
|
|
61
|
+
* Human-readable stage names for dashboard display.
|
|
62
|
+
*/
|
|
63
|
+
exports.STAGE_DISPLAY_NAMES = {
|
|
64
|
+
'customer-development': 'Customer Development',
|
|
65
|
+
'business-strategy': 'Business Strategy',
|
|
66
|
+
'product-quality': 'Product Quality',
|
|
67
|
+
'test-quality': 'Test Quality',
|
|
68
|
+
'fundraising': 'Fundraising',
|
|
69
|
+
'go-to-market': 'Go-to-Market',
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* All known stage categories, in display order for the tile grid.
|
|
73
|
+
*/
|
|
74
|
+
exports.ALL_STAGE_CATEGORIES = [
|
|
75
|
+
'customer-development',
|
|
76
|
+
'business-strategy',
|
|
77
|
+
'product-quality',
|
|
78
|
+
'test-quality',
|
|
79
|
+
'fundraising',
|
|
80
|
+
'go-to-market',
|
|
33
81
|
];
|
|
34
82
|
/**
|
|
35
|
-
* Required
|
|
36
|
-
*
|
|
37
|
-
* allowed so per-job assessment can evolve β but these five gate completion.
|
|
83
|
+
* Required fields for customer-development jobs (legacy V1 schema).
|
|
84
|
+
* These jobs require participant and evidence sub-dimensions.
|
|
38
85
|
*/
|
|
39
|
-
|
|
86
|
+
const CUSTOMER_DEV_REQUIRED_FIELDS = [
|
|
40
87
|
{ path: 'composite', type: 'number' },
|
|
41
88
|
{ path: 'participant.fit', type: 'number' },
|
|
42
89
|
{ path: 'participant.urgency', type: 'number' },
|
|
@@ -44,15 +91,38 @@ exports.REQUIRED_QUALITY_FIELDS = [
|
|
|
44
91
|
{ path: 'evidence.quoteSpecificityAvg', type: 'number' }
|
|
45
92
|
];
|
|
46
93
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
94
|
+
* Required fields for all other quality-producing jobs.
|
|
95
|
+
* Only composite and coaching are universally required β sub-dimensions
|
|
96
|
+
* vary by stage and are validated holistically, not mechanically.
|
|
97
|
+
*/
|
|
98
|
+
const UNIVERSAL_REQUIRED_FIELDS = [
|
|
99
|
+
{ path: 'composite', type: 'number' },
|
|
100
|
+
];
|
|
101
|
+
/**
|
|
102
|
+
* Returns the required quality fields for a given job.
|
|
103
|
+
* Customer-development jobs retain the strict V1 schema.
|
|
104
|
+
* All other jobs require only a composite score (principle-based, not prescriptive).
|
|
105
|
+
*/
|
|
106
|
+
function getRequiredFieldsForJob(jobName) {
|
|
107
|
+
const entry = exports.QUALITY_REGISTRY[jobName];
|
|
108
|
+
if (entry?.stage === 'customer-development') {
|
|
109
|
+
return CUSTOMER_DEV_REQUIRED_FIELDS;
|
|
110
|
+
}
|
|
111
|
+
return UNIVERSAL_REQUIRED_FIELDS;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* @deprecated Use getRequiredFieldsForJob() for stage-aware validation.
|
|
115
|
+
* Kept for backward compatibility with existing imports.
|
|
116
|
+
*/
|
|
117
|
+
exports.REQUIRED_QUALITY_FIELDS = CUSTOMER_DEV_REQUIRED_FIELDS;
|
|
118
|
+
/**
|
|
119
|
+
* Validate an `evidence.quality` object against the required fields for
|
|
120
|
+
* a given job. If no jobName is provided, falls back to the universal
|
|
121
|
+
* schema (composite only).
|
|
49
122
|
*
|
|
50
|
-
*
|
|
51
|
-
* required field and emit a per-field error, so the agent sees the
|
|
52
|
-
* complete schema in one round rather than being told only that
|
|
53
|
-
* `quality` itself is missing.
|
|
123
|
+
* Returns null if valid, or an array of human-readable error strings.
|
|
54
124
|
*/
|
|
55
|
-
function validateQualityEvidence(quality) {
|
|
125
|
+
function validateQualityEvidence(quality, jobName) {
|
|
56
126
|
const errors = [];
|
|
57
127
|
const isMissing = quality === undefined || quality === null;
|
|
58
128
|
if (isMissing) {
|
|
@@ -64,7 +134,8 @@ function validateQualityEvidence(quality) {
|
|
|
64
134
|
const effective = isMissing || typeof quality !== 'object' || Array.isArray(quality)
|
|
65
135
|
? undefined
|
|
66
136
|
: quality;
|
|
67
|
-
|
|
137
|
+
const requiredFields = jobName ? getRequiredFieldsForJob(jobName) : UNIVERSAL_REQUIRED_FIELDS;
|
|
138
|
+
for (const { path, type } of requiredFields) {
|
|
68
139
|
const parts = path.split('.');
|
|
69
140
|
let cursor = effective;
|
|
70
141
|
for (const part of parts) {
|
|
@@ -81,6 +152,10 @@ function validateQualityEvidence(quality) {
|
|
|
81
152
|
errors.push(`evidence.quality.${path} must be a ${type} (got ${got})`);
|
|
82
153
|
}
|
|
83
154
|
}
|
|
155
|
+
// Strict schema enforcement: No nested wrappers (Issue 255)
|
|
156
|
+
if (effective && effective.dimensions) {
|
|
157
|
+
errors.push('evidence.quality.dimensions is forbidden. Use a flat schema where all scores are direct properties of the quality object.');
|
|
158
|
+
}
|
|
84
159
|
return errors.length > 0 ? errors : null;
|
|
85
160
|
}
|
|
86
161
|
/**
|
|
@@ -91,31 +166,56 @@ function validateQualityEvidence(quality) {
|
|
|
91
166
|
*/
|
|
92
167
|
function buildQualityRejectionMessage(jobName, currentPhase, errors) {
|
|
93
168
|
const errorBullets = errors.map(e => `- ${e}`).join('\n');
|
|
169
|
+
const entry = exports.QUALITY_REGISTRY[jobName];
|
|
170
|
+
const isCustomerDev = entry?.stage === 'customer-development';
|
|
171
|
+
const schemaExample = isCustomerDev
|
|
172
|
+
? [
|
|
173
|
+
'```javascript',
|
|
174
|
+
'evidence: {',
|
|
175
|
+
' quality: {',
|
|
176
|
+
' composite: <number 0-10>,',
|
|
177
|
+
' participant: { fit: <number 1-10>, urgency: <number 1-10>, authority: <number 1-10> },',
|
|
178
|
+
' evidence: {',
|
|
179
|
+
' quoteSpecificityAvg: <number 1-10>',
|
|
180
|
+
' // other fields are allowed and encouraged but not required',
|
|
181
|
+
' },',
|
|
182
|
+
' coaching: "<actionable recommendation>",',
|
|
183
|
+
' interviewee: "<name>",',
|
|
184
|
+
' company: "<company>"',
|
|
185
|
+
' }',
|
|
186
|
+
'}',
|
|
187
|
+
'```'
|
|
188
|
+
]
|
|
189
|
+
: [
|
|
190
|
+
'```javascript',
|
|
191
|
+
'evidence: {',
|
|
192
|
+
' quality: {',
|
|
193
|
+
' composite: <number 0-10>,',
|
|
194
|
+
' coaching: "<actionable recommendation>",',
|
|
195
|
+
' marketEvidence: { score: <number>, rationale: "<string>" }, // flat, NO "dimensions" wrapper',
|
|
196
|
+
' unitEconomics: { score: <number>, rationale: "<string>" },',
|
|
197
|
+
' // add other sub-scores as direct properties',
|
|
198
|
+
' }',
|
|
199
|
+
'}',
|
|
200
|
+
'```'
|
|
201
|
+
];
|
|
202
|
+
const importantNote = [
|
|
203
|
+
'',
|
|
204
|
+
'> [!IMPORTANT]',
|
|
205
|
+
'> **Enforcement Notice**: Quality scores MUST follow a flat schema. Do NOT nest sub-dimension scores under a `dimensions` property. All sub-dimension objects must be direct properties of the `quality` object.',
|
|
206
|
+
''
|
|
207
|
+
];
|
|
94
208
|
return [
|
|
95
209
|
`β **Job completion rejected** for \`${jobName}\`.`,
|
|
96
210
|
'',
|
|
97
|
-
`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
|
|
211
|
+
`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\`:`,
|
|
98
212
|
'',
|
|
99
213
|
errorBullets,
|
|
214
|
+
...importantNote,
|
|
215
|
+
`Required minimum schema for \`${entry?.stage ?? 'unknown'}\` stage:`,
|
|
100
216
|
'',
|
|
101
|
-
|
|
102
|
-
'',
|
|
103
|
-
'```javascript',
|
|
104
|
-
'evidence: {',
|
|
105
|
-
' quality: {',
|
|
106
|
-
' composite: <number 0-10>,',
|
|
107
|
-
' participant: { fit: <number 1-10>, urgency: <number 1-10>, authority: <number 1-10> },',
|
|
108
|
-
' evidence: {',
|
|
109
|
-
' quoteSpecificityAvg: <number 1-10>',
|
|
110
|
-
' // other fields are allowed and encouraged but not required',
|
|
111
|
-
' },',
|
|
112
|
-
' interviewee: "<name>",',
|
|
113
|
-
' company: "<company>",',
|
|
114
|
-
' artifactPath: "docs/customer-development/<slug>-interview.md"',
|
|
115
|
-
' }',
|
|
116
|
-
'}',
|
|
117
|
-
'```',
|
|
217
|
+
...schemaExample,
|
|
118
218
|
'',
|
|
119
|
-
`You are still in phase \`${currentPhase}\`. The job is **not** marked complete. Build the \`evidence.quality\` object from your
|
|
219
|
+
`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.`
|
|
120
220
|
].join('\n');
|
|
121
221
|
}
|
|
@@ -5,6 +5,7 @@ exports.determineDatabaseName = determineDatabaseName;
|
|
|
5
5
|
exports.getCurrentGitBranch = getCurrentGitBranch;
|
|
6
6
|
exports.determineSchema = determineSchema;
|
|
7
7
|
exports.getDefaultBranch = getDefaultBranch;
|
|
8
|
+
exports.sanitizeRepoIdentifier = sanitizeRepoIdentifier;
|
|
8
9
|
const child_process_1 = require("child_process");
|
|
9
10
|
/**
|
|
10
11
|
* Gets a unique port based on the current git branch name (if it's an issue branch)
|
|
@@ -93,3 +94,55 @@ function getDefaultBranch() {
|
|
|
93
94
|
// Default fallback
|
|
94
95
|
return 'main';
|
|
95
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Sanitizes a repository identifier by stripping local paths and normalizing to standard HTTPS URLs.
|
|
99
|
+
* Mandatory per Compliance requirement (C1) to ensure data privacy.
|
|
100
|
+
*
|
|
101
|
+
* @param repoUrl The raw repository URL or path to sanitize
|
|
102
|
+
* @returns Normalized HTTPS URL string, or undefined if the input is a local path or invalid
|
|
103
|
+
*/
|
|
104
|
+
function sanitizeRepoIdentifier(repoUrl) {
|
|
105
|
+
if (!repoUrl || typeof repoUrl !== 'string') {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const trimmed = repoUrl.trim();
|
|
109
|
+
// Reject local paths (Windows and POSIX)
|
|
110
|
+
if (trimmed.startsWith('/') ||
|
|
111
|
+
trimmed.startsWith('\\') ||
|
|
112
|
+
/^[a-zA-Z]:\\/.test(trimmed) ||
|
|
113
|
+
trimmed.startsWith('./') ||
|
|
114
|
+
trimmed.startsWith('../')) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
// Normalize GitHub/GitLab SSH and HTTPS URLs
|
|
118
|
+
// Patterns:
|
|
119
|
+
// git@github.com:owner/repo.git
|
|
120
|
+
// https://github.com/owner/repo.git
|
|
121
|
+
// git@gitlab.com:owner/repo.git
|
|
122
|
+
// https://gitlab.com/owner/repo.git
|
|
123
|
+
const gitPattern = /^(?:git@|https:\/\/)(github\.com|gitlab\.com)[:/]([^/]+\/[^/.]+?)(?:\.git)?$/i;
|
|
124
|
+
const match = trimmed.match(gitPattern);
|
|
125
|
+
if (match) {
|
|
126
|
+
const [, domain, path] = match;
|
|
127
|
+
return `https://${domain.toLowerCase()}/${path}`;
|
|
128
|
+
}
|
|
129
|
+
// If it looks like a generic URL but not from approved providers, reject for privacy
|
|
130
|
+
try {
|
|
131
|
+
const url = new URL(trimmed);
|
|
132
|
+
if (url.hostname.includes('github.com') || url.hostname.includes('gitlab.com')) {
|
|
133
|
+
let path = url.pathname;
|
|
134
|
+
if (path.startsWith('/'))
|
|
135
|
+
path = path.substring(1);
|
|
136
|
+
if (path.endsWith('.git'))
|
|
137
|
+
path = path.substring(0, path.length - 4);
|
|
138
|
+
// Only return if it matches owner/repo structure
|
|
139
|
+
if (path.split('/').length >= 2) {
|
|
140
|
+
return `https://${url.hostname.toLowerCase()}/${path}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
// Not a valid URL
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
@@ -683,6 +683,7 @@ class FraimLocalMCPServer {
|
|
|
683
683
|
repoInfo.owner = owner;
|
|
684
684
|
}
|
|
685
685
|
this.repoInfo = repoInfo;
|
|
686
|
+
this.usageCollector.setRepoIdentifier(repoInfo.url || null);
|
|
686
687
|
const repoLabel = this.repoInfo.owner
|
|
687
688
|
? `${this.repoInfo.owner}/${this.repoInfo.name}`
|
|
688
689
|
: this.repoInfo.projectPath || this.repoInfo.name;
|
|
@@ -1681,7 +1682,7 @@ class FraimLocalMCPServer {
|
|
|
1681
1682
|
const isFinalCompletion = args.status === 'complete' &&
|
|
1682
1683
|
(tutoringResponse.nextPhase === null || tutoringResponse.nextPhase === undefined);
|
|
1683
1684
|
if (isQualityJob && isFinalCompletion) {
|
|
1684
|
-
const qualityErrors = (0, quality_evidence_1.validateQualityEvidence)(args.evidence?.quality);
|
|
1685
|
+
const qualityErrors = (0, quality_evidence_1.validateQualityEvidence)(args.evidence?.quality, args.jobName);
|
|
1685
1686
|
if (qualityErrors) {
|
|
1686
1687
|
this.log(`β Quality enforcement rejected ${args.jobName} completion: ${qualityErrors.join('; ')}`);
|
|
1687
1688
|
const rejection = (0, quality_evidence_1.buildQualityRejectionMessage)(args.jobName, args.currentPhase, qualityErrors);
|
|
@@ -1968,6 +1969,9 @@ class FraimLocalMCPServer {
|
|
|
1968
1969
|
if (toolName && requestSessionId) {
|
|
1969
1970
|
const success = !response.error;
|
|
1970
1971
|
this.log(`π Collecting usage: ${toolName} (session: ${requestSessionId}, success: ${success})`);
|
|
1972
|
+
if (!this.repoInfo) {
|
|
1973
|
+
this.detectRepoInfo();
|
|
1974
|
+
}
|
|
1971
1975
|
// Capture the current queue size before collection
|
|
1972
1976
|
const beforeCount = this.usageCollector.getEventCount();
|
|
1973
1977
|
try {
|
|
@@ -13,6 +13,7 @@ class UsageCollector {
|
|
|
13
13
|
constructor() {
|
|
14
14
|
this.events = [];
|
|
15
15
|
this.userId = null;
|
|
16
|
+
this.repoIdentifier = null;
|
|
16
17
|
}
|
|
17
18
|
static resolveMentoringJobName(args) {
|
|
18
19
|
if (!args || typeof args !== 'object') {
|
|
@@ -36,6 +37,9 @@ class UsageCollector {
|
|
|
36
37
|
setUserId(userId) {
|
|
37
38
|
this.userId = userId;
|
|
38
39
|
}
|
|
40
|
+
setRepoIdentifier(repoIdentifier) {
|
|
41
|
+
this.repoIdentifier = repoIdentifier;
|
|
42
|
+
}
|
|
39
43
|
/**
|
|
40
44
|
* Collect MCP tool call event
|
|
41
45
|
*/
|
|
@@ -62,6 +66,7 @@ class UsageCollector {
|
|
|
62
66
|
sessionId,
|
|
63
67
|
success,
|
|
64
68
|
category: parsed.category,
|
|
69
|
+
repoIdentifier: this.repoIdentifier || undefined,
|
|
65
70
|
args: Object.keys(analyticsArgs).length > 0 ? analyticsArgs : undefined
|
|
66
71
|
};
|
|
67
72
|
this.events.push(event);
|
|
@@ -81,6 +86,7 @@ class UsageCollector {
|
|
|
81
86
|
userId: this.userId || 'unknown',
|
|
82
87
|
sessionId,
|
|
83
88
|
success,
|
|
89
|
+
repoIdentifier: this.repoIdentifier || undefined,
|
|
84
90
|
args
|
|
85
91
|
};
|
|
86
92
|
this.events.push(event);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.116",
|
|
4
4
|
"description": "FRAIM CLI - Framework for Rigor-based AI Management (alias for fraim-framework)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"test": "node scripts/test-with-server.js",
|
|
18
18
|
"test:isolated": "npx tsx --test --test-reporter=spec tests/isolated/test-*.ts",
|
|
19
19
|
"test:smoke": "node scripts/test-with-server.js --tags=smoke",
|
|
20
|
+
"test:coverage": "node scripts/test-with-server.js --tags=smoke --coverage",
|
|
20
21
|
"test:stripe": "node scripts/test-with-server.js tests/test-stripe-payment-complete.ts",
|
|
21
22
|
"test:stripe:ui": "playwright test tests/ui/test-payment-ui.spec.ts",
|
|
22
23
|
"test:perf": "node scripts/test-with-server.js tests/performance/analytics-perf.ts",
|