fraim-framework 2.0.64 → 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 +23 -3
- package/dist/src/utils/remote-sync.js +130 -0
- package/package.json +2 -3
- 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/azure/cost-optimization.md +0 -11
- 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
|
@@ -44,11 +44,21 @@ class FraimLocalMCPServer {
|
|
|
44
44
|
this.log(`📡 Remote server: ${this.remoteUrl}`);
|
|
45
45
|
this.log(`🔑 API key: ${this.apiKey.substring(0, 10)}...`);
|
|
46
46
|
this.log(`Local MCP version: ${this.localVersion}`);
|
|
47
|
+
this.log(`🔍 DEBUG BUILD: Machine detection v2 active`);
|
|
47
48
|
}
|
|
48
49
|
log(message) {
|
|
49
50
|
// Log to stderr (stdout is reserved for MCP protocol)
|
|
50
51
|
const key = this.apiKey || 'MISSING_API_KEY';
|
|
51
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
|
+
}
|
|
52
62
|
}
|
|
53
63
|
logError(message) {
|
|
54
64
|
const key = this.apiKey || 'MISSING_API_KEY';
|
|
@@ -89,6 +99,12 @@ class FraimLocalMCPServer {
|
|
|
89
99
|
this.log(` INIT_CWD: ${process.env.INIT_CWD || '(not set)'}`);
|
|
90
100
|
this.log(` HOME: ${process.env.HOME || '(not set)'}`);
|
|
91
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
|
+
});
|
|
92
108
|
// Priority 1: Check for IDE-provided workspace environment variables
|
|
93
109
|
const workspaceHints = [
|
|
94
110
|
process.env.WORKSPACE_FOLDER_PATHS?.split(':')[0], // Cursor provides this (colon-separated for multi-root)
|
|
@@ -160,6 +176,7 @@ class FraimLocalMCPServer {
|
|
|
160
176
|
*/
|
|
161
177
|
detectMachineInfo() {
|
|
162
178
|
if (this.machineInfo) {
|
|
179
|
+
this.log(`🔄 Returning cached machine info: ${JSON.stringify(this.machineInfo)}`);
|
|
163
180
|
return this.machineInfo;
|
|
164
181
|
}
|
|
165
182
|
try {
|
|
@@ -169,7 +186,7 @@ class FraimLocalMCPServer {
|
|
|
169
186
|
memory: (0, os_1.totalmem)(),
|
|
170
187
|
cpus: (0, os_1.cpus)().length
|
|
171
188
|
};
|
|
172
|
-
this.log(`✅ Detected machine info: ${
|
|
189
|
+
this.log(`✅ Detected machine info: ${JSON.stringify(this.machineInfo)}`);
|
|
173
190
|
return this.machineInfo;
|
|
174
191
|
}
|
|
175
192
|
catch (error) {
|
|
@@ -512,7 +529,10 @@ class FraimLocalMCPServer {
|
|
|
512
529
|
...args.machine, // Agent values as fallback
|
|
513
530
|
...detectedMachine // Detected values override (always win)
|
|
514
531
|
};
|
|
515
|
-
this.log(`[req:${requestId}] Auto-detected and injected machine info: ${
|
|
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
|
+
}
|
|
516
536
|
// REQUIRED: Auto-detect and inject repo info
|
|
517
537
|
const detectedRepo = this.detectRepoInfo();
|
|
518
538
|
if (detectedRepo) {
|
|
@@ -631,7 +651,7 @@ class FraimLocalMCPServer {
|
|
|
631
651
|
// Proxy initialize to remote server first
|
|
632
652
|
const response = await this.proxyToRemote(request);
|
|
633
653
|
const processedResponse = this.processResponse(response);
|
|
634
|
-
// After successful initialization, load config
|
|
654
|
+
// After successful initialization, load config
|
|
635
655
|
if (!processedResponse.error) {
|
|
636
656
|
// For now, don't request roots - just use env var + upward search
|
|
637
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,6 +9,7 @@
|
|
|
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",
|
|
@@ -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
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateAllWorkflows = validateAllWorkflows;
|
|
4
|
-
const fs_1 = require("fs");
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
const workflow_parser_js_1 = require("./workflow-parser.js");
|
|
7
|
-
/**
|
|
8
|
-
* Validate all workflows in the registry
|
|
9
|
-
*/
|
|
10
|
-
function validateAllWorkflows(registryPath) {
|
|
11
|
-
const errors = [];
|
|
12
|
-
const workflowFiles = [];
|
|
13
|
-
function findWorkflows(dir) {
|
|
14
|
-
if (!(0, fs_1.existsSync)(dir))
|
|
15
|
-
return;
|
|
16
|
-
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
17
|
-
for (const entry of entries) {
|
|
18
|
-
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
19
|
-
if (entry.isDirectory()) {
|
|
20
|
-
findWorkflows(fullPath);
|
|
21
|
-
}
|
|
22
|
-
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
23
|
-
const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
24
|
-
if (content.includes('"initialPhase"')) {
|
|
25
|
-
workflowFiles.push(fullPath);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
findWorkflows(registryPath);
|
|
31
|
-
console.log(`🔍 Validating ${workflowFiles.length} workflows...`);
|
|
32
|
-
for (const filePath of workflowFiles) {
|
|
33
|
-
const relativePath = filePath.replace(registryPath, '');
|
|
34
|
-
try {
|
|
35
|
-
const wf = workflow_parser_js_1.WorkflowParser.parse(filePath);
|
|
36
|
-
if (!wf) {
|
|
37
|
-
errors.push(`❌ ${relativePath}: Failed to parse. Ensure JSON metadata is present and valid.`);
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
const { metadata, phases } = wf;
|
|
41
|
-
// 1. Initial Phase check
|
|
42
|
-
if (!phases.has(metadata.initialPhase)) {
|
|
43
|
-
errors.push(`❌ ${relativePath}: initialPhase "${metadata.initialPhase}" does not exist in phases.`);
|
|
44
|
-
}
|
|
45
|
-
// 2. Validate Phase Flow
|
|
46
|
-
for (const [phaseId, flow] of Object.entries(metadata.phases)) {
|
|
47
|
-
// Check if phase exists in markdown
|
|
48
|
-
if (!phases.has(phaseId)) {
|
|
49
|
-
errors.push(`❌ ${relativePath}: Phase "${phaseId}" defined in metadata but missing in markdown.`);
|
|
50
|
-
}
|
|
51
|
-
// Check Success transition
|
|
52
|
-
if (flow.onSuccess && flow.onSuccess !== 'null') {
|
|
53
|
-
if (typeof flow.onSuccess === 'string') {
|
|
54
|
-
if (!phases.has(flow.onSuccess)) {
|
|
55
|
-
errors.push(`❌ ${relativePath}: Phase "${phaseId}" transition onSuccess -> "${flow.onSuccess}" target not found.`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
// Validate each branch of conditional success
|
|
60
|
-
for (const [branch, target] of Object.entries(flow.onSuccess)) {
|
|
61
|
-
if (target && target !== 'null' && !phases.has(target)) {
|
|
62
|
-
errors.push(`❌ ${relativePath}: Phase "${phaseId}" conditional transition onSuccess[${branch}] -> "${target}" target not found.`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// Check Failure transition
|
|
68
|
-
if (flow.onFailure && !phases.has(flow.onFailure)) {
|
|
69
|
-
errors.push(`❌ ${relativePath}: Phase "${phaseId}" transition onFailure -> "${flow.onFailure}" target not found.`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// 3. Check for orphaned phases in markdown
|
|
73
|
-
for (const phaseId of phases.keys()) {
|
|
74
|
-
if (!metadata.phases[phaseId]) {
|
|
75
|
-
errors.push(`⚠️ ${relativePath}: Phase "${phaseId}" exists in markdown but is NOT defined in JSON metadata.`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (e) {
|
|
80
|
-
errors.push(`❌ ${relativePath}: Unexpected error: ${e.message}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
valid: errors.filter(e => e.startsWith('❌')).length === 0,
|
|
85
|
-
errors
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
// Run if called directly
|
|
89
|
-
if (process.argv[1]?.includes('validate-workflows')) {
|
|
90
|
-
const registryPath = process.argv[2] || (0, path_1.join)(process.cwd(), 'registry');
|
|
91
|
-
const result = validateAllWorkflows(registryPath);
|
|
92
|
-
result.errors.forEach(err => console.error(err));
|
|
93
|
-
if (result.valid) {
|
|
94
|
-
console.log('✅ All workflows validated successfully.');
|
|
95
|
-
process.exit(0);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
console.error('❌ Workflow validation failed.');
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
}
|