fraim-framework 2.0.71 → 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.
- package/bin/fraim.js +1 -1
- package/dist/src/cli/commands/doctor.js +55 -0
- package/dist/src/cli/commands/list-overridable.js +202 -0
- package/dist/src/cli/commands/override.js +181 -0
- package/dist/src/cli/fraim.js +4 -0
- package/dist/src/core/utils/inheritance-parser.js +150 -0
- package/dist/src/core/utils/local-registry-resolver.js +165 -0
- package/dist/src/local-mcp-server/stdio-server.js +246 -21
- package/index.js +1 -1
- package/package.json +1 -1
- package/CHANGELOG.md +0 -76
- package/dist/src/cli/commands/init.js +0 -148
- package/dist/src/cli/commands/mcp.js +0 -65
- package/dist/src/cli/commands/wizard.js +0 -35
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LocalRegistryResolver
|
|
4
|
+
*
|
|
5
|
+
* Resolves registry file requests by checking for local overrides first,
|
|
6
|
+
* then falling back to remote registry. Handles inheritance via InheritanceParser.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LocalRegistryResolver = void 0;
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const inheritance_parser_1 = require("./inheritance-parser");
|
|
13
|
+
class LocalRegistryResolver {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.workspaceRoot = options.workspaceRoot;
|
|
16
|
+
this.remoteContentResolver = options.remoteContentResolver;
|
|
17
|
+
this.parser = new inheritance_parser_1.InheritanceParser(options.maxDepth);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if a local override exists for the given path
|
|
21
|
+
*/
|
|
22
|
+
hasLocalOverride(path) {
|
|
23
|
+
const overridePath = this.getOverridePath(path);
|
|
24
|
+
const exists = (0, fs_1.existsSync)(overridePath);
|
|
25
|
+
console.error(`[LocalRegistryResolver] hasLocalOverride(${path}) -> ${overridePath} -> ${exists}`);
|
|
26
|
+
return exists;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the full path to a local override file
|
|
30
|
+
*/
|
|
31
|
+
getOverridePath(path) {
|
|
32
|
+
return (0, path_1.join)(this.workspaceRoot, '.fraim/overrides', path);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read local override file content
|
|
36
|
+
*/
|
|
37
|
+
readLocalOverride(path) {
|
|
38
|
+
const overridePath = this.getOverridePath(path);
|
|
39
|
+
try {
|
|
40
|
+
return (0, fs_1.readFileSync)(overridePath, 'utf-8');
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new Error(`Failed to read local override: ${path}. ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve inheritance in local override content
|
|
48
|
+
*/
|
|
49
|
+
async resolveInheritance(content, currentPath) {
|
|
50
|
+
// Check if content has imports
|
|
51
|
+
const parseResult = this.parser.parse(content);
|
|
52
|
+
if (!parseResult.hasImports) {
|
|
53
|
+
return { content, imports: [] };
|
|
54
|
+
}
|
|
55
|
+
// Resolve imports
|
|
56
|
+
try {
|
|
57
|
+
const resolved = await this.parser.resolve(content, currentPath, {
|
|
58
|
+
fetchParent: this.remoteContentResolver,
|
|
59
|
+
maxDepth: 5
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
content: resolved,
|
|
63
|
+
imports: parseResult.imports
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof inheritance_parser_1.InheritanceError) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Failed to resolve inheritance for ${currentPath}: ${error.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate metadata comment for resolved content
|
|
75
|
+
*/
|
|
76
|
+
generateMetadata(result) {
|
|
77
|
+
if (result.source === 'remote') {
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
if (result.inherited && result.imports && result.imports.length > 0) {
|
|
81
|
+
return `<!-- 📝 Using local override (inherited from: ${result.imports.join(', ')}) -->\n\n`;
|
|
82
|
+
}
|
|
83
|
+
return `<!-- 📝 Using local override -->\n\n`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a registry file request
|
|
87
|
+
*
|
|
88
|
+
* Resolution order:
|
|
89
|
+
* 1. Check for local override in .fraim/overrides/
|
|
90
|
+
* 2. If found, read and resolve inheritance
|
|
91
|
+
* 3. If not found, fetch from remote
|
|
92
|
+
*
|
|
93
|
+
* @param path - Registry path (e.g., "workflows/product-building/spec.md")
|
|
94
|
+
* @returns Resolved file with metadata
|
|
95
|
+
*/
|
|
96
|
+
async resolveFile(path) {
|
|
97
|
+
console.error(`[LocalRegistryResolver] ===== resolveFile called for: ${path} =====`);
|
|
98
|
+
// Check for local override
|
|
99
|
+
if (!this.hasLocalOverride(path)) {
|
|
100
|
+
// No override, fetch from remote
|
|
101
|
+
try {
|
|
102
|
+
const content = await this.remoteContentResolver(path);
|
|
103
|
+
return {
|
|
104
|
+
content,
|
|
105
|
+
source: 'remote',
|
|
106
|
+
inherited: false
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
throw new Error(`Failed to fetch from remote: ${path}. ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Read local override
|
|
114
|
+
let localContent;
|
|
115
|
+
try {
|
|
116
|
+
localContent = this.readLocalOverride(path);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// If local read fails, fall back to remote
|
|
120
|
+
console.warn(`Local override read failed, falling back to remote: ${path}`);
|
|
121
|
+
const content = await this.remoteContentResolver(path);
|
|
122
|
+
return {
|
|
123
|
+
content,
|
|
124
|
+
source: 'remote',
|
|
125
|
+
inherited: false
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Resolve inheritance
|
|
129
|
+
let resolved;
|
|
130
|
+
try {
|
|
131
|
+
console.error(`[LocalRegistryResolver] Resolving inheritance for ${path}`);
|
|
132
|
+
console.error(`[LocalRegistryResolver] Local content length: ${localContent.length} chars`);
|
|
133
|
+
console.error(`[LocalRegistryResolver] Local content preview: ${localContent.substring(0, 200)}`);
|
|
134
|
+
resolved = await this.resolveInheritance(localContent, path);
|
|
135
|
+
console.error(`[LocalRegistryResolver] Inheritance resolved: ${resolved.imports.length} imports`);
|
|
136
|
+
console.error(`[LocalRegistryResolver] Resolved content length: ${resolved.content.length} chars`);
|
|
137
|
+
console.error(`[LocalRegistryResolver] Resolved content preview: ${resolved.content.substring(0, 200)}`);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// If inheritance resolution fails, fall back to remote
|
|
141
|
+
console.error(`❌ Inheritance resolution failed for ${path}:`, error);
|
|
142
|
+
const content = await this.remoteContentResolver(path);
|
|
143
|
+
return {
|
|
144
|
+
content,
|
|
145
|
+
source: 'remote',
|
|
146
|
+
inherited: false
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Build result
|
|
150
|
+
const result = {
|
|
151
|
+
content: resolved.content,
|
|
152
|
+
source: 'local',
|
|
153
|
+
inherited: resolved.imports.length > 0,
|
|
154
|
+
imports: resolved.imports.length > 0 ? resolved.imports : undefined
|
|
155
|
+
};
|
|
156
|
+
// Add metadata comment
|
|
157
|
+
const metadata = this.generateMetadata(result);
|
|
158
|
+
if (metadata) {
|
|
159
|
+
result.metadata = metadata;
|
|
160
|
+
result.content = metadata + result.content;
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.LocalRegistryResolver = LocalRegistryResolver;
|
|
@@ -27,6 +27,7 @@ const crypto_1 = require("crypto");
|
|
|
27
27
|
const axios_1 = __importDefault(require("axios"));
|
|
28
28
|
const provider_utils_1 = require("../core/utils/provider-utils");
|
|
29
29
|
const object_utils_1 = require("../core/utils/object-utils");
|
|
30
|
+
const local_registry_resolver_1 = require("../core/utils/local-registry-resolver");
|
|
30
31
|
/**
|
|
31
32
|
* Handle template substitution logic separately for better testability
|
|
32
33
|
*/
|
|
@@ -80,22 +81,35 @@ class FraimTemplateEngine {
|
|
|
80
81
|
const filename = this.workingStyle === 'Conversation' ? 'delivery-conversation.json' : 'delivery-pr.json';
|
|
81
82
|
try {
|
|
82
83
|
let content = null;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
// Try framework installation directory first (relative to this file)
|
|
85
|
+
// This file is in dist/src/local-mcp-server/, so go up to framework root
|
|
86
|
+
const frameworkRoot = (0, path_1.join)(__dirname, '..', '..', '..');
|
|
87
|
+
const frameworkPath = (0, path_1.join)(frameworkRoot, 'registry', 'providers', filename);
|
|
88
|
+
if ((0, fs_1.existsSync)(frameworkPath)) {
|
|
89
|
+
content = (0, fs_1.readFileSync)(frameworkPath, 'utf-8');
|
|
90
|
+
this.logFn(`✅ Loaded delivery templates from framework: ${frameworkPath}`);
|
|
91
|
+
}
|
|
92
|
+
// Fallback: try node_modules if not found in framework root
|
|
89
93
|
if (!content) {
|
|
90
94
|
const nodeModulesPath = (0, path_1.join)(process.cwd(), 'node_modules', '@fraim', 'framework', 'registry', 'providers', filename);
|
|
91
95
|
if ((0, fs_1.existsSync)(nodeModulesPath)) {
|
|
92
96
|
content = (0, fs_1.readFileSync)(nodeModulesPath, 'utf-8');
|
|
97
|
+
this.logFn(`✅ Loaded delivery templates from node_modules: ${nodeModulesPath}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Last resort: try project root (for custom overrides)
|
|
101
|
+
if (!content && this.projectRoot) {
|
|
102
|
+
const deliveryPath = (0, path_1.join)(this.projectRoot, 'registry', 'providers', filename);
|
|
103
|
+
if ((0, fs_1.existsSync)(deliveryPath)) {
|
|
104
|
+
content = (0, fs_1.readFileSync)(deliveryPath, 'utf-8');
|
|
105
|
+
this.logFn(`✅ Loaded delivery templates from project: ${deliveryPath}`);
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
if (content) {
|
|
96
109
|
this.deliveryTemplatesCache = JSON.parse(content);
|
|
97
110
|
return this.deliveryTemplatesCache;
|
|
98
111
|
}
|
|
112
|
+
this.logFn(`⚠️ Could not find delivery templates: ${filename}`);
|
|
99
113
|
return null;
|
|
100
114
|
}
|
|
101
115
|
catch (error) {
|
|
@@ -121,18 +135,26 @@ class FraimTemplateEngine {
|
|
|
121
135
|
return this.providerTemplatesCache[provider];
|
|
122
136
|
try {
|
|
123
137
|
let content = null;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
// Try framework installation directory first (relative to this file)
|
|
139
|
+
const frameworkRoot = (0, path_1.join)(__dirname, '..', '..', '..');
|
|
140
|
+
const frameworkPath = (0, path_1.join)(frameworkRoot, 'registry', 'providers', `${provider}.json`);
|
|
141
|
+
if ((0, fs_1.existsSync)(frameworkPath)) {
|
|
142
|
+
content = (0, fs_1.readFileSync)(frameworkPath, 'utf-8');
|
|
129
143
|
}
|
|
144
|
+
// Fallback: try node_modules
|
|
130
145
|
if (!content) {
|
|
131
146
|
const nodeModulesPath = (0, path_1.join)(process.cwd(), 'node_modules', '@fraim', 'framework', 'registry', 'providers', `${provider}.json`);
|
|
132
147
|
if ((0, fs_1.existsSync)(nodeModulesPath)) {
|
|
133
148
|
content = (0, fs_1.readFileSync)(nodeModulesPath, 'utf-8');
|
|
134
149
|
}
|
|
135
150
|
}
|
|
151
|
+
// Last resort: try project root (for custom overrides)
|
|
152
|
+
if (!content && this.projectRoot) {
|
|
153
|
+
const providerPath = (0, path_1.join)(this.projectRoot, 'registry', 'providers', `${provider}.json`);
|
|
154
|
+
if ((0, fs_1.existsSync)(providerPath)) {
|
|
155
|
+
content = (0, fs_1.readFileSync)(providerPath, 'utf-8');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
136
158
|
if (content) {
|
|
137
159
|
const templates = JSON.parse(content);
|
|
138
160
|
this.providerTemplatesCache[provider] = templates;
|
|
@@ -178,6 +200,7 @@ class FraimLocalMCPServer {
|
|
|
178
200
|
this.machineInfo = null;
|
|
179
201
|
this.repoInfo = null;
|
|
180
202
|
this.engine = null;
|
|
203
|
+
this.registryResolver = null;
|
|
181
204
|
this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
182
205
|
this.apiKey = process.env.FRAIM_API_KEY || '';
|
|
183
206
|
this.localVersion = this.detectLocalVersion();
|
|
@@ -349,6 +372,10 @@ class FraimLocalMCPServer {
|
|
|
349
372
|
if (this.repoInfo) {
|
|
350
373
|
return this.repoInfo;
|
|
351
374
|
}
|
|
375
|
+
// Ensure config is loaded before trying to detect repo info
|
|
376
|
+
if (!this.config) {
|
|
377
|
+
this.loadConfig();
|
|
378
|
+
}
|
|
352
379
|
try {
|
|
353
380
|
const projectDir = this.findProjectRoot() || process.cwd();
|
|
354
381
|
// Try to get git remote URL
|
|
@@ -464,6 +491,112 @@ class FraimLocalMCPServer {
|
|
|
464
491
|
}
|
|
465
492
|
return this.engine.substituteTemplates(content);
|
|
466
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* Initialize the LocalRegistryResolver for override resolution
|
|
496
|
+
*/
|
|
497
|
+
getRegistryResolver() {
|
|
498
|
+
if (!this.registryResolver) {
|
|
499
|
+
const projectRoot = this.findProjectRoot();
|
|
500
|
+
this.log(`🔍 getRegistryResolver: projectRoot = ${projectRoot}`);
|
|
501
|
+
if (!projectRoot) {
|
|
502
|
+
this.log('⚠️ No project root found, override resolution disabled');
|
|
503
|
+
// Return a resolver that always falls back to remote
|
|
504
|
+
this.registryResolver = new local_registry_resolver_1.LocalRegistryResolver({
|
|
505
|
+
workspaceRoot: process.cwd(),
|
|
506
|
+
remoteContentResolver: async (path) => {
|
|
507
|
+
throw new Error('No project root available');
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
this.registryResolver = new local_registry_resolver_1.LocalRegistryResolver({
|
|
513
|
+
workspaceRoot: projectRoot,
|
|
514
|
+
remoteContentResolver: async (path) => {
|
|
515
|
+
// Fetch parent content from remote for inheritance
|
|
516
|
+
this.log(`🔄 Remote content resolver: fetching ${path}`);
|
|
517
|
+
let request;
|
|
518
|
+
if (path.startsWith('workflows/')) {
|
|
519
|
+
// Extract workflow name from path: workflows/category/name.md -> name
|
|
520
|
+
const pathParts = path.replace('workflows/', '').replace('.md', '').split('/');
|
|
521
|
+
const workflowName = pathParts[pathParts.length - 1]; // Get last part (name)
|
|
522
|
+
this.log(`🔄 Fetching workflow: ${workflowName}`);
|
|
523
|
+
request = {
|
|
524
|
+
jsonrpc: '2.0',
|
|
525
|
+
id: (0, crypto_1.randomUUID)(),
|
|
526
|
+
method: 'tools/call',
|
|
527
|
+
params: {
|
|
528
|
+
name: 'get_fraim_workflow',
|
|
529
|
+
arguments: { workflow: workflowName }
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
// For non-workflow files (templates, rules, etc.), use get_fraim_file
|
|
535
|
+
this.log(`🔄 Fetching file: ${path}`);
|
|
536
|
+
request = {
|
|
537
|
+
jsonrpc: '2.0',
|
|
538
|
+
id: (0, crypto_1.randomUUID)(),
|
|
539
|
+
method: 'tools/call',
|
|
540
|
+
params: {
|
|
541
|
+
name: 'get_fraim_file',
|
|
542
|
+
arguments: { path }
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const response = await this.proxyToRemote(request);
|
|
547
|
+
if (response.error) {
|
|
548
|
+
this.logError(`❌ Remote content resolver failed: ${response.error.message}`);
|
|
549
|
+
throw new Error(`Failed to fetch parent: ${response.error.message}`);
|
|
550
|
+
}
|
|
551
|
+
// Extract content from MCP response format
|
|
552
|
+
if (response.result?.content && Array.isArray(response.result.content)) {
|
|
553
|
+
const textContent = response.result.content.find((c) => c.type === 'text');
|
|
554
|
+
if (textContent?.text) {
|
|
555
|
+
this.log(`✅ Remote content resolver: fetched ${textContent.text.length} chars`);
|
|
556
|
+
return textContent.text;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
this.logError(`❌ Remote content resolver: no content in response for ${path}`);
|
|
560
|
+
this.logError(`Response: ${JSON.stringify(response, null, 2)}`);
|
|
561
|
+
throw new Error(`No content in remote response for ${path}`);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return this.registryResolver;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Determine workflow category from workflow name
|
|
570
|
+
*/
|
|
571
|
+
getWorkflowCategory(workflowName) {
|
|
572
|
+
// Product development workflows
|
|
573
|
+
const productWorkflows = [
|
|
574
|
+
'prep-issue', 'spec', 'design', 'implement', 'test', 'resolve',
|
|
575
|
+
'prototype', 'refactor', 'iterate-on-pr-comments', 'retrospect'
|
|
576
|
+
];
|
|
577
|
+
// Customer development workflows
|
|
578
|
+
const customerWorkflows = [
|
|
579
|
+
'linkedin-outreach', 'customer-interview', 'insight-analysis',
|
|
580
|
+
'insight-triage', 'interview-preparation', 'strategic-brainstorming',
|
|
581
|
+
'thank-customers', 'user-survey-dispatch', 'users-to-target', 'weekly-newsletter'
|
|
582
|
+
];
|
|
583
|
+
// Business development workflows
|
|
584
|
+
const businessWorkflows = [
|
|
585
|
+
'partnership-outreach', 'investor-pitch', 'create-business-plan',
|
|
586
|
+
'ideate-business-opportunity', 'price-product'
|
|
587
|
+
];
|
|
588
|
+
if (productWorkflows.includes(workflowName)) {
|
|
589
|
+
return 'product-building';
|
|
590
|
+
}
|
|
591
|
+
else if (customerWorkflows.includes(workflowName)) {
|
|
592
|
+
return 'customer-development';
|
|
593
|
+
}
|
|
594
|
+
else if (businessWorkflows.includes(workflowName)) {
|
|
595
|
+
return 'business-development';
|
|
596
|
+
}
|
|
597
|
+
// Default to product-building for unknown workflows
|
|
598
|
+
return 'product-building';
|
|
599
|
+
}
|
|
467
600
|
/**
|
|
468
601
|
* Process template substitution in MCP response
|
|
469
602
|
*/
|
|
@@ -522,16 +655,20 @@ class FraimLocalMCPServer {
|
|
|
522
655
|
this.log(`[req:${requestId}] Auto-detected and injected repo info: ${args.repo.owner}/${args.repo.name}`);
|
|
523
656
|
}
|
|
524
657
|
else {
|
|
525
|
-
// If detection fails
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
658
|
+
// If detection fails, use agent-provided values (if any)
|
|
659
|
+
if (!args.repo || !args.repo.url) {
|
|
660
|
+
// Only return error if agent didn't provide repo info either
|
|
661
|
+
this.logError(`[req:${requestId}] Could not detect repo info and no repo info provided by agent`);
|
|
662
|
+
return {
|
|
663
|
+
jsonrpc: '2.0',
|
|
664
|
+
id: request.id,
|
|
665
|
+
error: {
|
|
666
|
+
code: -32603,
|
|
667
|
+
message: 'Failed to detect repository information. Please ensure you are in a git repository or have .fraim/config.json configured with repository details, or provide repo info in fraim_connect arguments.'
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
this.log(`[req:${requestId}] Using agent-provided repo info: ${args.repo.owner}/${args.repo.name}`);
|
|
535
672
|
}
|
|
536
673
|
// Update the request with injected info
|
|
537
674
|
request.params.arguments = args;
|
|
@@ -640,6 +777,94 @@ class FraimLocalMCPServer {
|
|
|
640
777
|
this.log(`📤 ${request.method} → ${processedResponse.error ? 'ERROR' : 'OK'}`);
|
|
641
778
|
return processedResponse;
|
|
642
779
|
}
|
|
780
|
+
// Intercept get_fraim_workflow and get_fraim_file for override resolution
|
|
781
|
+
if (request.method === 'tools/call' &&
|
|
782
|
+
(request.params?.name === 'get_fraim_workflow' ||
|
|
783
|
+
request.params?.name === 'get_fraim_file')) {
|
|
784
|
+
try {
|
|
785
|
+
const toolName = request.params.name;
|
|
786
|
+
const args = request.params.arguments || {};
|
|
787
|
+
// Extract the requested path
|
|
788
|
+
let requestedPath;
|
|
789
|
+
if (toolName === 'get_fraim_workflow') {
|
|
790
|
+
// Convert workflow name to path (e.g., "spec" -> "workflows/product-building/spec.md")
|
|
791
|
+
const workflowName = args.workflow;
|
|
792
|
+
if (!workflowName) {
|
|
793
|
+
this.log('⚠️ No workflow name provided in get_fraim_workflow');
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
// Determine workflow category from name
|
|
797
|
+
const category = this.getWorkflowCategory(workflowName);
|
|
798
|
+
requestedPath = `workflows/${category}/${workflowName}.md`;
|
|
799
|
+
this.log(`🔍 Checking for override: ${requestedPath}`);
|
|
800
|
+
const resolver = this.getRegistryResolver();
|
|
801
|
+
const hasOverride = resolver.hasLocalOverride(requestedPath);
|
|
802
|
+
this.log(`🔍 hasLocalOverride(${requestedPath}) = ${hasOverride}`);
|
|
803
|
+
if (hasOverride) {
|
|
804
|
+
this.log(`✅ Local override found: ${requestedPath}`);
|
|
805
|
+
const resolved = await resolver.resolveFile(requestedPath);
|
|
806
|
+
this.log(`📝 Override resolved (source: ${resolved.source}, inherited: ${resolved.inherited})`);
|
|
807
|
+
// Build MCP response with resolved content
|
|
808
|
+
const response = {
|
|
809
|
+
jsonrpc: '2.0',
|
|
810
|
+
id: request.id,
|
|
811
|
+
result: {
|
|
812
|
+
content: [
|
|
813
|
+
{
|
|
814
|
+
type: 'text',
|
|
815
|
+
text: resolved.content
|
|
816
|
+
}
|
|
817
|
+
]
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
// Apply template substitution
|
|
821
|
+
const processedResponse = this.processResponse(response);
|
|
822
|
+
this.log(`📤 ${request.method} → OK`);
|
|
823
|
+
return processedResponse;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
else if (toolName === 'get_fraim_file') {
|
|
828
|
+
requestedPath = args.path;
|
|
829
|
+
if (!requestedPath) {
|
|
830
|
+
this.log('⚠️ No path provided in get_fraim_file');
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
this.log(`🔍 Checking for override: ${requestedPath}`);
|
|
834
|
+
const resolver = this.getRegistryResolver();
|
|
835
|
+
const hasOverride = resolver.hasLocalOverride(requestedPath);
|
|
836
|
+
this.log(`🔍 hasLocalOverride(${requestedPath}) = ${hasOverride}`);
|
|
837
|
+
if (hasOverride) {
|
|
838
|
+
this.log(`✅ Local override found: ${requestedPath}`);
|
|
839
|
+
const resolved = await resolver.resolveFile(requestedPath);
|
|
840
|
+
this.log(`📝 Override resolved (source: ${resolved.source}, inherited: ${resolved.inherited})`);
|
|
841
|
+
// Build MCP response with resolved content
|
|
842
|
+
const response = {
|
|
843
|
+
jsonrpc: '2.0',
|
|
844
|
+
id: request.id,
|
|
845
|
+
result: {
|
|
846
|
+
content: [
|
|
847
|
+
{
|
|
848
|
+
type: 'text',
|
|
849
|
+
text: resolved.content
|
|
850
|
+
}
|
|
851
|
+
]
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
// Apply template substitution
|
|
855
|
+
const processedResponse = this.processResponse(response);
|
|
856
|
+
this.log(`📤 ${request.method} → OK`);
|
|
857
|
+
return processedResponse;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
catch (error) {
|
|
863
|
+
this.logError(`Override resolution failed: ${error.message}`);
|
|
864
|
+
this.log('⚠️ Falling back to remote');
|
|
865
|
+
// Fall through to proxy to remote
|
|
866
|
+
}
|
|
867
|
+
}
|
|
643
868
|
// Proxy to remote server
|
|
644
869
|
const response = await this.proxyToRemote(request);
|
|
645
870
|
// Process template substitution (config vars, platform actions, delivery templates)
|
package/index.js
CHANGED
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": {
|
package/CHANGELOG.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
## [2.1.0] - 2026-02-04
|
|
7
|
-
|
|
8
|
-
### 🔄 Workflow Refinement
|
|
9
|
-
- **Consolidated Workflows**: Inlined all phase content into main workflow files (`spec.md`, `design.md`, `implement.md`) for better context retention and reduced file scatter.
|
|
10
|
-
- **Removed Redundant Files**: Deleted separate phase folders (`design-phases`, `implement-phases`, `spec-phases`, etc.) to streamline repository structure.
|
|
11
|
-
- **Terminology Update**: Renamed "AI Coach" to "AI Mentor" across all workflows to align with new branding.
|
|
12
|
-
- **Enhanced Spec Workflow**: Added `spec-competitor-analysis` phase and inlined validation steps.
|
|
13
|
-
- **Retrospective Integration**: Inlined retrospective phase content into all major workflows.
|
|
14
|
-
|
|
15
|
-
## [2.0.0] - 2024-12-19
|
|
16
|
-
|
|
17
|
-
### 🚀 Major Release: FRAIM v2 - The Future of AI-Assisted Development
|
|
18
|
-
|
|
19
|
-
#### ✨ New Features
|
|
20
|
-
- **Complete Generic Framework**: Removed all Ashley-specific IP, making FRAIM truly universal
|
|
21
|
-
- **Enhanced Rule System**: 13 comprehensive rule files covering all aspects of AI agent management
|
|
22
|
-
- **Simplified Label System**: Streamlined to 9 essential labels matching real-world usage
|
|
23
|
-
- **Spec Workflow**: Added specification phase for requirements and user experience definition
|
|
24
|
-
- **Timeout Management**: Advanced timeout scripts with output visibility for long-running tasks
|
|
25
|
-
- **Evidence-Based Validation**: Mandatory test evidence collection and validation
|
|
26
|
-
- **Systematic Debugging**: Structured debugging patterns with learning capture
|
|
27
|
-
|
|
28
|
-
#### 🔧 Improvements
|
|
29
|
-
- **Single Install Method**: Simplified to `npm install -g fraim-framework`
|
|
30
|
-
- **Better Documentation**: Comprehensive README with marketing-style positioning
|
|
31
|
-
- **Example Test Cases**: Complete example with tagging system and documentation
|
|
32
|
-
- **TypeScript Support**: Full TypeScript compilation and type safety
|
|
33
|
-
- **Git Safety**: Safe Git commands preventing agent hangs
|
|
34
|
-
- **Merge Requirements**: Advanced branch management with conflict resolution
|
|
35
|
-
|
|
36
|
-
#### 🛡️ Security & Reliability
|
|
37
|
-
- **Agent Integrity Rules**: Prevents "fake it till you make it" behavior
|
|
38
|
-
- **Test Ethics**: Mandatory evidence collection and validation
|
|
39
|
-
- **Communication Standards**: Clear accountability and progress reporting
|
|
40
|
-
- **Architecture Discipline**: Clean separation of concerns and technical debt prevention
|
|
41
|
-
|
|
42
|
-
#### 📚 Documentation
|
|
43
|
-
- **Marketing README**: Compelling documentation positioning FRAIM as the future
|
|
44
|
-
- **Problem-Solution Mapping**: Each rule clearly mapped to specific problems
|
|
45
|
-
- **Human-Agent Parallels**: Clear comparison between human and AI development
|
|
46
|
-
- **Success Stories**: Realistic testimonials and quantified benefits
|
|
47
|
-
|
|
48
|
-
#### 🎯 Workflow Enhancements
|
|
49
|
-
- **RIGOR Methodology**: Reviews, Isolation, GitOps, Observability, Retrospectives
|
|
50
|
-
- **Phase-Based Development**: Spec → Design → Implementation → Testing → Resolution
|
|
51
|
-
- **Agent Coordination**: Seamless handoffs between multiple AI agents
|
|
52
|
-
- **Continuous Learning**: Retrospective-driven improvement system
|
|
53
|
-
|
|
54
|
-
#### 🔄 Breaking Changes
|
|
55
|
-
- **Simplified Labels**: Reduced from 15 to 9 essential labels
|
|
56
|
-
- **Install Method**: Single npm install method (removed curl, Python options)
|
|
57
|
-
- **Generic Examples**: All Ashley-specific examples replaced with universal patterns
|
|
58
|
-
|
|
59
|
-
#### 🐛 Bug Fixes
|
|
60
|
-
- **TypeScript Compilation**: Fixed all compilation issues
|
|
61
|
-
- **Import Dependencies**: Resolved missing dependencies
|
|
62
|
-
- **Test Utilities**: Made generic and framework-agnostic
|
|
63
|
-
- **Repository References**: Updated all URLs to point to FRAIM repository
|
|
64
|
-
|
|
65
|
-
#### 📦 Dependencies
|
|
66
|
-
- Added TypeScript support with proper type definitions
|
|
67
|
-
- Added tsx for TypeScript execution
|
|
68
|
-
- Added dotenv for environment management
|
|
69
|
-
- Updated Node.js engine requirement to >=16.0.0
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## [1.0.12] - Previous Release
|
|
74
|
-
- Initial FRAIM framework with Ashley-specific implementations
|
|
75
|
-
- Basic rule system and workflows
|
|
76
|
-
- GitHub integration and automation
|