fraim-framework 2.0.72 → 2.0.73
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.
|
@@ -10,7 +10,9 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
11
|
exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
12
12
|
.description('List all FRAIM registry paths that can be overridden')
|
|
13
|
-
.
|
|
13
|
+
.option('--job-category <category>', 'Filter by workflow category (e.g., product-building, customer-development, marketing)')
|
|
14
|
+
.option('--rules', 'Show all overridable rules')
|
|
15
|
+
.action(async (options) => {
|
|
14
16
|
const projectRoot = process.cwd();
|
|
15
17
|
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
16
18
|
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
@@ -20,35 +22,24 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
20
22
|
console.log(chalk_1.default.red('❌ .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
|
|
21
23
|
process.exit(1);
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
'workflows/product-building/prep-issue.md'
|
|
35
|
-
]
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'Templates',
|
|
39
|
-
paths: [
|
|
40
|
-
'templates/specs/FEATURESPEC-TEMPLATE.md',
|
|
41
|
-
'templates/design/DESIGN-TEMPLATE.md'
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: 'Rules',
|
|
46
|
-
paths: [
|
|
47
|
-
'rules/coding-standards.md',
|
|
48
|
-
'rules/communication.md'
|
|
49
|
-
]
|
|
25
|
+
// Determine registry location (try framework root first, then node_modules)
|
|
26
|
+
let registryRoot = null;
|
|
27
|
+
const frameworkRoot = path_1.default.join(__dirname, '..', '..', '..');
|
|
28
|
+
const frameworkRegistry = path_1.default.join(frameworkRoot, 'registry');
|
|
29
|
+
if (fs_1.default.existsSync(frameworkRegistry)) {
|
|
30
|
+
registryRoot = frameworkRegistry;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const nodeModulesRegistry = path_1.default.join(process.cwd(), 'node_modules', '@fraim', 'framework', 'registry');
|
|
34
|
+
if (fs_1.default.existsSync(nodeModulesRegistry)) {
|
|
35
|
+
registryRoot = nodeModulesRegistry;
|
|
50
36
|
}
|
|
51
|
-
|
|
37
|
+
}
|
|
38
|
+
if (!registryRoot) {
|
|
39
|
+
console.log(chalk_1.default.red('❌ Could not find FRAIM registry. Please ensure @fraim/framework is installed.'));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
console.log(chalk_1.default.blue('📋 Overridable FRAIM Registry Paths:\n'));
|
|
52
43
|
// Get list of existing overrides
|
|
53
44
|
const existingOverrides = new Set();
|
|
54
45
|
if (fs_1.default.existsSync(overridesDir)) {
|
|
@@ -66,10 +57,50 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
66
57
|
};
|
|
67
58
|
scanDir(overridesDir);
|
|
68
59
|
}
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
// Handle --rules flag
|
|
61
|
+
if (options.rules) {
|
|
62
|
+
const rulesDir = path_1.default.join(registryRoot, 'rules');
|
|
63
|
+
if (fs_1.default.existsSync(rulesDir)) {
|
|
64
|
+
console.log(chalk_1.default.bold.cyan('Rules:\n'));
|
|
65
|
+
const ruleFiles = fs_1.default.readdirSync(rulesDir)
|
|
66
|
+
.filter(f => f.endsWith('.md'))
|
|
67
|
+
.sort();
|
|
68
|
+
for (const file of ruleFiles) {
|
|
69
|
+
const filePath = `rules/${file}`;
|
|
70
|
+
const hasOverride = existingOverrides.has(filePath);
|
|
71
|
+
const status = hasOverride
|
|
72
|
+
? chalk_1.default.green('[OVERRIDDEN]')
|
|
73
|
+
: chalk_1.default.gray('[OVERRIDABLE]');
|
|
74
|
+
console.log(` ${status} ${filePath}`);
|
|
75
|
+
}
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
78
|
+
showTips();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Handle --job-category flag
|
|
82
|
+
if (options.jobCategory) {
|
|
83
|
+
const category = options.jobCategory;
|
|
84
|
+
const workflowsDir = path_1.default.join(registryRoot, 'workflows', category);
|
|
85
|
+
const templatesDir = path_1.default.join(registryRoot, 'templates', category);
|
|
86
|
+
if (!fs_1.default.existsSync(workflowsDir)) {
|
|
87
|
+
console.log(chalk_1.default.red(`❌ Category "${category}" not found.`));
|
|
88
|
+
console.log(chalk_1.default.gray('\nAvailable categories:'));
|
|
89
|
+
const categoriesDir = path_1.default.join(registryRoot, 'workflows');
|
|
90
|
+
const categories = fs_1.default.readdirSync(categoriesDir, { withFileTypes: true })
|
|
91
|
+
.filter(d => d.isDirectory())
|
|
92
|
+
.map(d => d.name)
|
|
93
|
+
.sort();
|
|
94
|
+
categories.forEach(c => console.log(chalk_1.default.gray(` - ${c}`)));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
// Show workflows for this category
|
|
98
|
+
console.log(chalk_1.default.bold.cyan(`Workflows (${category}):\n`));
|
|
99
|
+
const workflowFiles = fs_1.default.readdirSync(workflowsDir)
|
|
100
|
+
.filter(f => f.endsWith('.md'))
|
|
101
|
+
.sort();
|
|
102
|
+
for (const file of workflowFiles) {
|
|
103
|
+
const filePath = `workflows/${category}/${file}`;
|
|
73
104
|
const hasOverride = existingOverrides.has(filePath);
|
|
74
105
|
const status = hasOverride
|
|
75
106
|
? chalk_1.default.green('[OVERRIDDEN]')
|
|
@@ -77,21 +108,95 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
|
77
108
|
console.log(` ${status} ${filePath}`);
|
|
78
109
|
}
|
|
79
110
|
console.log('');
|
|
111
|
+
// Show templates for this category (if they exist)
|
|
112
|
+
if (fs_1.default.existsSync(templatesDir)) {
|
|
113
|
+
console.log(chalk_1.default.bold.cyan(`Templates (${category}):\n`));
|
|
114
|
+
const templateFiles = fs_1.default.readdirSync(templatesDir)
|
|
115
|
+
.filter(f => f.endsWith('.md') || f.endsWith('.html') || f.endsWith('.csv') || f.endsWith('.yml'))
|
|
116
|
+
.sort();
|
|
117
|
+
for (const file of templateFiles) {
|
|
118
|
+
const filePath = `templates/${category}/${file}`;
|
|
119
|
+
const hasOverride = existingOverrides.has(filePath);
|
|
120
|
+
const status = hasOverride
|
|
121
|
+
? chalk_1.default.green('[OVERRIDDEN]')
|
|
122
|
+
: chalk_1.default.gray('[OVERRIDABLE]');
|
|
123
|
+
console.log(` ${status} ${filePath}`);
|
|
124
|
+
}
|
|
125
|
+
console.log('');
|
|
126
|
+
}
|
|
127
|
+
showTips();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Default: Show available categories and existing overrides
|
|
131
|
+
console.log(chalk_1.default.bold.cyan('Available Workflow Categories:\n'));
|
|
132
|
+
const workflowsDir = path_1.default.join(registryRoot, 'workflows');
|
|
133
|
+
const categories = fs_1.default.readdirSync(workflowsDir, { withFileTypes: true })
|
|
134
|
+
.filter(d => d.isDirectory())
|
|
135
|
+
.map(d => d.name)
|
|
136
|
+
.sort();
|
|
137
|
+
// Group categories for better display
|
|
138
|
+
const columns = 3;
|
|
139
|
+
for (let i = 0; i < categories.length; i += columns) {
|
|
140
|
+
const row = categories.slice(i, i + columns);
|
|
141
|
+
const formatted = row.map(cat => chalk_1.default.gray(` • ${cat.padEnd(30)}`)).join('');
|
|
142
|
+
console.log(formatted);
|
|
80
143
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
144
|
+
console.log('');
|
|
145
|
+
// Show existing overrides
|
|
146
|
+
if (existingOverrides.size > 0) {
|
|
147
|
+
console.log(chalk_1.default.bold.cyan('Your Active Overrides:\n'));
|
|
148
|
+
// Group by type
|
|
149
|
+
const overridesByType = {
|
|
150
|
+
workflows: [],
|
|
151
|
+
templates: [],
|
|
152
|
+
rules: [],
|
|
153
|
+
other: []
|
|
154
|
+
};
|
|
155
|
+
for (const override of Array.from(existingOverrides).sort()) {
|
|
156
|
+
if (override.startsWith('workflows/')) {
|
|
157
|
+
overridesByType.workflows.push(override);
|
|
158
|
+
}
|
|
159
|
+
else if (override.startsWith('templates/')) {
|
|
160
|
+
overridesByType.templates.push(override);
|
|
161
|
+
}
|
|
162
|
+
else if (override.startsWith('rules/')) {
|
|
163
|
+
overridesByType.rules.push(override);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
overridesByType.other.push(override);
|
|
167
|
+
}
|
|
89
168
|
}
|
|
90
|
-
|
|
169
|
+
if (overridesByType.workflows.length > 0) {
|
|
170
|
+
console.log(chalk_1.default.gray(' Workflows:'));
|
|
171
|
+
overridesByType.workflows.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
172
|
+
console.log('');
|
|
173
|
+
}
|
|
174
|
+
if (overridesByType.templates.length > 0) {
|
|
175
|
+
console.log(chalk_1.default.gray(' Templates:'));
|
|
176
|
+
overridesByType.templates.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
177
|
+
console.log('');
|
|
178
|
+
}
|
|
179
|
+
if (overridesByType.rules.length > 0) {
|
|
180
|
+
console.log(chalk_1.default.gray(' Rules:'));
|
|
181
|
+
overridesByType.rules.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
182
|
+
console.log('');
|
|
183
|
+
}
|
|
184
|
+
if (overridesByType.other.length > 0) {
|
|
185
|
+
console.log(chalk_1.default.gray(' Other:'));
|
|
186
|
+
overridesByType.other.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
|
|
187
|
+
console.log('');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(chalk_1.default.gray('No active overrides yet.\n'));
|
|
192
|
+
}
|
|
193
|
+
showTips();
|
|
194
|
+
function showTips() {
|
|
195
|
+
console.log(chalk_1.default.gray('Tips:'));
|
|
196
|
+
console.log(chalk_1.default.gray(' • Use "fraim override <path> --inherit" to inherit from global'));
|
|
197
|
+
console.log(chalk_1.default.gray(' • Use "fraim override <path> --copy" to copy current content'));
|
|
198
|
+
console.log(chalk_1.default.gray(' • Use --job-category <category> to see category-specific items'));
|
|
199
|
+
console.log(chalk_1.default.gray(' • Use --rules to see all overridable rules'));
|
|
200
|
+
console.log(chalk_1.default.gray(' • Overrides are stored in .fraim/overrides/'));
|
|
91
201
|
}
|
|
92
|
-
// Show tips
|
|
93
|
-
console.log(chalk_1.default.gray('Tips:'));
|
|
94
|
-
console.log(chalk_1.default.gray(' • Use "fraim override <path> --inherit" to inherit from global'));
|
|
95
|
-
console.log(chalk_1.default.gray(' • Use "fraim override <path> --copy" to copy current content'));
|
|
96
|
-
console.log(chalk_1.default.gray(' • Overrides are stored in .fraim/overrides/'));
|
|
97
202
|
});
|
|
@@ -9,11 +9,13 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
11
|
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
const git_utils_1 = require("../../core/utils/git-utils");
|
|
12
13
|
exports.overrideCommand = new commander_1.Command('override')
|
|
13
14
|
.description('Create a local override for a FRAIM registry file')
|
|
14
15
|
.argument('<path>', 'Registry path to override (e.g., workflows/product-building/spec.md)')
|
|
15
16
|
.option('--inherit', 'Create override with {{ import }} directive (inherits from global)')
|
|
16
|
-
.option('--copy', 'Copy current content from
|
|
17
|
+
.option('--copy', 'Copy current content from server to local')
|
|
18
|
+
.option('--local', 'Fetch from local development server (port derived from git branch)')
|
|
17
19
|
.action(async (registryPath, options) => {
|
|
18
20
|
const projectRoot = process.cwd();
|
|
19
21
|
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
@@ -56,48 +58,112 @@ exports.overrideCommand = new commander_1.Command('override')
|
|
|
56
58
|
console.log(chalk_1.default.blue(`📝 Creating inherited override for: ${registryPath}`));
|
|
57
59
|
}
|
|
58
60
|
else {
|
|
59
|
-
// Fetch current content from
|
|
60
|
-
|
|
61
|
+
// Fetch current content from server using MCP protocol
|
|
62
|
+
const isLocal = options.local || false;
|
|
63
|
+
const serverType = isLocal ? 'local server' : 'remote server';
|
|
64
|
+
console.log(chalk_1.default.blue(`📥 Fetching current content from ${serverType}: ${registryPath}`));
|
|
61
65
|
try {
|
|
62
66
|
// Read config to get remote URL
|
|
63
67
|
let config = {};
|
|
64
68
|
if (fs_1.default.existsSync(configPath)) {
|
|
65
69
|
config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
66
70
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const workflowName = registryPath.replace('workflows/', '').replace('.md', '');
|
|
73
|
-
endpoint = `${remoteUrl}/api/workflows/${workflowName}`;
|
|
71
|
+
// Determine server URL
|
|
72
|
+
let serverUrl;
|
|
73
|
+
if (isLocal) {
|
|
74
|
+
const localPort = process.env.FRAIM_MCP_PORT ? parseInt(process.env.FRAIM_MCP_PORT) : (0, git_utils_1.getPort)();
|
|
75
|
+
serverUrl = `http://localhost:${localPort}`;
|
|
74
76
|
}
|
|
75
77
|
else {
|
|
76
|
-
|
|
78
|
+
serverUrl = config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
77
79
|
}
|
|
78
|
-
const
|
|
80
|
+
const apiKey = isLocal ? 'local-dev' : (config.apiKey || process.env.FRAIM_API_KEY);
|
|
81
|
+
const headers = {
|
|
82
|
+
'Content-Type': 'application/json'
|
|
83
|
+
};
|
|
79
84
|
if (apiKey) {
|
|
80
85
|
headers['x-api-key'] = apiKey;
|
|
81
86
|
}
|
|
82
|
-
|
|
87
|
+
// First, establish session with fraim_connect
|
|
88
|
+
const connectRequest = {
|
|
89
|
+
jsonrpc: '2.0',
|
|
90
|
+
id: 0,
|
|
91
|
+
method: 'tools/call',
|
|
92
|
+
params: {
|
|
93
|
+
name: 'fraim_connect',
|
|
94
|
+
arguments: {
|
|
95
|
+
agent: { name: 'fraim-cli', model: 'override-command' },
|
|
96
|
+
machine: {
|
|
97
|
+
hostname: require('os').hostname(),
|
|
98
|
+
platform: process.platform,
|
|
99
|
+
memory: require('os').totalmem(),
|
|
100
|
+
cpus: require('os').cpus().length
|
|
101
|
+
},
|
|
102
|
+
repo: {
|
|
103
|
+
url: config.repository?.url || 'https://github.com/unknown/unknown',
|
|
104
|
+
owner: config.repository?.owner || 'unknown',
|
|
105
|
+
name: config.repository?.name || 'unknown',
|
|
106
|
+
branch: 'main'
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
await axios_1.default.post(`${serverUrl}/mcp`, connectRequest, { headers, timeout: 30000 });
|
|
112
|
+
// Now determine MCP tool and arguments based on path
|
|
113
|
+
let toolName;
|
|
114
|
+
let toolArgs;
|
|
115
|
+
if (registryPath.startsWith('workflows/')) {
|
|
116
|
+
// Extract workflow name from path
|
|
117
|
+
// e.g., "workflows/product-building/spec.md" -> "spec"
|
|
118
|
+
const parts = registryPath.split('/');
|
|
119
|
+
const workflowName = parts[parts.length - 1].replace('.md', '');
|
|
120
|
+
toolName = 'get_fraim_workflow';
|
|
121
|
+
toolArgs = { workflow: workflowName };
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
toolName = 'get_fraim_file';
|
|
125
|
+
toolArgs = { path: registryPath };
|
|
126
|
+
}
|
|
127
|
+
console.log(chalk_1.default.gray(` Using MCP tool: ${toolName}`));
|
|
128
|
+
// Make MCP JSON-RPC request
|
|
129
|
+
const mcpRequest = {
|
|
130
|
+
jsonrpc: '2.0',
|
|
131
|
+
id: 1,
|
|
132
|
+
method: 'tools/call',
|
|
133
|
+
params: {
|
|
134
|
+
name: toolName,
|
|
135
|
+
arguments: toolArgs
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const response = await axios_1.default.post(`${serverUrl}/mcp`, mcpRequest, {
|
|
83
139
|
headers,
|
|
84
140
|
timeout: 30000
|
|
85
141
|
});
|
|
86
|
-
// Extract content from response
|
|
87
|
-
if (response.data.
|
|
88
|
-
|
|
142
|
+
// Extract content from MCP response
|
|
143
|
+
if (response.data.error) {
|
|
144
|
+
throw new Error(response.data.error.message || 'MCP request failed');
|
|
89
145
|
}
|
|
90
|
-
|
|
91
|
-
|
|
146
|
+
const result = response.data.result;
|
|
147
|
+
if (!result || !result.content || !Array.isArray(result.content)) {
|
|
148
|
+
throw new Error('Unexpected MCP response format');
|
|
92
149
|
}
|
|
93
|
-
|
|
94
|
-
|
|
150
|
+
// Extract text content from MCP response
|
|
151
|
+
const textContent = result.content.find((c) => c.type === 'text');
|
|
152
|
+
if (!textContent || !textContent.text) {
|
|
153
|
+
throw new Error('No text content in MCP response');
|
|
95
154
|
}
|
|
96
|
-
|
|
155
|
+
content = textContent.text;
|
|
156
|
+
console.log(chalk_1.default.green(`✅ Fetched ${content.length} bytes from ${serverType}`));
|
|
97
157
|
}
|
|
98
158
|
catch (error) {
|
|
99
|
-
console.log(chalk_1.default.red(`❌ Failed to fetch content from
|
|
100
|
-
|
|
159
|
+
console.log(chalk_1.default.red(`❌ Failed to fetch content from ${serverType}: ${error.message}`));
|
|
160
|
+
if (isLocal) {
|
|
161
|
+
console.log(chalk_1.default.gray(` Tip: Make sure the FRAIM server is running locally (npm run start:fraim)`));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.log(chalk_1.default.gray(` Tip: Check your API key and network connection`));
|
|
165
|
+
console.log(chalk_1.default.gray(` Or use --local to fetch from a locally running server`));
|
|
166
|
+
}
|
|
101
167
|
process.exit(1);
|
|
102
168
|
}
|
|
103
169
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.73",
|
|
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": {
|