fraim 2.0.100

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.
Files changed (70) hide show
  1. package/README.md +445 -0
  2. package/bin/fraim.js +23 -0
  3. package/dist/src/cli/api/get-provider-client.js +41 -0
  4. package/dist/src/cli/api/provider-client.js +107 -0
  5. package/dist/src/cli/commands/add-ide.js +430 -0
  6. package/dist/src/cli/commands/add-provider.js +233 -0
  7. package/dist/src/cli/commands/doctor.js +149 -0
  8. package/dist/src/cli/commands/init-project.js +301 -0
  9. package/dist/src/cli/commands/list-overridable.js +184 -0
  10. package/dist/src/cli/commands/list.js +57 -0
  11. package/dist/src/cli/commands/login.js +84 -0
  12. package/dist/src/cli/commands/mcp.js +15 -0
  13. package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
  14. package/dist/src/cli/commands/override.js +177 -0
  15. package/dist/src/cli/commands/setup.js +651 -0
  16. package/dist/src/cli/commands/sync.js +162 -0
  17. package/dist/src/cli/commands/test-mcp.js +171 -0
  18. package/dist/src/cli/doctor/check-runner.js +199 -0
  19. package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
  20. package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
  21. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
  22. package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
  23. package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
  24. package/dist/src/cli/doctor/checks/workflow-checks.js +251 -0
  25. package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
  26. package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
  27. package/dist/src/cli/doctor/types.js +6 -0
  28. package/dist/src/cli/fraim.js +100 -0
  29. package/dist/src/cli/internal/device-flow-service.js +83 -0
  30. package/dist/src/cli/mcp/ide-formats.js +243 -0
  31. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  32. package/dist/src/cli/mcp/mcp-server-registry.js +160 -0
  33. package/dist/src/cli/mcp/types.js +3 -0
  34. package/dist/src/cli/providers/local-provider-registry.js +166 -0
  35. package/dist/src/cli/providers/provider-registry.js +230 -0
  36. package/dist/src/cli/setup/auto-mcp-setup.js +331 -0
  37. package/dist/src/cli/setup/codex-local-config.js +37 -0
  38. package/dist/src/cli/setup/first-run.js +242 -0
  39. package/dist/src/cli/setup/ide-detector.js +179 -0
  40. package/dist/src/cli/setup/mcp-config-generator.js +192 -0
  41. package/dist/src/cli/setup/provider-prompts.js +339 -0
  42. package/dist/src/cli/utils/agent-adapters.js +126 -0
  43. package/dist/src/cli/utils/digest-utils.js +47 -0
  44. package/dist/src/cli/utils/fraim-gitignore.js +40 -0
  45. package/dist/src/cli/utils/platform-detection.js +258 -0
  46. package/dist/src/cli/utils/project-bootstrap.js +93 -0
  47. package/dist/src/cli/utils/remote-sync.js +315 -0
  48. package/dist/src/cli/utils/script-sync-utils.js +221 -0
  49. package/dist/src/cli/utils/version-utils.js +32 -0
  50. package/dist/src/core/ai-mentor.js +230 -0
  51. package/dist/src/core/config-loader.js +114 -0
  52. package/dist/src/core/config-writer.js +75 -0
  53. package/dist/src/core/types.js +23 -0
  54. package/dist/src/core/utils/git-utils.js +95 -0
  55. package/dist/src/core/utils/include-resolver.js +92 -0
  56. package/dist/src/core/utils/inheritance-parser.js +288 -0
  57. package/dist/src/core/utils/job-parser.js +176 -0
  58. package/dist/src/core/utils/local-registry-resolver.js +616 -0
  59. package/dist/src/core/utils/object-utils.js +11 -0
  60. package/dist/src/core/utils/project-fraim-migration.js +103 -0
  61. package/dist/src/core/utils/project-fraim-paths.js +38 -0
  62. package/dist/src/core/utils/provider-utils.js +18 -0
  63. package/dist/src/core/utils/server-startup.js +34 -0
  64. package/dist/src/core/utils/stub-generator.js +147 -0
  65. package/dist/src/core/utils/workflow-parser.js +174 -0
  66. package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
  67. package/dist/src/local-mcp-server/stdio-server.js +1698 -0
  68. package/dist/src/local-mcp-server/usage-collector.js +264 -0
  69. package/index.js +85 -0
  70. package/package.json +139 -0
@@ -0,0 +1,221 @@
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.getUserFraimDir = getUserFraimDir;
7
+ exports.getUserScriptsDir = getUserScriptsDir;
8
+ exports.ensureUserFraimDirectories = ensureUserFraimDirectories;
9
+ exports.getRegistryScripts = getRegistryScripts;
10
+ exports.copyScriptToUserDirectory = copyScriptToUserDirectory;
11
+ exports.syncScriptsToUserDirectory = syncScriptsToUserDirectory;
12
+ exports.cleanupObsoleteUserScripts = cleanupObsoleteUserScripts;
13
+ exports.getUserScriptPath = getUserScriptPath;
14
+ exports.userScriptExists = userScriptExists;
15
+ exports.validateUserScripts = validateUserScripts;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const os_1 = __importDefault(require("os"));
19
+ const chalk_1 = __importDefault(require("chalk"));
20
+ /**
21
+ * Get the user-level FRAIM directory path
22
+ * Can be overridden with FRAIM_USER_DIR environment variable for testing
23
+ */
24
+ function getUserFraimDir() {
25
+ return process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
26
+ }
27
+ /**
28
+ * Get the user-level scripts directory path
29
+ */
30
+ function getUserScriptsDir() {
31
+ return path_1.default.join(getUserFraimDir(), 'scripts');
32
+ }
33
+ /**
34
+ * Ensure the user-level FRAIM directories exist and initialize user config
35
+ */
36
+ function ensureUserFraimDirectories() {
37
+ const userFraimDir = getUserFraimDir();
38
+ const userScriptsDir = getUserScriptsDir();
39
+ if (!fs_1.default.existsSync(userFraimDir)) {
40
+ fs_1.default.mkdirSync(userFraimDir, { recursive: true });
41
+ }
42
+ if (!fs_1.default.existsSync(userScriptsDir)) {
43
+ fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
44
+ }
45
+ // Initialize user config (~/.fraim/config.json) if it doesn't exist
46
+ const userConfigPath = path_1.default.join(userFraimDir, 'config.json');
47
+ if (!fs_1.default.existsSync(userConfigPath)) {
48
+ fs_1.default.writeFileSync(userConfigPath, JSON.stringify({ workingStyle: 'PR' }, null, 2));
49
+ }
50
+ }
51
+ /**
52
+ * Get all script files from the registry (recursively)
53
+ */
54
+ function getRegistryScripts(registryPath) {
55
+ const scriptsPath = path_1.default.join(registryPath, 'scripts');
56
+ if (!fs_1.default.existsSync(scriptsPath)) {
57
+ return [];
58
+ }
59
+ const getAllFiles = (dir, baseDir = dir) => {
60
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
61
+ let files = [];
62
+ for (const entry of entries) {
63
+ const fullPath = path_1.default.join(dir, entry.name);
64
+ if (entry.isDirectory()) {
65
+ // Recursively get files from subdirectories
66
+ files = files.concat(getAllFiles(fullPath, baseDir));
67
+ }
68
+ else {
69
+ // Include all files (not just specific extensions)
70
+ files.push(fullPath);
71
+ }
72
+ }
73
+ return files;
74
+ };
75
+ return getAllFiles(scriptsPath);
76
+ }
77
+ /**
78
+ * Copy a script file to the user scripts directory with proper permissions
79
+ * Preserves directory structure from registry/scripts/
80
+ */
81
+ function copyScriptToUserDirectory(sourcePath, registryScriptsPath) {
82
+ const userScriptsDir = getUserScriptsDir();
83
+ // Calculate relative path from registry/scripts to preserve structure
84
+ const relativePath = path_1.default.relative(registryScriptsPath, sourcePath);
85
+ const targetPath = path_1.default.join(userScriptsDir, relativePath);
86
+ // Ensure target directory exists
87
+ const targetDir = path_1.default.dirname(targetPath);
88
+ if (!fs_1.default.existsSync(targetDir)) {
89
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
90
+ }
91
+ // Copy the file
92
+ fs_1.default.copyFileSync(sourcePath, targetPath);
93
+ // Set executable permissions on Unix systems for shell scripts
94
+ if (process.platform !== 'win32' && sourcePath.endsWith('.sh')) {
95
+ try {
96
+ fs_1.default.chmodSync(targetPath, 0o755);
97
+ }
98
+ catch (error) {
99
+ console.warn(chalk_1.default.yellow(`⚠️ Could not set executable permissions for ${relativePath}`));
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * Sync all scripts from registry to user directory
105
+ */
106
+ function syncScriptsToUserDirectory(registryPath) {
107
+ ensureUserFraimDirectories();
108
+ const registryScriptsPath = path_1.default.join(registryPath, 'scripts');
109
+ const allScripts = getRegistryScripts(registryPath);
110
+ let syncedCount = 0;
111
+ console.log(chalk_1.default.gray(` Syncing all scripts to ~/.fraim/scripts/:`));
112
+ for (const scriptPath of allScripts) {
113
+ const relativePath = path_1.default.relative(registryScriptsPath, scriptPath);
114
+ const userScriptPath = path_1.default.join(getUserScriptsDir(), relativePath);
115
+ try {
116
+ // Check if script needs updating
117
+ let needsUpdate = true;
118
+ if (fs_1.default.existsSync(userScriptPath)) {
119
+ const registryContent = fs_1.default.readFileSync(scriptPath, 'utf-8');
120
+ const userContent = fs_1.default.readFileSync(userScriptPath, 'utf-8');
121
+ needsUpdate = registryContent !== userContent;
122
+ }
123
+ if (needsUpdate) {
124
+ copyScriptToUserDirectory(scriptPath, registryScriptsPath);
125
+ syncedCount++;
126
+ console.log(chalk_1.default.gray(` + ${relativePath}`));
127
+ }
128
+ else {
129
+ console.log(chalk_1.default.gray(` = ${relativePath} (up to date)`));
130
+ }
131
+ }
132
+ catch (error) {
133
+ console.warn(chalk_1.default.yellow(`⚠️ Could not sync script ${relativePath}: ${error}`));
134
+ }
135
+ }
136
+ return { synced: syncedCount, ephemeral: 0 };
137
+ }
138
+ /**
139
+ * Clean up scripts in user directory that no longer exist in registry
140
+ */
141
+ function cleanupObsoleteUserScripts(registryPath) {
142
+ const userScriptsDir = getUserScriptsDir();
143
+ if (!fs_1.default.existsSync(userScriptsDir)) {
144
+ return 0;
145
+ }
146
+ const registryScriptsPath = path_1.default.join(registryPath, 'scripts');
147
+ const registryScripts = getRegistryScripts(registryPath);
148
+ const registryRelativePaths = registryScripts.map(scriptPath => path_1.default.relative(registryScriptsPath, scriptPath));
149
+ // Get all files in user scripts directory recursively
150
+ const getAllUserFiles = (dir, baseDir = dir) => {
151
+ if (!fs_1.default.existsSync(dir))
152
+ return [];
153
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
154
+ let files = [];
155
+ for (const entry of entries) {
156
+ const fullPath = path_1.default.join(dir, entry.name);
157
+ if (entry.isDirectory()) {
158
+ files = files.concat(getAllUserFiles(fullPath, baseDir));
159
+ }
160
+ else {
161
+ files.push(path_1.default.relative(baseDir, fullPath));
162
+ }
163
+ }
164
+ return files;
165
+ };
166
+ const userFiles = getAllUserFiles(userScriptsDir);
167
+ let cleanedCount = 0;
168
+ for (const userFile of userFiles) {
169
+ // Normalize path separators for comparison
170
+ const normalizedUserFile = userFile.replace(/\\/g, '/');
171
+ const normalizedRegistry = registryRelativePaths.map(p => p.replace(/\\/g, '/'));
172
+ if (!normalizedRegistry.includes(normalizedUserFile)) {
173
+ try {
174
+ const userFilePath = path_1.default.join(userScriptsDir, userFile);
175
+ fs_1.default.unlinkSync(userFilePath);
176
+ cleanedCount++;
177
+ console.log(chalk_1.default.yellow(` - ${userFile} (removed from registry)`));
178
+ // Cleanup empty directories
179
+ try {
180
+ const dir = path_1.default.dirname(userFilePath);
181
+ if (fs_1.default.existsSync(dir) && fs_1.default.readdirSync(dir).length === 0) {
182
+ fs_1.default.rmdirSync(dir);
183
+ }
184
+ }
185
+ catch (e) { }
186
+ }
187
+ catch (error) {
188
+ console.warn(chalk_1.default.yellow(`⚠️ Could not remove obsolete script ${userFile}: ${error}`));
189
+ }
190
+ }
191
+ }
192
+ return cleanedCount;
193
+ }
194
+ /**
195
+ * Get the path to a script in the user directory
196
+ */
197
+ function getUserScriptPath(scriptName) {
198
+ return path_1.default.join(getUserScriptsDir(), scriptName);
199
+ }
200
+ /**
201
+ * Check if a script exists in the user directory
202
+ */
203
+ function userScriptExists(scriptName) {
204
+ return fs_1.default.existsSync(getUserScriptPath(scriptName));
205
+ }
206
+ /**
207
+ * Validate that all expected scripts are present in user directory
208
+ */
209
+ function validateUserScripts(expectedScripts) {
210
+ const missing = [];
211
+ const present = [];
212
+ for (const scriptName of expectedScripts) {
213
+ if (userScriptExists(scriptName)) {
214
+ present.push(scriptName);
215
+ }
216
+ else {
217
+ missing.push(scriptName);
218
+ }
219
+ }
220
+ return { missing, present };
221
+ }
@@ -0,0 +1,32 @@
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.getFraimVersion = getFraimVersion;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function getFraimVersion() {
10
+ // Try reliable paths to find package.json relative to this file
11
+ // locally: src/cli/utils/version-utils.ts -> package.json is ../../../package.json
12
+ // dist: dist/src/cli/utils/version-utils.js -> package.json is ../../../../package.json
13
+ const possiblePaths = [
14
+ path_1.default.join(__dirname, '../../../package.json'), // Local dev (src)
15
+ path_1.default.join(__dirname, '../../../../package.json'), // Dist (dist/src)
16
+ path_1.default.join(process.cwd(), 'package.json') // Fallback to CWD
17
+ ];
18
+ for (const pkgPath of possiblePaths) {
19
+ if (fs_1.default.existsSync(pkgPath)) {
20
+ try {
21
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
22
+ if (pkg.name === 'fraim-framework') {
23
+ return pkg.version;
24
+ }
25
+ }
26
+ catch (e) {
27
+ // Ignore parsing errors
28
+ }
29
+ }
30
+ }
31
+ return '1.0.0'; // Fallback
32
+ }
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AIMentor = void 0;
4
+ const include_resolver_1 = require("./utils/include-resolver");
5
+ class AIMentor {
6
+ constructor(resolver) {
7
+ this.jobCache = new Map();
8
+ this.resolver = resolver;
9
+ }
10
+ /**
11
+ * Handle mentoring/coaching request from agent
12
+ */
13
+ async handleMentoringRequest(args) {
14
+ const workflow = await this.getOrLoadJob(args.jobName);
15
+ if (!workflow) {
16
+ throw new Error(`Job "${args.jobName}" not found or invalid.`);
17
+ }
18
+ const validStatus = ['starting', 'complete', 'incomplete', 'failure'].includes(args.status);
19
+ if (!validStatus) {
20
+ throw new Error(`Invalid status: ${args.status}. Must be one of: starting, complete, incomplete, failure.`);
21
+ }
22
+ // For simple workflows, skip phase validation
23
+ if (!workflow.isSimple) {
24
+ const phases = workflow.metadata.phases || {};
25
+ const hasMetadata = !!phases[args.currentPhase];
26
+ const hasMarkdown = workflow.phases.has(args.currentPhase);
27
+ if (!hasMetadata && !hasMarkdown && args.currentPhase !== 'starting') {
28
+ throw new Error(`Phase "${args.currentPhase}" not found in job "${args.jobName}".`);
29
+ }
30
+ }
31
+ // Handle different statuses
32
+ if (args.status === 'starting') {
33
+ return await this.generateStartingMessage(workflow, args.currentPhase, args.skipIncludes);
34
+ }
35
+ else if (args.status === 'complete') {
36
+ return await this.generateCompletionMessage(workflow, args.currentPhase, args.findings, args.evidence, args.skipIncludes);
37
+ }
38
+ else {
39
+ return await this.generateHelpMessage(workflow, args.currentPhase, args.status, args.skipIncludes);
40
+ }
41
+ }
42
+ async getOrLoadJob(jobType) {
43
+ if (this.jobCache.has(jobType)) {
44
+ return this.jobCache.get(jobType);
45
+ }
46
+ const job = await this.resolver.getJob(jobType);
47
+ if (job) {
48
+ this.jobCache.set(jobType, job);
49
+ return job;
50
+ }
51
+ return null;
52
+ }
53
+ async resolveIncludes(content, basePath) {
54
+ return (0, include_resolver_1.resolveIncludes)(content, this.resolver, basePath);
55
+ }
56
+ assertNoUnresolvedIncludes(content, context) {
57
+ const matches = content.match(/\{\{include:[^}]+\}\}/g);
58
+ if (!matches || matches.length === 0)
59
+ return;
60
+ const unique = Array.from(new Set(matches));
61
+ throw new Error(`Unresolved include directives in ${context}: ${unique.join(', ')}`);
62
+ }
63
+ buildReportBackFooter(jobName, phaseId, phaseFlow) {
64
+ if (!phaseFlow)
65
+ return '';
66
+ const base = `seekMentoring({
67
+ jobName: "${jobName}",
68
+ issueNumber: "<issue_number>",
69
+ currentPhase: "${phaseId}",
70
+ status: "complete",`;
71
+ const onSuccess = phaseFlow.onSuccess;
72
+ let successBlock;
73
+ if (!onSuccess) {
74
+ successBlock = `\n\n---\n\n> **⚑ Phase Complete — Report Back**\n> When you have finished all steps above, call:\n> \`\`\`javascript\n> ${base}\n> // This is the final phase.\n> })\n> \`\`\``;
75
+ }
76
+ else if (typeof onSuccess === 'string') {
77
+ successBlock = `\n\n---\n\n> **⚑ Phase Complete — Report Back**\n> When you have finished all steps above, call:\n> \`\`\`javascript\n> ${base}\n> })\n> \`\`\``;
78
+ }
79
+ else {
80
+ const validOutcomes = Object.keys(onSuccess)
81
+ .filter(k => k !== 'default')
82
+ .map(k => `"${k}"`)
83
+ .join(' | ');
84
+ successBlock = `\n\n---\n\n> **⚑ Phase Complete — Report Back**\n> The next phase depends on your outcome. Set \`findings.issueType\` (or \`findings.phaseOutcome\`) to one of: ${validOutcomes}\n>\n> Then call:\n> \`\`\`javascript\n> ${base}\n> findings: { issueType: "<Outcome>" }\n> })\n> \`\`\``;
85
+ }
86
+ return successBlock;
87
+ }
88
+ /** Phase-authority content injected for all phased workflows. Loaded from orchestration/phase-authority.md. */
89
+ async getPhaseAuthorityContent() {
90
+ try {
91
+ const content = await this.resolver.getFile('orchestration/phase-authority.md');
92
+ return content?.trim() || '';
93
+ }
94
+ catch {
95
+ return '';
96
+ }
97
+ }
98
+ async prependPhaseAuthority(message, isPhased) {
99
+ if (!isPhased)
100
+ return message;
101
+ const block = await this.getPhaseAuthorityContent();
102
+ if (!block)
103
+ return message;
104
+ return `${block}\n\n---\n\n${message}`;
105
+ }
106
+ async generateStartingMessage(workflow, phaseId, skipIncludes) {
107
+ const entityType = 'Job';
108
+ if (workflow.isSimple) {
109
+ const message = `🚀 **Starting ${entityType}: ${workflow.metadata.name}**\n\n${workflow.overview}`;
110
+ this.assertNoUnresolvedIncludes(message, `${workflow.metadata.name} (starting)`);
111
+ return {
112
+ message,
113
+ nextPhase: null,
114
+ status: 'starting'
115
+ };
116
+ }
117
+ const isVeryFirstCall = phaseId === 'starting';
118
+ const targetPhase = isVeryFirstCall ? (workflow.metadata.initialPhase || 'starting') : phaseId;
119
+ let message = '';
120
+ if (isVeryFirstCall && workflow.overview) {
121
+ message += `${workflow.overview}\n\n---\n\n`;
122
+ }
123
+ let instructions = workflow.phases.get(targetPhase);
124
+ if (instructions) {
125
+ instructions = skipIncludes ? instructions : await this.resolveIncludes(instructions, workflow.path);
126
+ message += `${instructions}`;
127
+ }
128
+ else {
129
+ message += `⚠️ No specific instructions found for phase: ${targetPhase}`;
130
+ }
131
+ const phaseFlow = workflow.metadata.phases?.[targetPhase];
132
+ message += this.buildReportBackFooter(workflow.metadata.name, targetPhase, phaseFlow);
133
+ if (!skipIncludes) {
134
+ this.assertNoUnresolvedIncludes(message, `${workflow.metadata.name}:${targetPhase} (starting)`);
135
+ }
136
+ return {
137
+ message: await this.prependPhaseAuthority(message, true),
138
+ nextPhase: targetPhase,
139
+ status: 'starting'
140
+ };
141
+ }
142
+ async generateCompletionMessage(workflow, phaseId, findings, evidence, skipIncludes) {
143
+ const entityType = 'Job';
144
+ if (workflow.isSimple) {
145
+ const message = `✅ **${entityType} Complete: ${workflow.metadata.name}**\n\n🎉 Great work! You have completed the ${workflow.metadata.name} ${entityType.toLowerCase()}.`;
146
+ this.assertNoUnresolvedIncludes(message, `${workflow.metadata.name} (complete)`);
147
+ return {
148
+ message,
149
+ nextPhase: null,
150
+ status: 'complete'
151
+ };
152
+ }
153
+ const phaseFlow = workflow.metadata.phases?.[phaseId];
154
+ let nextPhaseId = null;
155
+ if (phaseFlow && phaseFlow.onSuccess) {
156
+ if (typeof phaseFlow.onSuccess === 'string') {
157
+ nextPhaseId = phaseFlow.onSuccess;
158
+ }
159
+ else {
160
+ const outcome = findings?.phaseOutcome ?? findings?.issueType ?? evidence?.issueType ?? evidence?.phaseOutcome ?? 'default';
161
+ nextPhaseId = phaseFlow.onSuccess[outcome] ?? phaseFlow.onSuccess['default'] ?? null;
162
+ }
163
+ }
164
+ let message = '';
165
+ if (nextPhaseId) {
166
+ message += `Great work. Moving to the next phase: **${nextPhaseId}**.\n\n`;
167
+ let nextInstructions = workflow.phases.get(nextPhaseId);
168
+ if (nextInstructions) {
169
+ nextInstructions = skipIncludes ? nextInstructions : await this.resolveIncludes(nextInstructions, workflow.path);
170
+ message += nextInstructions;
171
+ }
172
+ const nextPhaseFlow = workflow.metadata.phases?.[nextPhaseId];
173
+ message += this.buildReportBackFooter(workflow.metadata.name, nextPhaseId, nextPhaseFlow);
174
+ }
175
+ else {
176
+ message += `🎉 **${entityType} Accomplished!** You have completed all phases of the ${workflow.metadata.name} ${entityType.toLowerCase()}.`;
177
+ }
178
+ if (!skipIncludes) {
179
+ this.assertNoUnresolvedIncludes(message, `${workflow.metadata.name}:${phaseId} (complete)`);
180
+ }
181
+ return {
182
+ message: await this.prependPhaseAuthority(message, true),
183
+ nextPhase: nextPhaseId,
184
+ status: 'complete'
185
+ };
186
+ }
187
+ async generateHelpMessage(workflow, phaseId, status, skipIncludes) {
188
+ const entityType = 'Job';
189
+ if (workflow.isSimple) {
190
+ const message = `**${entityType}: ${workflow.metadata.name}**\n\n${workflow.overview}`;
191
+ this.assertNoUnresolvedIncludes(message, `${workflow.metadata.name} (${status})`);
192
+ return {
193
+ message,
194
+ nextPhase: null,
195
+ status
196
+ };
197
+ }
198
+ const phaseMeta = workflow.metadata.phases?.[phaseId];
199
+ const targetPhaseId = status === 'failure' ? (phaseMeta?.onFailure || phaseId) : phaseId;
200
+ let message = `### Current Phase: ${targetPhaseId}\n\n`;
201
+ let instructions = workflow.phases.get(targetPhaseId);
202
+ if (instructions) {
203
+ instructions = skipIncludes ? instructions : await this.resolveIncludes(instructions, workflow.path);
204
+ message += instructions;
205
+ }
206
+ if (!skipIncludes) {
207
+ this.assertNoUnresolvedIncludes(message, `${workflow.metadata.name}:${targetPhaseId} (${status})`);
208
+ }
209
+ return {
210
+ message: await this.prependPhaseAuthority(message, true),
211
+ nextPhase: targetPhaseId,
212
+ status
213
+ };
214
+ }
215
+ async getJobOverview(jobName) {
216
+ const job = await this.getOrLoadJob(jobName);
217
+ return job ? { overview: job.overview, isSimple: job.isSimple } : null;
218
+ }
219
+ async getAllJobMetadata() {
220
+ const items = await this.resolver.listItems('job');
221
+ const jobs = [];
222
+ for (const item of items) {
223
+ const job = await this.resolver.getJob(item.name);
224
+ if (job)
225
+ jobs.push(job);
226
+ }
227
+ return jobs;
228
+ }
229
+ }
230
+ exports.AIMentor = AIMentor;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * FRAIM Configuration Loader
4
+ * Loads and validates workspace FRAIM config with fallback to defaults.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.normalizeFraimConfig = normalizeFraimConfig;
8
+ exports.loadFraimConfig = loadFraimConfig;
9
+ exports.getConfigValue = getConfigValue;
10
+ exports.getRepositoryInfo = getRepositoryInfo;
11
+ const fs_1 = require("fs");
12
+ const types_1 = require("./types");
13
+ const project_fraim_paths_1 = require("./utils/project-fraim-paths");
14
+ function normalizeFraimConfig(config) {
15
+ // Handle backward compatibility and migration
16
+ const mergedConfig = {
17
+ version: config.version || types_1.DEFAULT_FRAIM_CONFIG.version,
18
+ project: {
19
+ ...types_1.DEFAULT_FRAIM_CONFIG.project,
20
+ ...(config.project || {})
21
+ },
22
+ repository: {
23
+ ...types_1.DEFAULT_FRAIM_CONFIG.repository,
24
+ ...(config.repository || {}),
25
+ // Migrate from old git config if repository is missing
26
+ ...((!config.repository && config.git) ? {
27
+ provider: config.git.provider || 'github',
28
+ owner: config.git.repoOwner,
29
+ name: config.git.repoName,
30
+ defaultBranch: config.git.defaultBranch || 'main'
31
+ } : {})
32
+ },
33
+ customizations: {
34
+ ...types_1.DEFAULT_FRAIM_CONFIG.customizations,
35
+ ...(config.customizations || {})
36
+ }
37
+ };
38
+ if (config.customizations?.postCleanupHook || config.customizations?.cleanupCommand) {
39
+ if (!mergedConfig.customizations)
40
+ mergedConfig.customizations = {};
41
+ mergedConfig.customizations.postCleanupHook = config.customizations.postCleanupHook || config.customizations.cleanupCommand;
42
+ }
43
+ if (config.issueTracking && typeof config.issueTracking === 'object') {
44
+ mergedConfig.issueTracking = config.issueTracking;
45
+ }
46
+ if (config.compliance) {
47
+ mergedConfig.compliance = config.compliance;
48
+ }
49
+ if (config.learning) {
50
+ mergedConfig.learning = config.learning;
51
+ }
52
+ if (config.competitors && typeof config.competitors === 'object') {
53
+ mergedConfig.competitors = config.competitors;
54
+ }
55
+ return mergedConfig;
56
+ }
57
+ /**
58
+ * Load FRAIM configuration from the workspace FRAIM config file.
59
+ * Falls back to defaults if the file doesn't exist.
60
+ */
61
+ function loadFraimConfig(configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd())) {
62
+ const displayPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json');
63
+ if (!(0, fs_1.existsSync)(configPath)) {
64
+ console.log(`No ${displayPath} found, using defaults`);
65
+ return { ...types_1.DEFAULT_FRAIM_CONFIG };
66
+ }
67
+ try {
68
+ const configContent = (0, fs_1.readFileSync)(configPath, 'utf-8');
69
+ const config = JSON.parse(configContent);
70
+ const mergedConfig = normalizeFraimConfig(config);
71
+ console.log(`Loaded FRAIM config from ${displayPath} (version ${mergedConfig.version})`);
72
+ if (config.git && !config.repository) {
73
+ console.warn('Deprecated: "git" config detected. Consider migrating to "repository" config.');
74
+ }
75
+ return mergedConfig;
76
+ }
77
+ catch (error) {
78
+ console.warn(`Failed to load ${displayPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
79
+ console.warn('Using default configuration');
80
+ return { ...types_1.DEFAULT_FRAIM_CONFIG };
81
+ }
82
+ }
83
+ function getConfigValue(config, path) {
84
+ const parts = path.split('.');
85
+ let value = config;
86
+ for (const part of parts) {
87
+ if (value && typeof value === 'object' && part in value) {
88
+ value = value[part];
89
+ }
90
+ else {
91
+ return undefined;
92
+ }
93
+ }
94
+ return value;
95
+ }
96
+ function getRepositoryInfo(config) {
97
+ if (config.repository) {
98
+ return {
99
+ owner: config.repository.owner || config.repository.organization || config.repository.namespace,
100
+ name: config.repository.name,
101
+ provider: config.repository.provider,
102
+ defaultBranch: config.repository.defaultBranch
103
+ };
104
+ }
105
+ if (config.git) {
106
+ return {
107
+ owner: config.git.repoOwner,
108
+ name: config.git.repoName,
109
+ provider: config.git.provider || 'github',
110
+ defaultBranch: config.git.defaultBranch
111
+ };
112
+ }
113
+ return {};
114
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readFraimConfigRaw = readFraimConfigRaw;
4
+ exports.mergeFraimConfig = mergeFraimConfig;
5
+ exports.writeFraimConfigUpdate = writeFraimConfigUpdate;
6
+ exports.writeFraimConfig = writeFraimConfig;
7
+ const fs_1 = require("fs");
8
+ const path_1 = require("path");
9
+ const config_loader_1 = require("./config-loader");
10
+ const types_1 = require("./types");
11
+ function isPlainObject(value) {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ }
14
+ function deepMerge(baseValue, updateValue) {
15
+ if (updateValue === undefined) {
16
+ return baseValue;
17
+ }
18
+ if (Array.isArray(updateValue)) {
19
+ return [...updateValue];
20
+ }
21
+ if (!isPlainObject(updateValue)) {
22
+ return updateValue;
23
+ }
24
+ const baseObject = isPlainObject(baseValue) ? baseValue : {};
25
+ const merged = { ...baseObject };
26
+ for (const [key, value] of Object.entries(updateValue)) {
27
+ merged[key] = deepMerge(baseObject[key], value);
28
+ }
29
+ return merged;
30
+ }
31
+ function ensureWritableFraimConfigShape(rawConfig) {
32
+ const config = { ...rawConfig };
33
+ if (typeof config.version !== 'string' || config.version.trim().length === 0) {
34
+ config.version = types_1.DEFAULT_FRAIM_CONFIG.version;
35
+ }
36
+ const projectConfig = isPlainObject(config.project) ? config.project : {};
37
+ config.project = {
38
+ ...types_1.DEFAULT_FRAIM_CONFIG.project,
39
+ ...projectConfig
40
+ };
41
+ const customizationsConfig = isPlainObject(config.customizations) ? config.customizations : {};
42
+ config.customizations = {
43
+ ...types_1.DEFAULT_FRAIM_CONFIG.customizations,
44
+ ...customizationsConfig
45
+ };
46
+ return config;
47
+ }
48
+ function readFraimConfigRaw(configPath) {
49
+ if (!(0, fs_1.existsSync)(configPath)) {
50
+ return {};
51
+ }
52
+ const parsed = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
53
+ if (!isPlainObject(parsed)) {
54
+ throw new Error('FRAIM config must contain a JSON object at the top level.');
55
+ }
56
+ return parsed;
57
+ }
58
+ function mergeFraimConfig(existingRawConfig, update) {
59
+ const rawConfig = ensureWritableFraimConfigShape(deepMerge(existingRawConfig, update));
60
+ return {
61
+ config: (0, config_loader_1.normalizeFraimConfig)(rawConfig),
62
+ created: Object.keys(existingRawConfig).length === 0,
63
+ rawConfig
64
+ };
65
+ }
66
+ function writeFraimConfigUpdate(configPath, update) {
67
+ const existingRawConfig = readFraimConfigRaw(configPath);
68
+ const result = mergeFraimConfig(existingRawConfig, update);
69
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(configPath), { recursive: true });
70
+ (0, fs_1.writeFileSync)(configPath, JSON.stringify(result.rawConfig, null, 2));
71
+ return result;
72
+ }
73
+ function writeFraimConfig(configPath, config) {
74
+ return writeFraimConfigUpdate(configPath, config);
75
+ }