fraim-framework 2.0.63 → 2.0.65
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/bin/fraim-mcp.js +52 -19
- package/bin/fraim.js +23 -0
- package/dist/src/cli/commands/add-ide.js +53 -14
- package/dist/src/cli/commands/doctor.js +12 -24
- package/dist/src/cli/commands/init-project.js +0 -3
- package/dist/src/cli/commands/init.js +0 -2
- package/dist/src/cli/commands/mcp.js +65 -0
- package/dist/src/cli/commands/setup.js +17 -1
- package/dist/src/cli/commands/sync.js +173 -104
- package/dist/src/cli/setup/auto-mcp-setup.js +6 -4
- package/dist/src/cli/setup/mcp-config-generator.js +65 -41
- package/dist/src/fraim/issue-tracking/ado-provider.js +304 -0
- package/dist/src/fraim/issue-tracking/factory.js +63 -0
- package/dist/src/fraim/issue-tracking/github-provider.js +200 -0
- package/dist/src/fraim/issue-tracking/types.js +7 -0
- package/dist/src/fraim/issue-tracking-config.js +83 -0
- package/dist/src/local-mcp-server/stdio-server.js +91 -15
- package/dist/src/utils/remote-sync.js +130 -0
- package/package.json +3 -4
- package/dist/src/utils/enforcement-utils.js +0 -239
- package/dist/src/utils/validate-workflows.js +0 -101
- package/registry/scripts/cleanup-branch.ts +0 -341
- package/registry/scripts/code-quality-check.sh +0 -566
- package/registry/scripts/comprehensive-explorer.py +0 -297
- package/registry/scripts/create-git-labels.sh +0 -49
- package/registry/scripts/create-website-structure.js +0 -562
- package/registry/scripts/detect-tautological-tests.sh +0 -38
- package/registry/scripts/evaluate-code-quality.ts +0 -36
- package/registry/scripts/exec-with-timeout.ts +0 -122
- package/registry/scripts/generate-engagement-emails.ts +0 -830
- package/registry/scripts/interactive-explorer.py +0 -270
- package/registry/scripts/markdown-to-pdf.js +0 -395
- package/registry/scripts/newsletter-helpers.ts +0 -777
- package/registry/scripts/pdf-styles.css +0 -172
- package/registry/scripts/prep-issue.sh +0 -548
- package/registry/scripts/productivity/build-productivity-csv.mjs +0 -242
- package/registry/scripts/productivity/fetch-pr-details.mjs +0 -144
- package/registry/scripts/productivity/productivity-report.sh +0 -147
- package/registry/scripts/profile-server.ts +0 -426
- package/registry/scripts/run-thank-you-workflow.ts +0 -122
- package/registry/scripts/scrape-site.py +0 -302
- package/registry/scripts/send-newsletter-simple.ts +0 -102
- package/registry/scripts/send-thank-you-emails.ts +0 -57
- package/registry/scripts/validate-openapi-limits.ts +0 -366
- package/registry/scripts/validate-test-coverage.ts +0 -280
- package/registry/scripts/verify-pr-comments.sh +0 -74
- package/registry/scripts/verify-test-coverage.ts +0 -36
- package/registry/stubs/workflows/bootstrap/create-architecture.md +0 -11
- package/registry/stubs/workflows/bootstrap/detect-broken-windows.md +0 -11
- package/registry/stubs/workflows/bootstrap/evaluate-code-quality.md +0 -11
- package/registry/stubs/workflows/bootstrap/verify-test-coverage.md +0 -11
- package/registry/stubs/workflows/brainstorming/blue-sky-brainstorming.md +0 -11
- package/registry/stubs/workflows/brainstorming/codebase-brainstorming.md +0 -11
- package/registry/stubs/workflows/business-development/create-business-plan.md +0 -11
- package/registry/stubs/workflows/business-development/ideate-business-opportunity.md +0 -11
- package/registry/stubs/workflows/business-development/price-product.md +0 -18
- package/registry/stubs/workflows/compliance/detect-compliance-requirements.md +0 -11
- package/registry/stubs/workflows/compliance/generate-audit-evidence.md +0 -11
- package/registry/stubs/workflows/compliance/soc2-evidence-generator.md +0 -11
- package/registry/stubs/workflows/customer-development/insight-analysis.md +0 -11
- package/registry/stubs/workflows/customer-development/insight-triage.md +0 -11
- package/registry/stubs/workflows/customer-development/interview-preparation.md +0 -11
- package/registry/stubs/workflows/customer-development/linkedin-outreach.md +0 -11
- package/registry/stubs/workflows/customer-development/strategic-brainstorming.md +0 -11
- package/registry/stubs/workflows/customer-development/thank-customers.md +0 -11
- package/registry/stubs/workflows/customer-development/user-survey-dispatch.md +0 -11
- package/registry/stubs/workflows/customer-development/users-to-target.md +0 -11
- package/registry/stubs/workflows/customer-development/weekly-newsletter.md +0 -11
- package/registry/stubs/workflows/deploy/cloud-deployment.md +0 -11
- package/registry/stubs/workflows/improve-fraim/contribute.md +0 -11
- package/registry/stubs/workflows/improve-fraim/file-issue.md +0 -11
- package/registry/stubs/workflows/learning/build-skillset.md +0 -11
- package/registry/stubs/workflows/learning/synthesize-learnings.md +0 -11
- package/registry/stubs/workflows/legal/contract-review-analysis.md +0 -11
- package/registry/stubs/workflows/legal/nda.md +0 -11
- package/registry/stubs/workflows/legal/patent-filing.md +0 -11
- package/registry/stubs/workflows/legal/saas-contract-development.md +0 -11
- package/registry/stubs/workflows/legal/trademark-filing.md +0 -11
- package/registry/stubs/workflows/marketing/content-creation.md +0 -11
- package/registry/stubs/workflows/marketing/convert-to-pdf.md +0 -11
- package/registry/stubs/workflows/marketing/create-modern-website.md +0 -11
- package/registry/stubs/workflows/marketing/domain-registration.md +0 -11
- package/registry/stubs/workflows/marketing/hbr-article.md +0 -11
- package/registry/stubs/workflows/marketing/launch-checklist.md +0 -11
- package/registry/stubs/workflows/marketing/marketing-strategy.md +0 -11
- package/registry/stubs/workflows/marketing/storytelling.md +0 -11
- package/registry/stubs/workflows/performance/analyze-performance.md +0 -11
- package/registry/stubs/workflows/product-building/design.md +0 -11
- package/registry/stubs/workflows/product-building/implement.md +0 -11
- package/registry/stubs/workflows/product-building/iterate-on-pr-comments.md +0 -11
- package/registry/stubs/workflows/product-building/prep-issue.md +0 -11
- package/registry/stubs/workflows/product-building/prototype.md +0 -11
- package/registry/stubs/workflows/product-building/resolve.md +0 -11
- package/registry/stubs/workflows/product-building/retrospect.md +0 -11
- package/registry/stubs/workflows/product-building/spec.md +0 -11
- package/registry/stubs/workflows/product-building/test.md +0 -11
- package/registry/stubs/workflows/productivity-report/productivity-report.md +0 -11
- package/registry/stubs/workflows/quality-assurance/browser-validation.md +0 -11
- package/registry/stubs/workflows/quality-assurance/iterative-improvement-cycle.md +0 -11
- package/registry/stubs/workflows/replicate/replicate-discovery.md +0 -11
- package/registry/stubs/workflows/replicate/replicate-to-issues.md +0 -11
- package/registry/stubs/workflows/reviewer/review-implementation-vs-design-spec.md +0 -11
- package/registry/stubs/workflows/reviewer/review-implementation-vs-feature-spec.md +0 -11
- package/registry/stubs/workflows/startup-credits/aws-activate-application.md +0 -11
- package/registry/stubs/workflows/startup-credits/google-cloud-application.md +0 -11
- package/registry/stubs/workflows/startup-credits/microsoft-azure-application.md +0 -11
|
@@ -23,6 +23,7 @@ const fs_1 = require("fs");
|
|
|
23
23
|
const path_1 = require("path");
|
|
24
24
|
const os_1 = require("os");
|
|
25
25
|
const child_process_1 = require("child_process");
|
|
26
|
+
const crypto_1 = require("crypto");
|
|
26
27
|
const axios_1 = __importDefault(require("axios"));
|
|
27
28
|
class FraimLocalMCPServer {
|
|
28
29
|
constructor() {
|
|
@@ -34,6 +35,7 @@ class FraimLocalMCPServer {
|
|
|
34
35
|
this.repoInfo = null;
|
|
35
36
|
this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
36
37
|
this.apiKey = process.env.FRAIM_API_KEY || '';
|
|
38
|
+
this.localVersion = this.detectLocalVersion();
|
|
37
39
|
if (!this.apiKey) {
|
|
38
40
|
this.logError('❌ FRAIM_API_KEY environment variable is required');
|
|
39
41
|
process.exit(1);
|
|
@@ -41,13 +43,46 @@ class FraimLocalMCPServer {
|
|
|
41
43
|
this.log('🚀 FRAIM Local MCP Server starting...');
|
|
42
44
|
this.log(`📡 Remote server: ${this.remoteUrl}`);
|
|
43
45
|
this.log(`🔑 API key: ${this.apiKey.substring(0, 10)}...`);
|
|
46
|
+
this.log(`Local MCP version: ${this.localVersion}`);
|
|
47
|
+
this.log(`🔍 DEBUG BUILD: Machine detection v2 active`);
|
|
44
48
|
}
|
|
45
49
|
log(message) {
|
|
46
50
|
// Log to stderr (stdout is reserved for MCP protocol)
|
|
47
|
-
|
|
51
|
+
const key = this.apiKey || 'MISSING_API_KEY';
|
|
52
|
+
console.error(`[FRAIM key:${key}] ${message}`);
|
|
53
|
+
// Also log to file for debugging
|
|
54
|
+
try {
|
|
55
|
+
const fs = require('fs');
|
|
56
|
+
const logFile = require('path').join(require('os').tmpdir(), 'fraim-mcp-proxy.log');
|
|
57
|
+
fs.appendFileSync(logFile, `${new Date().toISOString()} [${key}] ${message}\n`);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
// Ignore file logging errors
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
logError(message) {
|
|
50
|
-
|
|
64
|
+
const key = this.apiKey || 'MISSING_API_KEY';
|
|
65
|
+
console.error(`[FRAIM ERROR key:${key}] ${message}`);
|
|
66
|
+
}
|
|
67
|
+
detectLocalVersion() {
|
|
68
|
+
const candidates = [
|
|
69
|
+
(0, path_1.join)(__dirname, '..', '..', '..', 'package.json'),
|
|
70
|
+
(0, path_1.join)(__dirname, '..', '..', 'package.json')
|
|
71
|
+
];
|
|
72
|
+
for (const pkgPath of candidates) {
|
|
73
|
+
try {
|
|
74
|
+
if (!(0, fs_1.existsSync)(pkgPath))
|
|
75
|
+
continue;
|
|
76
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
|
|
77
|
+
if (typeof pkg.version === 'string' && pkg.version.trim().length > 0) {
|
|
78
|
+
return pkg.version;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Ignore and try the next candidate
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return 'unknown';
|
|
51
86
|
}
|
|
52
87
|
findProjectRoot() {
|
|
53
88
|
// If we already have workspace root from MCP roots, use it
|
|
@@ -64,6 +99,12 @@ class FraimLocalMCPServer {
|
|
|
64
99
|
this.log(` INIT_CWD: ${process.env.INIT_CWD || '(not set)'}`);
|
|
65
100
|
this.log(` HOME: ${process.env.HOME || '(not set)'}`);
|
|
66
101
|
this.log(` USERPROFILE: ${process.env.USERPROFILE || '(not set)'}`);
|
|
102
|
+
this.log(` PWD: ${process.env.PWD || '(not set)'}`);
|
|
103
|
+
this.log(` OLDPWD: ${process.env.OLDPWD || '(not set)'}`);
|
|
104
|
+
// Log ALL env vars that might contain workspace info
|
|
105
|
+
Object.keys(process.env).filter(k => k.toLowerCase().includes('workspace') || k.toLowerCase().includes('project') || k.toLowerCase().includes('kiro')).forEach(k => {
|
|
106
|
+
this.log(` ${k}: ${process.env[k]}`);
|
|
107
|
+
});
|
|
67
108
|
// Priority 1: Check for IDE-provided workspace environment variables
|
|
68
109
|
const workspaceHints = [
|
|
69
110
|
process.env.WORKSPACE_FOLDER_PATHS?.split(':')[0], // Cursor provides this (colon-separated for multi-root)
|
|
@@ -135,6 +176,7 @@ class FraimLocalMCPServer {
|
|
|
135
176
|
*/
|
|
136
177
|
detectMachineInfo() {
|
|
137
178
|
if (this.machineInfo) {
|
|
179
|
+
this.log(`🔄 Returning cached machine info: ${JSON.stringify(this.machineInfo)}`);
|
|
138
180
|
return this.machineInfo;
|
|
139
181
|
}
|
|
140
182
|
try {
|
|
@@ -144,7 +186,7 @@ class FraimLocalMCPServer {
|
|
|
144
186
|
memory: (0, os_1.totalmem)(),
|
|
145
187
|
cpus: (0, os_1.cpus)().length
|
|
146
188
|
};
|
|
147
|
-
this.log(`✅ Detected machine info: ${
|
|
189
|
+
this.log(`✅ Detected machine info: ${JSON.stringify(this.machineInfo)}`);
|
|
148
190
|
return this.machineInfo;
|
|
149
191
|
}
|
|
150
192
|
catch (error) {
|
|
@@ -475,30 +517,34 @@ class FraimLocalMCPServer {
|
|
|
475
517
|
* Proxy request to remote FRAIM server
|
|
476
518
|
*/
|
|
477
519
|
async proxyToRemote(request) {
|
|
520
|
+
const requestId = (0, crypto_1.randomUUID)();
|
|
478
521
|
try {
|
|
479
522
|
// Special handling for fraim_connect - automatically inject machine and repo info
|
|
480
523
|
if (request.method === 'tools/call' && request.params?.name === 'fraim_connect') {
|
|
481
|
-
this.log(
|
|
524
|
+
this.log(`[req:${requestId}] Intercepting fraim_connect to inject machine/repo info`);
|
|
482
525
|
const args = request.params.arguments || {};
|
|
483
526
|
// REQUIRED: Auto-detect and inject machine info
|
|
484
527
|
const detectedMachine = this.detectMachineInfo();
|
|
485
528
|
args.machine = {
|
|
486
|
-
...
|
|
487
|
-
...
|
|
529
|
+
...args.machine, // Agent values as fallback
|
|
530
|
+
...detectedMachine // Detected values override (always win)
|
|
488
531
|
};
|
|
489
|
-
this.log(
|
|
532
|
+
this.log(`[req:${requestId}] Auto-detected and injected machine info: ${JSON.stringify(args.machine)}`);
|
|
533
|
+
if (!args.machine.memory || !args.machine.cpus) {
|
|
534
|
+
this.logError(`[req:${requestId}] WARNING: Machine info missing memory or cpus! Detected: ${JSON.stringify(detectedMachine)}, Final: ${JSON.stringify(args.machine)}`);
|
|
535
|
+
}
|
|
490
536
|
// REQUIRED: Auto-detect and inject repo info
|
|
491
537
|
const detectedRepo = this.detectRepoInfo();
|
|
492
538
|
if (detectedRepo) {
|
|
493
539
|
args.repo = {
|
|
494
|
-
...
|
|
495
|
-
...
|
|
540
|
+
...args.repo, // Agent values as fallback
|
|
541
|
+
...detectedRepo // Detected values override (always win)
|
|
496
542
|
};
|
|
497
|
-
this.log(
|
|
543
|
+
this.log(`[req:${requestId}] Auto-detected and injected repo info: ${args.repo.owner}/${args.repo.name}`);
|
|
498
544
|
}
|
|
499
545
|
else {
|
|
500
546
|
// If detection fails completely, return error instead of sending garbage
|
|
501
|
-
this.logError(
|
|
547
|
+
this.logError(`[req:${requestId}] Could not detect repo info and no config available`);
|
|
502
548
|
return {
|
|
503
549
|
jsonrpc: '2.0',
|
|
504
550
|
id: request.id,
|
|
@@ -511,23 +557,53 @@ class FraimLocalMCPServer {
|
|
|
511
557
|
// Update the request with injected info
|
|
512
558
|
request.params.arguments = args;
|
|
513
559
|
}
|
|
560
|
+
this.log(`[req:${requestId}] Proxying ${request.method} to ${this.remoteUrl}/mcp`);
|
|
514
561
|
const response = await axios_1.default.post(`${this.remoteUrl}/mcp`, request, {
|
|
515
562
|
headers: {
|
|
516
563
|
'Content-Type': 'application/json',
|
|
517
|
-
'x-api-key': this.apiKey
|
|
564
|
+
'x-api-key': this.apiKey,
|
|
565
|
+
'x-fraim-request-id': requestId,
|
|
566
|
+
'x-fraim-local-version': this.localVersion
|
|
518
567
|
},
|
|
519
568
|
timeout: 30000
|
|
520
569
|
});
|
|
521
570
|
return response.data;
|
|
522
571
|
}
|
|
523
572
|
catch (error) {
|
|
524
|
-
|
|
573
|
+
const status = error?.response?.status;
|
|
574
|
+
const remoteData = error?.response?.data;
|
|
575
|
+
this.logError(`[req:${requestId}] Remote request failed (${status || 'no-status'}): ${error.message}`);
|
|
576
|
+
if (remoteData && typeof remoteData === 'object') {
|
|
577
|
+
const forwarded = {
|
|
578
|
+
jsonrpc: typeof remoteData.jsonrpc === 'string' ? remoteData.jsonrpc : '2.0',
|
|
579
|
+
id: remoteData.id ?? request.id,
|
|
580
|
+
error: remoteData.error ?? {
|
|
581
|
+
code: -32603,
|
|
582
|
+
message: `Remote server error (${status || 'unknown status'}): ${error.message}`
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
if (forwarded.error && typeof forwarded.error === 'object') {
|
|
586
|
+
const existingData = forwarded.error.data;
|
|
587
|
+
forwarded.error.data = {
|
|
588
|
+
...(existingData && typeof existingData === 'object' ? existingData : {}),
|
|
589
|
+
fraimRequestId: requestId,
|
|
590
|
+
remoteStatus: status ?? null,
|
|
591
|
+
localMcpVersion: this.localVersion
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
return forwarded;
|
|
595
|
+
}
|
|
525
596
|
return {
|
|
526
597
|
jsonrpc: '2.0',
|
|
527
598
|
id: request.id,
|
|
528
599
|
error: {
|
|
529
600
|
code: -32603,
|
|
530
|
-
message: `Remote server error: ${error.message}
|
|
601
|
+
message: `Remote server error: ${error.message}`,
|
|
602
|
+
data: {
|
|
603
|
+
fraimRequestId: requestId,
|
|
604
|
+
remoteStatus: status ?? null,
|
|
605
|
+
localMcpVersion: this.localVersion
|
|
606
|
+
}
|
|
531
607
|
}
|
|
532
608
|
};
|
|
533
609
|
}
|
|
@@ -575,7 +651,7 @@ class FraimLocalMCPServer {
|
|
|
575
651
|
// Proxy initialize to remote server first
|
|
576
652
|
const response = await this.proxyToRemote(request);
|
|
577
653
|
const processedResponse = this.processResponse(response);
|
|
578
|
-
// After successful initialization, load config
|
|
654
|
+
// After successful initialization, load config
|
|
579
655
|
if (!processedResponse.error) {
|
|
580
656
|
// For now, don't request roots - just use env var + upward search
|
|
581
657
|
// TODO: Implement roots/list properly after initialization is complete
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Remote Registry Sync
|
|
4
|
+
*
|
|
5
|
+
* Fetches workflows and scripts from the remote FRAIM server
|
|
6
|
+
* instead of bundling them in the npm package.
|
|
7
|
+
*
|
|
8
|
+
* Issue: #83 - Minimize client package by fetching registry remotely
|
|
9
|
+
*/
|
|
10
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.syncFromRemote = syncFromRemote;
|
|
15
|
+
const axios_1 = __importDefault(require("axios"));
|
|
16
|
+
const fs_1 = require("fs");
|
|
17
|
+
const path_1 = require("path");
|
|
18
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
19
|
+
const script_sync_utils_1 = require("./script-sync-utils");
|
|
20
|
+
/**
|
|
21
|
+
* Sync workflows and scripts from remote FRAIM server
|
|
22
|
+
*/
|
|
23
|
+
async function syncFromRemote(options) {
|
|
24
|
+
const remoteUrl = options.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
25
|
+
const apiKey = options.apiKey || process.env.FRAIM_API_KEY || '';
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
workflowsSynced: 0,
|
|
30
|
+
scriptsSynced: 0,
|
|
31
|
+
error: 'FRAIM_API_KEY not set'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
console.log(chalk_1.default.blue('🔄 Syncing from remote FRAIM server...'));
|
|
36
|
+
console.log(chalk_1.default.gray(` Remote: ${remoteUrl}`));
|
|
37
|
+
// Fetch registry files from remote server
|
|
38
|
+
const response = await axios_1.default.get(`${remoteUrl}/api/registry/sync`, {
|
|
39
|
+
headers: {
|
|
40
|
+
'x-api-key': apiKey
|
|
41
|
+
},
|
|
42
|
+
timeout: 30000
|
|
43
|
+
});
|
|
44
|
+
const files = response.data.files || [];
|
|
45
|
+
if (!files || files.length === 0) {
|
|
46
|
+
console.log(chalk_1.default.yellow('⚠️ No files received from remote server'));
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
workflowsSynced: 0,
|
|
50
|
+
scriptsSynced: 0,
|
|
51
|
+
error: 'No files received'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Sync workflows
|
|
55
|
+
const workflowFiles = files.filter(f => f.type === 'workflow');
|
|
56
|
+
const workflowsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'workflows');
|
|
57
|
+
if (!(0, fs_1.existsSync)(workflowsDir)) {
|
|
58
|
+
(0, fs_1.mkdirSync)(workflowsDir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
// Clean existing workflows
|
|
61
|
+
cleanDirectory(workflowsDir);
|
|
62
|
+
// Write workflow files
|
|
63
|
+
for (const file of workflowFiles) {
|
|
64
|
+
const filePath = (0, path_1.join)(workflowsDir, file.path);
|
|
65
|
+
const fileDir = (0, path_1.dirname)(filePath);
|
|
66
|
+
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
67
|
+
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
70
|
+
console.log(chalk_1.default.gray(` + ${file.path}`));
|
|
71
|
+
}
|
|
72
|
+
// Sync scripts to user directory
|
|
73
|
+
const scriptFiles = files.filter(f => f.type === 'script');
|
|
74
|
+
const userDir = (0, script_sync_utils_1.getUserFraimDir)();
|
|
75
|
+
const scriptsDir = (0, path_1.join)(userDir, 'scripts');
|
|
76
|
+
if (!(0, fs_1.existsSync)(scriptsDir)) {
|
|
77
|
+
(0, fs_1.mkdirSync)(scriptsDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
// Clean existing scripts
|
|
80
|
+
cleanDirectory(scriptsDir);
|
|
81
|
+
// Write script files
|
|
82
|
+
for (const file of scriptFiles) {
|
|
83
|
+
const filePath = (0, path_1.join)(scriptsDir, file.path);
|
|
84
|
+
const fileDir = (0, path_1.dirname)(filePath);
|
|
85
|
+
if (!(0, fs_1.existsSync)(fileDir)) {
|
|
86
|
+
(0, fs_1.mkdirSync)(fileDir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
(0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
|
|
89
|
+
console.log(chalk_1.default.gray(` + ${file.path}`));
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk_1.default.green(`\n✅ Synced ${workflowFiles.length} workflows and ${scriptFiles.length} scripts from remote`));
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
workflowsSynced: workflowFiles.length,
|
|
95
|
+
scriptsSynced: scriptFiles.length
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(chalk_1.default.red(`❌ Remote sync failed: ${error.message}`));
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
workflowsSynced: 0,
|
|
103
|
+
scriptsSynced: 0,
|
|
104
|
+
error: error.message
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Clean directory contents (but keep the directory itself)
|
|
110
|
+
*/
|
|
111
|
+
function cleanDirectory(dirPath) {
|
|
112
|
+
if (!(0, fs_1.existsSync)(dirPath))
|
|
113
|
+
return;
|
|
114
|
+
const entries = (0, fs_1.readdirSync)(dirPath, { withFileTypes: true });
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const fullPath = (0, path_1.join)(dirPath, entry.name);
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
cleanDirectory(fullPath);
|
|
119
|
+
try {
|
|
120
|
+
(0, fs_1.rmdirSync)(fullPath);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
// Directory not empty, skip
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
(0, fs_1.unlinkSync)(fullPath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.65",
|
|
4
4
|
"description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "tsx --watch src/fraim-mcp-server.ts > server.log 2>&1",
|
|
12
|
+
"dev:prod": "npm run build && node dist/src/fraim-mcp-server.js > server.log 2>&1",
|
|
12
13
|
"build": "tsc && node scripts/copy-ai-manager-rules.js && npm run build:stubs && npm run validate:registry",
|
|
13
14
|
"build:stubs": "tsx scripts/build-stub-registry.ts",
|
|
14
15
|
"test": "node scripts/test-with-server.js",
|
|
15
16
|
"start:fraim": "tsx src/fraim-mcp-server.ts",
|
|
16
17
|
"dev:fraim": "tsx --watch src/fraim-mcp-server.ts",
|
|
17
18
|
"serve:website": "node fraim-pro/serve.js",
|
|
18
|
-
"watch:fraimlogs": "tsx scripts/watch-fraim-logs.ts",
|
|
19
|
+
"watch:fraimlogs": "tsx scripts/watch-fraim-logs.ts > prodlogs.log 2>&1",
|
|
19
20
|
"manage-keys": "tsx scripts/fraim/manage-keys.ts",
|
|
20
21
|
"view-signups": "tsx scripts/view-signups.ts",
|
|
21
22
|
"fraim:init": "npm run build && node index.js init",
|
|
@@ -74,8 +75,6 @@
|
|
|
74
75
|
"dist/src/cli/",
|
|
75
76
|
"dist/src/fraim/",
|
|
76
77
|
"dist/src/utils/",
|
|
77
|
-
"registry/stubs/",
|
|
78
|
-
"registry/scripts/",
|
|
79
78
|
"bin/fraim.js",
|
|
80
79
|
"bin/fraim-mcp.js",
|
|
81
80
|
"index.js",
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Enforcement Utilities for FRAIM
|
|
4
|
-
*
|
|
5
|
-
* Provides deterministic enforcement of:
|
|
6
|
-
* - Working style (PR vs Conversation)
|
|
7
|
-
* - Feedback tracking requirements
|
|
8
|
-
* - User config initialization
|
|
9
|
-
*/
|
|
10
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.initializeUserConfig = initializeUserConfig;
|
|
12
|
-
exports.loadUserConfig = loadUserConfig;
|
|
13
|
-
exports.getEnforcementContext = getEnforcementContext;
|
|
14
|
-
exports.isCommitAllowed = isCommitAllowed;
|
|
15
|
-
exports.checkUnaddressedFeedback = checkUnaddressedFeedback;
|
|
16
|
-
exports.generateCommitInstructions = generateCommitInstructions;
|
|
17
|
-
exports.generateFeedbackInstructions = generateFeedbackInstructions;
|
|
18
|
-
const fs_1 = require("fs");
|
|
19
|
-
const path_1 = require("path");
|
|
20
|
-
const child_process_1 = require("child_process");
|
|
21
|
-
const script_sync_utils_js_1 = require("./script-sync-utils.js");
|
|
22
|
-
const git_utils_js_1 = require("./git-utils.js");
|
|
23
|
-
/**
|
|
24
|
-
* Initialize user config with default values if it doesn't exist
|
|
25
|
-
*/
|
|
26
|
-
function initializeUserConfig() {
|
|
27
|
-
try {
|
|
28
|
-
const userConfigPath = (0, path_1.join)((0, script_sync_utils_js_1.getUserFraimDir)(), 'config.json');
|
|
29
|
-
if (!(0, fs_1.existsSync)(userConfigPath)) {
|
|
30
|
-
const defaultConfig = {
|
|
31
|
-
workingStyle: 'PR' // Default to PR mode
|
|
32
|
-
};
|
|
33
|
-
// Ensure directory exists
|
|
34
|
-
const userFraimDir = (0, script_sync_utils_js_1.getUserFraimDir)();
|
|
35
|
-
if (!(0, fs_1.existsSync)(userFraimDir)) {
|
|
36
|
-
const { mkdirSync } = require('fs');
|
|
37
|
-
mkdirSync(userFraimDir, { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
(0, fs_1.writeFileSync)(userConfigPath, JSON.stringify(defaultConfig, null, 2));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
console.warn('⚠️ Failed to initialize user config:', error);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Load user config from ~/.fraim/config.json
|
|
48
|
-
*/
|
|
49
|
-
function loadUserConfig() {
|
|
50
|
-
try {
|
|
51
|
-
const userConfigPath = (0, path_1.join)((0, script_sync_utils_js_1.getUserFraimDir)(), 'config.json');
|
|
52
|
-
if ((0, fs_1.existsSync)(userConfigPath)) {
|
|
53
|
-
const content = (0, fs_1.readFileSync)(userConfigPath, 'utf8');
|
|
54
|
-
return JSON.parse(content);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
console.warn('⚠️ Failed to load user config:', error);
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Get enforcement context (working style, branch info)
|
|
64
|
-
*/
|
|
65
|
-
function getEnforcementContext(issueNumber, projectRoot) {
|
|
66
|
-
const userConfig = loadUserConfig();
|
|
67
|
-
const workingStyle = userConfig?.workingStyle || 'PR'; // Default to PR
|
|
68
|
-
let currentBranch = null;
|
|
69
|
-
let defaultBranch = null;
|
|
70
|
-
try {
|
|
71
|
-
currentBranch = (0, git_utils_js_1.getCurrentGitBranch)();
|
|
72
|
-
// Try to get default branch
|
|
73
|
-
try {
|
|
74
|
-
const remoteHead = (0, child_process_1.execSync)('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
75
|
-
timeout: 2000,
|
|
76
|
-
stdio: 'pipe'
|
|
77
|
-
}).toString().trim();
|
|
78
|
-
const match = remoteHead.match(/refs\/remotes\/origin\/(.+)$/);
|
|
79
|
-
if (match) {
|
|
80
|
-
defaultBranch = match[1];
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch (e) {
|
|
84
|
-
defaultBranch = 'main'; // Fallback
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
// Not a git repo or git command failed
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
workingStyle,
|
|
92
|
-
currentBranch,
|
|
93
|
-
defaultBranch,
|
|
94
|
-
issueNumber,
|
|
95
|
-
projectRoot
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Check if agent commit is allowed based on working style
|
|
100
|
-
* Note: In Conversation mode, agent NEVER commits (user commits manually)
|
|
101
|
-
*/
|
|
102
|
-
function isCommitAllowed(context) {
|
|
103
|
-
// If Conversation style, agent NEVER commits (user commits manually)
|
|
104
|
-
if (context.workingStyle === 'Conversation') {
|
|
105
|
-
return {
|
|
106
|
-
allowed: false,
|
|
107
|
-
reason: 'Working style is "Conversation" - agent does not commit. User commits manually.'
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
// PR style: commits only allowed on feature branches, not main/master
|
|
111
|
-
if (!context.currentBranch) {
|
|
112
|
-
return {
|
|
113
|
-
allowed: false,
|
|
114
|
-
reason: 'Not in a git repository or unable to determine current branch'
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
const defaultBranch = context.defaultBranch || 'main';
|
|
118
|
-
const isDefaultBranch = context.currentBranch === defaultBranch ||
|
|
119
|
-
context.currentBranch === 'master' ||
|
|
120
|
-
context.currentBranch === 'main';
|
|
121
|
-
if (isDefaultBranch) {
|
|
122
|
-
return {
|
|
123
|
-
allowed: false,
|
|
124
|
-
reason: `Working style is "PR" but you are on ${context.currentBranch} branch. Create a feature branch first.`
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return { allowed: true };
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Check for unaddressed feedback files
|
|
131
|
-
*/
|
|
132
|
-
function checkUnaddressedFeedback(issueNumber, projectRoot) {
|
|
133
|
-
const feedbackFiles = [];
|
|
134
|
-
if (!projectRoot) {
|
|
135
|
-
return { hasUnaddressed: false, feedbackFiles: [] };
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
const evidenceDir = (0, path_1.join)(projectRoot, 'docs', 'evidence');
|
|
139
|
-
if (!(0, fs_1.existsSync)(evidenceDir)) {
|
|
140
|
-
return { hasUnaddressed: false, feedbackFiles: [] };
|
|
141
|
-
}
|
|
142
|
-
const files = (0, fs_1.readdirSync)(evidenceDir);
|
|
143
|
-
const issueFeedbackPattern = new RegExp(`^${issueNumber}-.*-feedback\\.md$`);
|
|
144
|
-
for (const file of files) {
|
|
145
|
-
if (issueFeedbackPattern.test(file)) {
|
|
146
|
-
const filePath = (0, path_1.join)(evidenceDir, file);
|
|
147
|
-
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
148
|
-
// Check if feedback is addressed (look for "Status: Addressed" or similar markers)
|
|
149
|
-
// Simple heuristic: if file contains "Status: Addressed" or "✅ Addressed", consider it addressed
|
|
150
|
-
// If it contains "Status: Unaddressed", it's definitely unaddressed
|
|
151
|
-
const hasUnaddressed = /status:\s*unaddressed/i.test(content);
|
|
152
|
-
const isAddressed = /status:\s*addressed|✅\s*addressed/i.test(content);
|
|
153
|
-
// If explicitly unaddressed, or if no addressed markers found at all, consider unaddressed
|
|
154
|
-
// Note: If file has both, "Unaddressed" takes precedence
|
|
155
|
-
if (hasUnaddressed || (!hasUnaddressed && !isAddressed)) {
|
|
156
|
-
feedbackFiles.push(file);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
console.warn('⚠️ Failed to check feedback files:', error);
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
hasUnaddressed: feedbackFiles.length > 0,
|
|
166
|
-
feedbackFiles
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Generate commit behavior instructions based on working style
|
|
171
|
-
*/
|
|
172
|
-
function generateCommitInstructions(context) {
|
|
173
|
-
// Check commit permission first (handles both Conversation and PR modes)
|
|
174
|
-
const commitCheck = isCommitAllowed(context);
|
|
175
|
-
if (context.workingStyle === 'Conversation') {
|
|
176
|
-
// Conversation mode: Agent never commits, user commits manually
|
|
177
|
-
return `\n\n**⚠️ Working Style: Conversation**\n\n` +
|
|
178
|
-
`You are in "Conversation" mode. **DO NOT commit changes**. The user will commit manually.\n\n` +
|
|
179
|
-
`**Branch Strategy**:\n` +
|
|
180
|
-
`- Work directly on ${context.defaultBranch || 'main/master'} branch\n` +
|
|
181
|
-
`- **Skip branch creation**: No need to run prep-issue.sh or create feature branches\n` +
|
|
182
|
-
`- **Skip PR creation**: Work is committed directly to ${context.defaultBranch || 'main/master'}\n\n` +
|
|
183
|
-
`**Commit Behavior**:\n` +
|
|
184
|
-
`- Make changes as requested\n` +
|
|
185
|
-
`- Do not use git commit tools\n` +
|
|
186
|
-
`- Show diff to user: "I've made these changes: [show diff]"\n` +
|
|
187
|
-
`- Suggest commit message if helpful\n` +
|
|
188
|
-
`- Wait for user to review and commit manually\n\n` +
|
|
189
|
-
`**Outcome Statements**: When documenting completion, use:\n` +
|
|
190
|
-
`- "All work ready for user review and commit" (instead of "committed to feature branch")\n` +
|
|
191
|
-
`- "Changes prepared on ${context.defaultBranch || 'main/master'} branch"\n`;
|
|
192
|
-
}
|
|
193
|
-
// PR style: Check if commits are allowed
|
|
194
|
-
if (!commitCheck.allowed) {
|
|
195
|
-
// Blocked: either on main/master or not in git repo
|
|
196
|
-
return `\n\n**⚠️ Working Style: PR**\n\n` +
|
|
197
|
-
`**BLOCKED**: ${commitCheck.reason}\n\n` +
|
|
198
|
-
`**Action Required**:\n` +
|
|
199
|
-
`1. Run prep-issue script: \`~/.fraim/scripts/prep-issue.sh ${context.issueNumber || 'ISSUE_NUMBER'}\`\n` +
|
|
200
|
-
` OR create feature branch manually: \`git checkout -b feature/${context.issueNumber || 'issue'}-description\`\n` +
|
|
201
|
-
`2. Push branch: \`git push -u origin feature/${context.issueNumber || 'issue'}-description\`\n` +
|
|
202
|
-
`3. Then proceed with your work\n` +
|
|
203
|
-
`**DO NOT commit to ${context.currentBranch || 'main/master'} branch.**\n`;
|
|
204
|
-
}
|
|
205
|
-
// PR mode on feature branch: Commits allowed
|
|
206
|
-
return `\n\n**✅ Working Style: PR**\n\n` +
|
|
207
|
-
`You are in "PR" mode. Commits are allowed on feature branch: **${context.currentBranch}**\n\n` +
|
|
208
|
-
`**Branch Setup**:\n` +
|
|
209
|
-
`- If branch doesn't exist, run: \`~/.fraim/scripts/prep-issue.sh ${context.issueNumber || 'ISSUE_NUMBER'}\`\n` +
|
|
210
|
-
`- Confirm you're on feature branch: \`git branch --show-current\`\n\n` +
|
|
211
|
-
`**Commit Behavior**:\n` +
|
|
212
|
-
`- Create commits on this branch as you complete work\n` +
|
|
213
|
-
`- Commit message format: \`{workflow_type}({issue_number}): {description}\`\n` +
|
|
214
|
-
`- Push branch: \`git push origin ${context.currentBranch}\`\n\n` +
|
|
215
|
-
`**PR Management**:\n` +
|
|
216
|
-
`- Create PR when ready for review\n` +
|
|
217
|
-
`- **Verify PR exists** before marking work complete\n` +
|
|
218
|
-
`- Link PR to issue and add evidence document link\n\n` +
|
|
219
|
-
`**Outcome Statements**: When documenting completion, use:\n` +
|
|
220
|
-
`- "All work committed and pushed to feature branch: ${context.currentBranch}"\n`;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Generate feedback check instructions
|
|
224
|
-
*/
|
|
225
|
-
function generateFeedbackInstructions(issueNumber, projectRoot) {
|
|
226
|
-
const feedbackCheck = checkUnaddressedFeedback(issueNumber, projectRoot);
|
|
227
|
-
if (!feedbackCheck.hasUnaddressed) {
|
|
228
|
-
return '';
|
|
229
|
-
}
|
|
230
|
-
return `\n\n**⚠️ Unaddressed Feedback Detected**\n\n` +
|
|
231
|
-
`The following feedback files need to be addressed before proceeding:\n` +
|
|
232
|
-
feedbackCheck.feedbackFiles.map(f => `- \`docs/evidence/${f}\``).join('\n') +
|
|
233
|
-
`\n\n**Action Required**:\n` +
|
|
234
|
-
`1. Review each feedback file\n` +
|
|
235
|
-
`2. Address all feedback items\n` +
|
|
236
|
-
`3. Mark feedback as addressed in the file\n` +
|
|
237
|
-
`4. Then continue with this phase\n` +
|
|
238
|
-
`\n**DO NOT proceed until all feedback is addressed.**\n`;
|
|
239
|
-
}
|