fraim-framework 2.0.71 ā 2.0.72
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 +97 -0
- package/dist/src/cli/commands/override.js +115 -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
package/bin/fraim.js
CHANGED
|
@@ -8,6 +8,7 @@ const commander_1 = require("commander");
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const inheritance_parser_1 = require("../../core/utils/inheritance-parser");
|
|
11
12
|
exports.doctorCommand = new commander_1.Command('doctor')
|
|
12
13
|
.description('Validate FRAIM installation and configuration')
|
|
13
14
|
.action(async () => {
|
|
@@ -71,4 +72,58 @@ exports.doctorCommand = new commander_1.Command('doctor')
|
|
|
71
72
|
else {
|
|
72
73
|
console.log(chalk_1.default.red(`\n𩹠Found ${issuesFound} issues. See recommendations above.`));
|
|
73
74
|
}
|
|
75
|
+
// 5. Check for overrides
|
|
76
|
+
const overridesDir = path_1.default.join(fraimDir, 'overrides');
|
|
77
|
+
if (fs_1.default.existsSync(overridesDir)) {
|
|
78
|
+
console.log(chalk_1.default.blue('\nš Override Diagnostics:\n'));
|
|
79
|
+
const overrides = [];
|
|
80
|
+
const scanDir = (dir, base = '') => {
|
|
81
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const relativePath = path_1.default.join(base, entry.name);
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
scanDir(path_1.default.join(dir, entry.name), relativePath);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
overrides.push(relativePath.replace(/\\/g, '/'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
scanDir(overridesDir);
|
|
93
|
+
if (overrides.length === 0) {
|
|
94
|
+
console.log(chalk_1.default.gray(' No active overrides found.'));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.log(chalk_1.default.green(` Found ${overrides.length} active override(s):\n`));
|
|
98
|
+
const parser = new inheritance_parser_1.InheritanceParser();
|
|
99
|
+
for (const override of overrides) {
|
|
100
|
+
const overridePath = path_1.default.join(overridesDir, override);
|
|
101
|
+
const content = fs_1.default.readFileSync(overridePath, 'utf-8');
|
|
102
|
+
const parseResult = parser.parse(content);
|
|
103
|
+
if (parseResult.hasImports) {
|
|
104
|
+
console.log(chalk_1.default.white(` š ${override}`));
|
|
105
|
+
console.log(chalk_1.default.gray(` Inherits from: ${parseResult.imports.join(', ')}`));
|
|
106
|
+
// Validate import syntax
|
|
107
|
+
let hasErrors = false;
|
|
108
|
+
for (const importPath of parseResult.imports) {
|
|
109
|
+
try {
|
|
110
|
+
parser.sanitizePath(importPath);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.log(chalk_1.default.red(` ā ļø Invalid import: ${error.message}`));
|
|
114
|
+
hasErrors = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!hasErrors) {
|
|
118
|
+
console.log(chalk_1.default.green(` ā
Syntax valid`));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log(chalk_1.default.white(` š ${override}`));
|
|
123
|
+
console.log(chalk_1.default.gray(` Full override (no inheritance)`));
|
|
124
|
+
}
|
|
125
|
+
console.log('');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
74
129
|
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listOverridableCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
exports.listOverridableCommand = new commander_1.Command('list-overridable')
|
|
12
|
+
.description('List all FRAIM registry paths that can be overridden')
|
|
13
|
+
.action(async () => {
|
|
14
|
+
const projectRoot = process.cwd();
|
|
15
|
+
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
16
|
+
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
17
|
+
const overridesDir = path_1.default.join(fraimDir, 'overrides');
|
|
18
|
+
// Validate .fraim directory exists
|
|
19
|
+
if (!fs_1.default.existsSync(fraimDir)) {
|
|
20
|
+
console.log(chalk_1.default.red('ā .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
console.log(chalk_1.default.blue('š Overridable FRAIM Registry Paths:\n'));
|
|
24
|
+
// Define overridable categories
|
|
25
|
+
const categories = [
|
|
26
|
+
{
|
|
27
|
+
name: 'Workflows',
|
|
28
|
+
paths: [
|
|
29
|
+
'workflows/product-building/spec.md',
|
|
30
|
+
'workflows/product-building/design.md',
|
|
31
|
+
'workflows/product-building/implement.md',
|
|
32
|
+
'workflows/product-building/test.md',
|
|
33
|
+
'workflows/product-building/resolve.md',
|
|
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
|
+
]
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
// Get list of existing overrides
|
|
53
|
+
const existingOverrides = new Set();
|
|
54
|
+
if (fs_1.default.existsSync(overridesDir)) {
|
|
55
|
+
const scanDir = (dir, base = '') => {
|
|
56
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const relativePath = path_1.default.join(base, entry.name);
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
scanDir(path_1.default.join(dir, entry.name), relativePath);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
existingOverrides.add(relativePath.replace(/\\/g, '/'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
scanDir(overridesDir);
|
|
68
|
+
}
|
|
69
|
+
// Display categories
|
|
70
|
+
for (const category of categories) {
|
|
71
|
+
console.log(chalk_1.default.bold.cyan(`${category.name}:`));
|
|
72
|
+
for (const filePath of category.paths) {
|
|
73
|
+
const hasOverride = existingOverrides.has(filePath);
|
|
74
|
+
const status = hasOverride
|
|
75
|
+
? chalk_1.default.green('[OVERRIDDEN]')
|
|
76
|
+
: chalk_1.default.gray('[OVERRIDABLE]');
|
|
77
|
+
console.log(` ${status} ${filePath}`);
|
|
78
|
+
}
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
// Show existing overrides not in the standard list
|
|
82
|
+
const standardPaths = new Set(categories.flatMap(c => c.paths));
|
|
83
|
+
const customOverrides = Array.from(existingOverrides)
|
|
84
|
+
.filter(p => !standardPaths.has(p));
|
|
85
|
+
if (customOverrides.length > 0) {
|
|
86
|
+
console.log(chalk_1.default.bold.cyan('Custom Overrides:'));
|
|
87
|
+
for (const filePath of customOverrides) {
|
|
88
|
+
console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${filePath}`);
|
|
89
|
+
}
|
|
90
|
+
console.log('');
|
|
91
|
+
}
|
|
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
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.overrideCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
exports.overrideCommand = new commander_1.Command('override')
|
|
13
|
+
.description('Create a local override for a FRAIM registry file')
|
|
14
|
+
.argument('<path>', 'Registry path to override (e.g., workflows/product-building/spec.md)')
|
|
15
|
+
.option('--inherit', 'Create override with {{ import }} directive (inherits from global)')
|
|
16
|
+
.option('--copy', 'Copy current content from remote to local')
|
|
17
|
+
.action(async (registryPath, options) => {
|
|
18
|
+
const projectRoot = process.cwd();
|
|
19
|
+
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
20
|
+
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
21
|
+
// Validate .fraim directory exists
|
|
22
|
+
if (!fs_1.default.existsSync(fraimDir)) {
|
|
23
|
+
console.log(chalk_1.default.red('ā .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
// Validate path format
|
|
27
|
+
if (registryPath.includes('..') || path_1.default.isAbsolute(registryPath)) {
|
|
28
|
+
console.log(chalk_1.default.red('ā Invalid path. Path must be relative and cannot contain ".."'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// Ensure exactly one option is specified
|
|
32
|
+
if (options.inherit && options.copy) {
|
|
33
|
+
console.log(chalk_1.default.red('ā Cannot use both --inherit and --copy. Choose one.'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
if (!options.inherit && !options.copy) {
|
|
37
|
+
console.log(chalk_1.default.red('ā Must specify either --inherit or --copy.'));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
// Create overrides directory structure
|
|
41
|
+
const overridePath = path_1.default.join(fraimDir, 'overrides', registryPath);
|
|
42
|
+
const overrideDir = path_1.default.dirname(overridePath);
|
|
43
|
+
if (!fs_1.default.existsSync(overrideDir)) {
|
|
44
|
+
fs_1.default.mkdirSync(overrideDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
// Check if override already exists
|
|
47
|
+
if (fs_1.default.existsSync(overridePath)) {
|
|
48
|
+
console.log(chalk_1.default.yellow(`ā ļø Override already exists: ${registryPath}`));
|
|
49
|
+
console.log(chalk_1.default.gray(` Location: ${overridePath}`));
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
let content;
|
|
53
|
+
if (options.inherit) {
|
|
54
|
+
// Create file with import directive
|
|
55
|
+
content = `{{ import: ${registryPath} }}\n\n<!-- Add your custom content below -->\n`;
|
|
56
|
+
console.log(chalk_1.default.blue(`š Creating inherited override for: ${registryPath}`));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Fetch current content from remote
|
|
60
|
+
console.log(chalk_1.default.blue(`š„ Fetching current content from remote: ${registryPath}`));
|
|
61
|
+
try {
|
|
62
|
+
// Read config to get remote URL
|
|
63
|
+
let config = {};
|
|
64
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
65
|
+
config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
66
|
+
}
|
|
67
|
+
const remoteUrl = config.remoteUrl || 'https://fraim-registry.azurewebsites.net';
|
|
68
|
+
const apiKey = config.apiKey;
|
|
69
|
+
// Determine endpoint based on path
|
|
70
|
+
let endpoint;
|
|
71
|
+
if (registryPath.startsWith('workflows/')) {
|
|
72
|
+
const workflowName = registryPath.replace('workflows/', '').replace('.md', '');
|
|
73
|
+
endpoint = `${remoteUrl}/api/workflows/${workflowName}`;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
endpoint = `${remoteUrl}/api/files/${registryPath}`;
|
|
77
|
+
}
|
|
78
|
+
const headers = {};
|
|
79
|
+
if (apiKey) {
|
|
80
|
+
headers['x-api-key'] = apiKey;
|
|
81
|
+
}
|
|
82
|
+
const response = await axios_1.default.get(endpoint, {
|
|
83
|
+
headers,
|
|
84
|
+
timeout: 30000
|
|
85
|
+
});
|
|
86
|
+
// Extract content from response
|
|
87
|
+
if (response.data.content) {
|
|
88
|
+
content = response.data.content;
|
|
89
|
+
}
|
|
90
|
+
else if (typeof response.data === 'string') {
|
|
91
|
+
content = response.data;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new Error('Unexpected response format');
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk_1.default.green(`ā
Fetched ${content.length} bytes from remote`));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.log(chalk_1.default.red(`ā Failed to fetch content from remote: ${error.message}`));
|
|
100
|
+
console.log(chalk_1.default.gray(' Tip: Check your network connection and .fraim/config.json settings'));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Write override file
|
|
105
|
+
try {
|
|
106
|
+
fs_1.default.writeFileSync(overridePath, content, 'utf-8');
|
|
107
|
+
console.log(chalk_1.default.green(`ā
Created override: ${registryPath}`));
|
|
108
|
+
console.log(chalk_1.default.gray(` Location: ${overridePath}`));
|
|
109
|
+
console.log(chalk_1.default.gray(` Edit this file to customize for your project`));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.log(chalk_1.default.red(`ā Failed to write override file: ${error.message}`));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
});
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -12,6 +12,8 @@ const setup_1 = require("./commands/setup");
|
|
|
12
12
|
const init_project_1 = require("./commands/init-project");
|
|
13
13
|
const test_mcp_1 = require("./commands/test-mcp");
|
|
14
14
|
const add_ide_1 = require("./commands/add-ide");
|
|
15
|
+
const override_1 = require("./commands/override");
|
|
16
|
+
const list_overridable_1 = require("./commands/list-overridable");
|
|
15
17
|
const fs_1 = __importDefault(require("fs"));
|
|
16
18
|
const path_1 = __importDefault(require("path"));
|
|
17
19
|
const program = new commander_1.Command();
|
|
@@ -46,4 +48,6 @@ program.addCommand(setup_1.setupCommand);
|
|
|
46
48
|
program.addCommand(init_project_1.initProjectCommand);
|
|
47
49
|
program.addCommand(test_mcp_1.testMCPCommand);
|
|
48
50
|
program.addCommand(add_ide_1.addIDECommand);
|
|
51
|
+
program.addCommand(override_1.overrideCommand);
|
|
52
|
+
program.addCommand(list_overridable_1.listOverridableCommand);
|
|
49
53
|
program.parse(process.argv);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* InheritanceParser
|
|
4
|
+
*
|
|
5
|
+
* Parses and resolves {{ import: path }} directives in registry files,
|
|
6
|
+
* enabling local overrides to inherit from global registry files.
|
|
7
|
+
*
|
|
8
|
+
* Security features:
|
|
9
|
+
* - Path traversal protection (rejects .. and absolute paths)
|
|
10
|
+
* - Circular import detection
|
|
11
|
+
* - Max depth limit (5 levels)
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.InheritanceParser = exports.InheritanceError = void 0;
|
|
15
|
+
class InheritanceError extends Error {
|
|
16
|
+
constructor(message, path) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.path = path;
|
|
19
|
+
this.name = 'InheritanceError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.InheritanceError = InheritanceError;
|
|
23
|
+
/**
|
|
24
|
+
* Regular expression to match {{ import: path }} directives
|
|
25
|
+
*/
|
|
26
|
+
const IMPORT_REGEX = /\{\{\s*import:\s*([^\}]+)\s*\}\}/g;
|
|
27
|
+
class InheritanceParser {
|
|
28
|
+
constructor(maxDepth = 5) {
|
|
29
|
+
this.maxDepth = maxDepth;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract import directives from content without resolving them
|
|
33
|
+
*/
|
|
34
|
+
extractImports(content) {
|
|
35
|
+
const imports = [];
|
|
36
|
+
let match;
|
|
37
|
+
// Reset regex state
|
|
38
|
+
IMPORT_REGEX.lastIndex = 0;
|
|
39
|
+
while ((match = IMPORT_REGEX.exec(content)) !== null) {
|
|
40
|
+
imports.push(match[1].trim());
|
|
41
|
+
}
|
|
42
|
+
return imports;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sanitize and validate import path
|
|
46
|
+
*
|
|
47
|
+
* @throws {InheritanceError} If path is invalid
|
|
48
|
+
*/
|
|
49
|
+
sanitizePath(path) {
|
|
50
|
+
const trimmed = path.trim();
|
|
51
|
+
// Reject empty paths
|
|
52
|
+
if (!trimmed) {
|
|
53
|
+
throw new InheritanceError('Import path cannot be empty');
|
|
54
|
+
}
|
|
55
|
+
// Reject absolute paths (Unix and Windows)
|
|
56
|
+
if (trimmed.startsWith('/') || trimmed.match(/^[A-Za-z]:\\/)) {
|
|
57
|
+
throw new InheritanceError(`Absolute paths not allowed: ${trimmed}`, trimmed);
|
|
58
|
+
}
|
|
59
|
+
// Reject path traversal attempts
|
|
60
|
+
if (trimmed.includes('..')) {
|
|
61
|
+
throw new InheritanceError(`Path traversal not allowed: ${trimmed}`, trimmed);
|
|
62
|
+
}
|
|
63
|
+
return trimmed;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Detect circular imports
|
|
67
|
+
*
|
|
68
|
+
* Special case: If the import path is the same as the current path,
|
|
69
|
+
* it's not a circular import - it's importing the parent/remote version.
|
|
70
|
+
* This allows local overrides to inherit from their remote counterparts.
|
|
71
|
+
*
|
|
72
|
+
* @throws {InheritanceError} If circular import detected
|
|
73
|
+
*/
|
|
74
|
+
detectCircularImport(path, visited, isParentImport = false) {
|
|
75
|
+
// If this is a parent import (same path as current), allow it
|
|
76
|
+
if (isParentImport) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (visited.has(path)) {
|
|
80
|
+
throw new InheritanceError(`Circular import detected: ${path}`, path);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolve all import directives in content recursively
|
|
85
|
+
*
|
|
86
|
+
* @param content - Content with {{ import }} directives
|
|
87
|
+
* @param currentPath - Path of current file (for circular detection)
|
|
88
|
+
* @param options - Resolution options
|
|
89
|
+
* @returns Resolved content with all imports replaced
|
|
90
|
+
*
|
|
91
|
+
* @throws {InheritanceError} If circular import, path traversal, or max depth exceeded
|
|
92
|
+
*/
|
|
93
|
+
async resolve(content, currentPath, options) {
|
|
94
|
+
const depth = options.currentDepth || 0;
|
|
95
|
+
const visited = options.visited || new Set();
|
|
96
|
+
const maxDepth = options.maxDepth || this.maxDepth;
|
|
97
|
+
// Check depth limit
|
|
98
|
+
if (depth > maxDepth) {
|
|
99
|
+
throw new InheritanceError(`Max import depth exceeded (${maxDepth})`, currentPath);
|
|
100
|
+
}
|
|
101
|
+
// Check circular imports (but allow importing the same path as parent)
|
|
102
|
+
this.detectCircularImport(currentPath, visited, false);
|
|
103
|
+
visited.add(currentPath);
|
|
104
|
+
// Extract imports
|
|
105
|
+
const imports = this.extractImports(content);
|
|
106
|
+
if (imports.length === 0) {
|
|
107
|
+
return content;
|
|
108
|
+
}
|
|
109
|
+
// Resolve each import
|
|
110
|
+
let resolved = content;
|
|
111
|
+
for (const importPath of imports) {
|
|
112
|
+
// Sanitize path
|
|
113
|
+
const sanitized = this.sanitizePath(importPath);
|
|
114
|
+
// Check if this is a parent import (same path as current)
|
|
115
|
+
const isParentImport = sanitized === currentPath;
|
|
116
|
+
// Fetch parent content
|
|
117
|
+
let parentContent;
|
|
118
|
+
try {
|
|
119
|
+
parentContent = await options.fetchParent(sanitized);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw new InheritanceError(`Failed to fetch parent content: ${sanitized}. ${error.message}`, sanitized);
|
|
123
|
+
}
|
|
124
|
+
// Recursively resolve parent imports
|
|
125
|
+
// For parent imports, use a fresh visited set to allow the same path
|
|
126
|
+
const parentVisited = isParentImport ? new Set() : new Set(visited);
|
|
127
|
+
const resolvedParent = await this.resolve(parentContent, sanitized, {
|
|
128
|
+
...options,
|
|
129
|
+
currentDepth: depth + 1,
|
|
130
|
+
visited: parentVisited
|
|
131
|
+
});
|
|
132
|
+
// Replace import directive with resolved parent content
|
|
133
|
+
const importDirective = `{{ import: ${importPath} }}`;
|
|
134
|
+
resolved = resolved.replace(importDirective, resolvedParent);
|
|
135
|
+
}
|
|
136
|
+
return resolved;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Parse content and return detailed information about imports
|
|
140
|
+
*/
|
|
141
|
+
parse(content) {
|
|
142
|
+
const imports = this.extractImports(content);
|
|
143
|
+
return {
|
|
144
|
+
content,
|
|
145
|
+
imports,
|
|
146
|
+
hasImports: imports.length > 0
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.InheritanceParser = InheritanceParser;
|
|
@@ -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.72",
|
|
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
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.initCommand = exports.runInit = void 0;
|
|
7
|
-
const commander_1 = require("commander");
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const first_run_1 = require("../setup/first-run");
|
|
12
|
-
const sync_1 = require("./sync");
|
|
13
|
-
const platform_detection_1 = require("../../utils/platform-detection");
|
|
14
|
-
const version_utils_1 = require("../../utils/version-utils");
|
|
15
|
-
const script_sync_utils_1 = require("../../utils/script-sync-utils");
|
|
16
|
-
const runInit = async (options = {}) => {
|
|
17
|
-
const projectRoot = process.cwd();
|
|
18
|
-
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
19
|
-
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
20
|
-
console.log(chalk_1.default.blue('š Initializing FRAIM...'));
|
|
21
|
-
if (!fs_1.default.existsSync(fraimDir)) {
|
|
22
|
-
fs_1.default.mkdirSync(fraimDir, { recursive: true });
|
|
23
|
-
console.log(chalk_1.default.green('ā
Created .fraim directory'));
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
console.log(chalk_1.default.yellow('ā¹ļø .fraim directory already exists'));
|
|
27
|
-
}
|
|
28
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
29
|
-
let config;
|
|
30
|
-
// Try to detect platform from git remote
|
|
31
|
-
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
32
|
-
if (options.skipPlatform || detection.provider === 'unknown' || !detection.repository) {
|
|
33
|
-
// Conversational mode - no platform integration
|
|
34
|
-
if (!options.skipPlatform && (detection.provider === 'unknown' || !detection.repository)) {
|
|
35
|
-
console.log(chalk_1.default.yellow('\nā¹ļø No git remote found or unsupported platform.'));
|
|
36
|
-
console.log(chalk_1.default.blue(' Initializing in conversational mode (no platform integration).'));
|
|
37
|
-
console.log(chalk_1.default.gray(' You can still use FRAIM workflows and AI features.'));
|
|
38
|
-
console.log(chalk_1.default.gray(' Platform features (issues, PRs) will be unavailable.\n'));
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
console.log(chalk_1.default.blue('\n Initializing in conversational mode (platform integration skipped).\n'));
|
|
42
|
-
}
|
|
43
|
-
// Get project name from directory or git
|
|
44
|
-
let projectName = path_1.default.basename(projectRoot);
|
|
45
|
-
try {
|
|
46
|
-
const { execSync } = require('child_process');
|
|
47
|
-
const gitName = execSync('git config --get remote.origin.url', {
|
|
48
|
-
stdio: 'pipe',
|
|
49
|
-
timeout: 2000
|
|
50
|
-
}).toString().trim();
|
|
51
|
-
const match = gitName.match(/\/([^\/]+?)(\.git)?$/);
|
|
52
|
-
if (match) {
|
|
53
|
-
projectName = match[1];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
// Use directory name as fallback
|
|
58
|
-
}
|
|
59
|
-
config = {
|
|
60
|
-
version: (0, version_utils_1.getFraimVersion)(),
|
|
61
|
-
project: {
|
|
62
|
-
name: projectName
|
|
63
|
-
},
|
|
64
|
-
customizations: {
|
|
65
|
-
workflowsPath: '.fraim/workflows'
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
console.log(chalk_1.default.gray(` Project: ${projectName}`));
|
|
69
|
-
console.log(chalk_1.default.gray(` Mode: Conversational (no platform integration)`));
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
// Integrated mode - use platform
|
|
73
|
-
if (!detection.repository) {
|
|
74
|
-
console.log(chalk_1.default.red('ā Error: Repository information not available'));
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
// Validate the detected repository config
|
|
78
|
-
const validation = (0, platform_detection_1.validateRepositoryConfig)(detection.repository);
|
|
79
|
-
if (!validation.valid) {
|
|
80
|
-
console.log(chalk_1.default.red('ā Error: Invalid repository configuration:'));
|
|
81
|
-
validation.errors.forEach(err => console.log(chalk_1.default.red(` - ${err}`)));
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
console.log(chalk_1.default.blue('\n Initializing in integrated mode (platform integration enabled).'));
|
|
85
|
-
console.log(chalk_1.default.gray(` Platform: ${detection.provider.toUpperCase()}`));
|
|
86
|
-
if (detection.provider === 'github') {
|
|
87
|
-
console.log(chalk_1.default.gray(` Repository: ${detection.repository.owner}/${detection.repository.name}`));
|
|
88
|
-
}
|
|
89
|
-
else if (detection.provider === 'ado') {
|
|
90
|
-
console.log(chalk_1.default.gray(` Organization: ${detection.repository.organization}`));
|
|
91
|
-
console.log(chalk_1.default.gray(` Project: ${detection.repository.project}`));
|
|
92
|
-
console.log(chalk_1.default.gray(` Repository: ${detection.repository.name}`));
|
|
93
|
-
}
|
|
94
|
-
console.log();
|
|
95
|
-
config = {
|
|
96
|
-
version: (0, version_utils_1.getFraimVersion)(),
|
|
97
|
-
project: {
|
|
98
|
-
name: detection.repository.name
|
|
99
|
-
},
|
|
100
|
-
repository: detection.repository,
|
|
101
|
-
customizations: {
|
|
102
|
-
workflowsPath: '.fraim/workflows'
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
107
|
-
console.log(chalk_1.default.green('ā
Created .fraim/config.json'));
|
|
108
|
-
}
|
|
109
|
-
// Create subdirectories
|
|
110
|
-
['workflows'].forEach(dir => {
|
|
111
|
-
const dirPath = path_1.default.join(fraimDir, dir);
|
|
112
|
-
if (!fs_1.default.existsSync(dirPath)) {
|
|
113
|
-
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
114
|
-
console.log(chalk_1.default.green(`ā
Created .fraim/${dir}`));
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
console.log(chalk_1.default.blue('\nš FRAIM initialized successfully!'));
|
|
118
|
-
// Sync workflows from registry
|
|
119
|
-
await (0, sync_1.runSync)({});
|
|
120
|
-
// Sync scripts to user directory
|
|
121
|
-
console.log(chalk_1.default.blue('š Syncing FRAIM scripts to user directory...'));
|
|
122
|
-
// Find registry path (same logic as sync command)
|
|
123
|
-
let registryPath = path_1.default.join(__dirname, '../../../../registry');
|
|
124
|
-
if (!fs_1.default.existsSync(registryPath)) {
|
|
125
|
-
registryPath = path_1.default.join(__dirname, '../../../registry');
|
|
126
|
-
}
|
|
127
|
-
if (!fs_1.default.existsSync(registryPath)) {
|
|
128
|
-
registryPath = path_1.default.join(projectRoot, 'registry');
|
|
129
|
-
}
|
|
130
|
-
if (fs_1.default.existsSync(registryPath)) {
|
|
131
|
-
const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
|
|
132
|
-
console.log(chalk_1.default.green(`ā
Synced ${syncResult.synced} self-contained scripts to user directory.`));
|
|
133
|
-
if (syncResult.ephemeral > 0) {
|
|
134
|
-
console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
console.log(chalk_1.default.yellow('ā ļø Registry not found, skipping script sync.'));
|
|
139
|
-
}
|
|
140
|
-
// Trigger First Run Experience
|
|
141
|
-
await (0, first_run_1.runFirstRunExperience)();
|
|
142
|
-
process.exit(0);
|
|
143
|
-
};
|
|
144
|
-
exports.runInit = runInit;
|
|
145
|
-
exports.initCommand = new commander_1.Command('init')
|
|
146
|
-
.description('Initialize FRAIM in the current project')
|
|
147
|
-
.option('--skip-platform', 'Skip platform integration (conversational mode only)')
|
|
148
|
-
.action((options) => (0, exports.runInit)(options));
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.mcpCommand = void 0;
|
|
7
|
-
const commander_1 = require("commander");
|
|
8
|
-
const server_1 = require("../../local-mcp-server/server");
|
|
9
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
-
exports.mcpCommand = new commander_1.Command('mcp')
|
|
11
|
-
.description('Start the local FRAIM MCP server')
|
|
12
|
-
.option('-p, --port <port>', 'Port to run the server on', '3003')
|
|
13
|
-
.option('--remote-only', 'Use remote server only (skip local proxy)', false)
|
|
14
|
-
.action(async (options) => {
|
|
15
|
-
try {
|
|
16
|
-
if (options.remoteOnly) {
|
|
17
|
-
console.log(chalk_1.default.yellow('ā ļø Remote-only mode not yet implemented'));
|
|
18
|
-
console.log(chalk_1.default.blue('ā¹ļø Starting local MCP server instead...'));
|
|
19
|
-
}
|
|
20
|
-
const port = parseInt(options.port);
|
|
21
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
22
|
-
console.error(chalk_1.default.red('ā Invalid port number. Must be between 1 and 65535.'));
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
console.log(chalk_1.default.blue('š Starting FRAIM Local MCP Server...'));
|
|
26
|
-
console.log(chalk_1.default.gray(`š” Port: ${port}`));
|
|
27
|
-
console.log(chalk_1.default.gray(`š Auto-updates: Every 5 minutes`));
|
|
28
|
-
console.log(chalk_1.default.gray(`š Cache: ~/.fraim/cache`));
|
|
29
|
-
console.log('');
|
|
30
|
-
// Set the port in environment
|
|
31
|
-
process.env.FRAIM_LOCAL_MCP_PORT = port.toString();
|
|
32
|
-
// Start the server
|
|
33
|
-
await (0, server_1.startLocalMCPServer)();
|
|
34
|
-
// Server is now running, show configuration instructions
|
|
35
|
-
console.log('');
|
|
36
|
-
console.log(chalk_1.default.green('ā
FRAIM Local MCP Server is running!'));
|
|
37
|
-
console.log('');
|
|
38
|
-
console.log(chalk_1.default.bold('š Agent Configuration:'));
|
|
39
|
-
console.log('');
|
|
40
|
-
console.log(chalk_1.default.gray('Add this to your agent\'s MCP configuration:'));
|
|
41
|
-
console.log('');
|
|
42
|
-
console.log(chalk_1.default.cyan(JSON.stringify({
|
|
43
|
-
"mcpServers": {
|
|
44
|
-
"fraim": {
|
|
45
|
-
"command": "fraim",
|
|
46
|
-
"args": ["mcp"],
|
|
47
|
-
"env": {}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}, null, 2)));
|
|
51
|
-
console.log('');
|
|
52
|
-
console.log(chalk_1.default.bold('š Endpoints:'));
|
|
53
|
-
console.log(chalk_1.default.gray(` MCP: http://localhost:${port}/mcp`));
|
|
54
|
-
console.log(chalk_1.default.gray(` Health: http://localhost:${port}/health`));
|
|
55
|
-
console.log('');
|
|
56
|
-
console.log(chalk_1.default.bold('š To stop the server:'));
|
|
57
|
-
console.log(chalk_1.default.gray(' Press Ctrl+C'));
|
|
58
|
-
console.log('');
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
console.error(chalk_1.default.red('ā Failed to start Local MCP Server:'));
|
|
62
|
-
console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.wizardCommand = void 0;
|
|
7
|
-
const commander_1 = require("commander");
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const init_js_1 = require("./init.js");
|
|
10
|
-
const sync_js_1 = require("./sync.js");
|
|
11
|
-
exports.wizardCommand = new commander_1.Command('wizard')
|
|
12
|
-
.description('Run the interactive setup wizard')
|
|
13
|
-
.action(async () => {
|
|
14
|
-
console.log(chalk_1.default.cyan('š® FRAIM Interactive Setup Wizard\n'));
|
|
15
|
-
console.log('Running V2 setup (Init + Sync)...\n');
|
|
16
|
-
try {
|
|
17
|
-
// Run initialization
|
|
18
|
-
try {
|
|
19
|
-
await (0, init_js_1.runInit)();
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
console.log(chalk_1.default.yellow('ā ļø Init notice:'), err.message);
|
|
23
|
-
}
|
|
24
|
-
console.log(''); // newline
|
|
25
|
-
// Run sync (Force not strictly required if init is fresh, but safe)
|
|
26
|
-
// But if user runs wizard on existing project, maybe they want to resync
|
|
27
|
-
await (0, sync_js_1.runSync)({ force: false });
|
|
28
|
-
console.log(chalk_1.default.blue('\n⨠Wizard completed successfully!'));
|
|
29
|
-
console.log(chalk_1.default.gray('Tip: In the future, you can run `fraim sync` to update workflows.'));
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
console.error(chalk_1.default.red('ā Wizard failed:'), error);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
});
|