fraim-framework 2.0.81 → 2.0.82
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/dist/src/cli/commands/add-provider.js +383 -0
- package/dist/src/cli/commands/doctor.js +129 -112
- package/dist/src/cli/commands/init-project.js +66 -19
- package/dist/src/cli/commands/sync.js +2 -2
- package/dist/src/cli/doctor/check-runner.js +196 -0
- package/dist/src/cli/doctor/checks/global-setup-checks.js +221 -0
- package/dist/src/cli/doctor/checks/ide-config-checks.js +229 -0
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +203 -0
- package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
- package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
- package/dist/src/cli/doctor/checks/workflow-checks.js +247 -0
- package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
- package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
- package/dist/src/cli/doctor/types.js +6 -0
- package/dist/src/cli/fraim.js +2 -2
- package/dist/src/cli/utils/remote-sync.js +22 -2
- package/package.json +3 -2
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Project setup checks for FRAIM doctor command
|
|
4
|
+
* Validates project configuration and initialization
|
|
5
|
+
* Issue #144: Enhanced doctor command
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getProjectSetupChecks = getProjectSetupChecks;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
/**
|
|
16
|
+
* Check if project is initialized
|
|
17
|
+
*/
|
|
18
|
+
function checkProjectInitialized() {
|
|
19
|
+
return {
|
|
20
|
+
name: 'Project initialized',
|
|
21
|
+
category: 'projectSetup',
|
|
22
|
+
critical: true,
|
|
23
|
+
run: async () => {
|
|
24
|
+
const fraimDir = path_1.default.join(process.cwd(), '.fraim');
|
|
25
|
+
if (fs_1.default.existsSync(fraimDir)) {
|
|
26
|
+
return {
|
|
27
|
+
status: 'passed',
|
|
28
|
+
message: 'Project initialized',
|
|
29
|
+
details: { path: fraimDir }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
status: 'error',
|
|
34
|
+
message: 'Project not initialized',
|
|
35
|
+
suggestion: 'Initialize project with fraim init-project',
|
|
36
|
+
command: 'fraim init-project'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if project config is valid
|
|
43
|
+
*/
|
|
44
|
+
function checkProjectConfigValid() {
|
|
45
|
+
return {
|
|
46
|
+
name: 'Project config valid',
|
|
47
|
+
category: 'projectSetup',
|
|
48
|
+
critical: true,
|
|
49
|
+
run: async () => {
|
|
50
|
+
const configPath = path_1.default.join(process.cwd(), '.fraim', 'config.json');
|
|
51
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
52
|
+
return {
|
|
53
|
+
status: 'error',
|
|
54
|
+
message: 'Project config missing',
|
|
55
|
+
suggestion: 'Initialize project with fraim init-project',
|
|
56
|
+
command: 'fraim init-project'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
61
|
+
if (!config.project || !config.project.name) {
|
|
62
|
+
return {
|
|
63
|
+
status: 'warning',
|
|
64
|
+
message: 'Project config incomplete',
|
|
65
|
+
suggestion: 'Add project name to .fraim/config.json'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
status: 'passed',
|
|
70
|
+
message: 'Project config valid',
|
|
71
|
+
details: { projectName: config.project.name }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
status: 'error',
|
|
77
|
+
message: 'Project config corrupted',
|
|
78
|
+
suggestion: 'Fix JSON syntax in .fraim/config.json',
|
|
79
|
+
details: { error: error.message }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if git remote is detected
|
|
87
|
+
*/
|
|
88
|
+
function checkGitRemoteDetected() {
|
|
89
|
+
return {
|
|
90
|
+
name: 'Git remote detected',
|
|
91
|
+
category: 'projectSetup',
|
|
92
|
+
critical: false,
|
|
93
|
+
run: async () => {
|
|
94
|
+
try {
|
|
95
|
+
const remote = (0, child_process_1.execSync)('git remote get-url origin', {
|
|
96
|
+
encoding: 'utf8',
|
|
97
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
98
|
+
}).trim();
|
|
99
|
+
if (remote) {
|
|
100
|
+
return {
|
|
101
|
+
status: 'passed',
|
|
102
|
+
message: `Git remote: ${remote}`,
|
|
103
|
+
details: { remote }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
status: 'warning',
|
|
108
|
+
message: 'No git remote configured',
|
|
109
|
+
suggestion: 'Add git remote with: git remote add origin <url>'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
status: 'warning',
|
|
115
|
+
message: 'Not a git repository',
|
|
116
|
+
suggestion: 'Initialize git with: git init',
|
|
117
|
+
details: { error: error.message }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if repository provider matches config
|
|
125
|
+
*/
|
|
126
|
+
function checkProviderMatches() {
|
|
127
|
+
return {
|
|
128
|
+
name: 'Repository provider matches',
|
|
129
|
+
category: 'projectSetup',
|
|
130
|
+
critical: false,
|
|
131
|
+
run: async () => {
|
|
132
|
+
const configPath = path_1.default.join(process.cwd(), '.fraim', 'config.json');
|
|
133
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
134
|
+
return {
|
|
135
|
+
status: 'error',
|
|
136
|
+
message: 'Cannot check provider - config missing'
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
141
|
+
if (!config.repository?.provider) {
|
|
142
|
+
return {
|
|
143
|
+
status: 'passed',
|
|
144
|
+
message: 'No repository provider configured (conversational mode)',
|
|
145
|
+
details: { mode: config.mode || 'conversational' }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Try to get git remote
|
|
149
|
+
try {
|
|
150
|
+
const remote = (0, child_process_1.execSync)('git remote get-url origin', {
|
|
151
|
+
encoding: 'utf8',
|
|
152
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
153
|
+
}).trim();
|
|
154
|
+
const provider = config.repository.provider;
|
|
155
|
+
const remoteMatches = remote.includes(provider);
|
|
156
|
+
if (remoteMatches) {
|
|
157
|
+
return {
|
|
158
|
+
status: 'passed',
|
|
159
|
+
message: `Repository provider: ${provider}`,
|
|
160
|
+
details: { provider, remote }
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
status: 'warning',
|
|
165
|
+
message: `Provider mismatch: config says ${provider} but remote is ${remote}`,
|
|
166
|
+
suggestion: 'Update repository.provider in .fraim/config.json'
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return {
|
|
171
|
+
status: 'warning',
|
|
172
|
+
message: 'Cannot verify provider - no git remote',
|
|
173
|
+
details: { configuredProvider: config.repository.provider }
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
status: 'error',
|
|
180
|
+
message: 'Failed to check provider',
|
|
181
|
+
details: { error: error.message }
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if config matches mode
|
|
189
|
+
*/
|
|
190
|
+
function checkConfigMatchesMode() {
|
|
191
|
+
return {
|
|
192
|
+
name: 'Config matches mode',
|
|
193
|
+
category: 'projectSetup',
|
|
194
|
+
critical: false,
|
|
195
|
+
run: async () => {
|
|
196
|
+
const configPath = path_1.default.join(process.cwd(), '.fraim', 'config.json');
|
|
197
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
198
|
+
return {
|
|
199
|
+
status: 'error',
|
|
200
|
+
message: 'Cannot check mode - config missing'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
205
|
+
const mode = config.mode || 'conversational';
|
|
206
|
+
if (mode === 'conversational') {
|
|
207
|
+
// No repository or issue tracking required
|
|
208
|
+
return {
|
|
209
|
+
status: 'passed',
|
|
210
|
+
message: 'Conversational mode (no platform required)',
|
|
211
|
+
details: { mode }
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (mode === 'integrated') {
|
|
215
|
+
// Repository required, issue tracking should match
|
|
216
|
+
if (!config.repository?.provider) {
|
|
217
|
+
return {
|
|
218
|
+
status: 'error',
|
|
219
|
+
message: 'Integrated mode requires repository configuration',
|
|
220
|
+
suggestion: 'Add repository config to .fraim/config.json'
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
status: 'passed',
|
|
225
|
+
message: 'Integrated mode configured',
|
|
226
|
+
details: { mode, provider: config.repository.provider }
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (mode === 'split') {
|
|
230
|
+
// Both repository and issue tracking required
|
|
231
|
+
if (!config.repository?.provider) {
|
|
232
|
+
return {
|
|
233
|
+
status: 'error',
|
|
234
|
+
message: 'Split mode requires repository configuration',
|
|
235
|
+
suggestion: 'Add repository config to .fraim/config.json'
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (!config.issueTracking?.provider) {
|
|
239
|
+
return {
|
|
240
|
+
status: 'error',
|
|
241
|
+
message: 'Split mode requires issue tracking configuration',
|
|
242
|
+
suggestion: 'Add issueTracking config to .fraim/config.json'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
status: 'passed',
|
|
247
|
+
message: 'Split mode configured',
|
|
248
|
+
details: {
|
|
249
|
+
mode,
|
|
250
|
+
repository: config.repository.provider,
|
|
251
|
+
issueTracking: config.issueTracking.provider
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
status: 'error',
|
|
257
|
+
message: `Unknown mode: ${mode}`,
|
|
258
|
+
suggestion: 'Mode must be conversational, integrated, or split'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
return {
|
|
263
|
+
status: 'error',
|
|
264
|
+
message: 'Failed to check mode configuration',
|
|
265
|
+
details: { error: error.message }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get all project setup checks
|
|
273
|
+
*/
|
|
274
|
+
function getProjectSetupChecks() {
|
|
275
|
+
return [
|
|
276
|
+
checkProjectInitialized(),
|
|
277
|
+
checkProjectConfigValid(),
|
|
278
|
+
checkGitRemoteDetected(),
|
|
279
|
+
checkProviderMatches(),
|
|
280
|
+
checkConfigMatchesMode()
|
|
281
|
+
];
|
|
282
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scripts checks for FRAIM doctor command
|
|
4
|
+
* Validates user scripts directory and sync status
|
|
5
|
+
* Issue #144: Enhanced doctor command
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getScriptsChecks = getScriptsChecks;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const SCRIPTS_DIR = path_1.default.join(os_1.default.homedir(), '.fraim', 'scripts');
|
|
16
|
+
/**
|
|
17
|
+
* Check if scripts directory exists
|
|
18
|
+
*/
|
|
19
|
+
function checkScriptsDirectoryExists() {
|
|
20
|
+
return {
|
|
21
|
+
name: 'Scripts directory exists',
|
|
22
|
+
category: 'scripts',
|
|
23
|
+
critical: false,
|
|
24
|
+
run: async () => {
|
|
25
|
+
if (fs_1.default.existsSync(SCRIPTS_DIR)) {
|
|
26
|
+
return {
|
|
27
|
+
status: 'passed',
|
|
28
|
+
message: 'Scripts directory exists',
|
|
29
|
+
details: { path: SCRIPTS_DIR }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
status: 'warning',
|
|
34
|
+
message: 'Scripts directory missing',
|
|
35
|
+
suggestion: 'Run fraim sync to create scripts directory',
|
|
36
|
+
command: 'fraim sync'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if scripts are synced
|
|
43
|
+
*/
|
|
44
|
+
function checkScriptsSynced() {
|
|
45
|
+
return {
|
|
46
|
+
name: 'Scripts synced',
|
|
47
|
+
category: 'scripts',
|
|
48
|
+
critical: false,
|
|
49
|
+
run: async () => {
|
|
50
|
+
if (!fs_1.default.existsSync(SCRIPTS_DIR)) {
|
|
51
|
+
return {
|
|
52
|
+
status: 'warning',
|
|
53
|
+
message: 'Cannot check scripts - directory missing'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const scripts = fs_1.default.readdirSync(SCRIPTS_DIR).filter(f => f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.sh'));
|
|
58
|
+
if (scripts.length > 0) {
|
|
59
|
+
return {
|
|
60
|
+
status: 'passed',
|
|
61
|
+
message: `${scripts.length} scripts synced`,
|
|
62
|
+
details: { scriptCount: scripts.length }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
status: 'warning',
|
|
67
|
+
message: 'No scripts found',
|
|
68
|
+
suggestion: 'Run fraim sync to fetch scripts',
|
|
69
|
+
command: 'fraim sync'
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
status: 'error',
|
|
75
|
+
message: 'Failed to check scripts',
|
|
76
|
+
details: { error: error.message }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if scripts are executable (Unix/Linux/macOS only)
|
|
84
|
+
*/
|
|
85
|
+
function checkScriptsExecutable() {
|
|
86
|
+
return {
|
|
87
|
+
name: 'Scripts executable',
|
|
88
|
+
category: 'scripts',
|
|
89
|
+
critical: false,
|
|
90
|
+
run: async () => {
|
|
91
|
+
// Skip on Windows
|
|
92
|
+
if (process.platform === 'win32') {
|
|
93
|
+
return {
|
|
94
|
+
status: 'passed',
|
|
95
|
+
message: 'Script permissions not applicable on Windows',
|
|
96
|
+
details: { platform: 'win32' }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (!fs_1.default.existsSync(SCRIPTS_DIR)) {
|
|
100
|
+
return {
|
|
101
|
+
status: 'warning',
|
|
102
|
+
message: 'Cannot check permissions - directory missing'
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const scripts = fs_1.default.readdirSync(SCRIPTS_DIR).filter(f => f.endsWith('.sh'));
|
|
107
|
+
if (scripts.length === 0) {
|
|
108
|
+
return {
|
|
109
|
+
status: 'passed',
|
|
110
|
+
message: 'No shell scripts to check',
|
|
111
|
+
details: { scriptCount: 0 }
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
let nonExecutable = 0;
|
|
115
|
+
for (const script of scripts) {
|
|
116
|
+
const scriptPath = path_1.default.join(SCRIPTS_DIR, script);
|
|
117
|
+
const stats = fs_1.default.statSync(scriptPath);
|
|
118
|
+
// Check if executable bit is set (mode & 0o111)
|
|
119
|
+
if ((stats.mode & 0o111) === 0) {
|
|
120
|
+
nonExecutable++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (nonExecutable === 0) {
|
|
124
|
+
return {
|
|
125
|
+
status: 'passed',
|
|
126
|
+
message: `All ${scripts.length} shell scripts executable`,
|
|
127
|
+
details: { scriptCount: scripts.length }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
status: 'warning',
|
|
132
|
+
message: `${nonExecutable} scripts not executable`,
|
|
133
|
+
suggestion: `Run: chmod +x ${SCRIPTS_DIR}/*.sh`,
|
|
134
|
+
command: `chmod +x ${SCRIPTS_DIR}/*.sh`,
|
|
135
|
+
details: { nonExecutable, total: scripts.length }
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
status: 'error',
|
|
141
|
+
message: 'Failed to check script permissions',
|
|
142
|
+
details: { error: error.message }
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get all scripts checks
|
|
150
|
+
*/
|
|
151
|
+
function getScriptsChecks() {
|
|
152
|
+
return [
|
|
153
|
+
checkScriptsDirectoryExists(),
|
|
154
|
+
checkScriptsSynced(),
|
|
155
|
+
checkScriptsExecutable()
|
|
156
|
+
];
|
|
157
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Workflow checks for FRAIM doctor command
|
|
4
|
+
* Validates workflow installation and versions
|
|
5
|
+
* Issue #144: Enhanced doctor command
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getWorkflowChecks = getWorkflowChecks;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const inheritance_parser_1 = require("../../../core/utils/inheritance-parser");
|
|
15
|
+
/**
|
|
16
|
+
* Check if workflows directory exists
|
|
17
|
+
*/
|
|
18
|
+
function checkWorkflowsDirectoryExists() {
|
|
19
|
+
return {
|
|
20
|
+
name: 'Workflows directory exists',
|
|
21
|
+
category: 'workflows',
|
|
22
|
+
critical: true,
|
|
23
|
+
run: async () => {
|
|
24
|
+
const workflowsDir = path_1.default.join(process.cwd(), '.fraim', 'workflows');
|
|
25
|
+
if (fs_1.default.existsSync(workflowsDir)) {
|
|
26
|
+
return {
|
|
27
|
+
status: 'passed',
|
|
28
|
+
message: 'Workflows directory exists',
|
|
29
|
+
details: { path: workflowsDir }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
status: 'error',
|
|
34
|
+
message: 'Workflows directory missing',
|
|
35
|
+
suggestion: 'Run fraim sync to fetch workflows',
|
|
36
|
+
command: 'fraim sync'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if workflow stubs are present
|
|
43
|
+
*/
|
|
44
|
+
function checkWorkflowStubsPresent() {
|
|
45
|
+
return {
|
|
46
|
+
name: 'Workflow stubs present',
|
|
47
|
+
category: 'workflows',
|
|
48
|
+
critical: false,
|
|
49
|
+
run: async () => {
|
|
50
|
+
const workflowsDir = path_1.default.join(process.cwd(), '.fraim', 'workflows');
|
|
51
|
+
if (!fs_1.default.existsSync(workflowsDir)) {
|
|
52
|
+
return {
|
|
53
|
+
status: 'error',
|
|
54
|
+
message: 'Cannot check stubs - workflows directory missing'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
// Count all .md files recursively
|
|
59
|
+
let stubCount = 0;
|
|
60
|
+
const countStubs = (dir) => {
|
|
61
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (entry.isDirectory()) {
|
|
64
|
+
countStubs(path_1.default.join(dir, entry.name));
|
|
65
|
+
}
|
|
66
|
+
else if (entry.name.endsWith('.md')) {
|
|
67
|
+
stubCount++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
countStubs(workflowsDir);
|
|
72
|
+
if (stubCount > 0) {
|
|
73
|
+
return {
|
|
74
|
+
status: 'passed',
|
|
75
|
+
message: `${stubCount} workflow stubs found`,
|
|
76
|
+
details: { stubCount }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
status: 'warning',
|
|
81
|
+
message: 'No workflow stubs found',
|
|
82
|
+
suggestion: 'Run fraim sync to fetch workflows',
|
|
83
|
+
command: 'fraim sync'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
status: 'error',
|
|
89
|
+
message: 'Failed to count workflow stubs',
|
|
90
|
+
details: { error: error.message }
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if workflows are up to date
|
|
98
|
+
*/
|
|
99
|
+
function checkWorkflowsUpToDate() {
|
|
100
|
+
return {
|
|
101
|
+
name: 'Workflows up to date',
|
|
102
|
+
category: 'workflows',
|
|
103
|
+
critical: false,
|
|
104
|
+
run: async () => {
|
|
105
|
+
const configPath = path_1.default.join(process.cwd(), '.fraim', 'config.json');
|
|
106
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
107
|
+
return {
|
|
108
|
+
status: 'warning',
|
|
109
|
+
message: 'Cannot check workflow version - config missing'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
114
|
+
// Try multiple paths to find package.json
|
|
115
|
+
const possiblePaths = [
|
|
116
|
+
path_1.default.join(__dirname, '../../../../package.json'), // From dist/src/cli/doctor/checks
|
|
117
|
+
path_1.default.join(__dirname, '../../../package.json'), // From src/cli/doctor/checks
|
|
118
|
+
path_1.default.join(process.cwd(), 'package.json') // From project root
|
|
119
|
+
];
|
|
120
|
+
let packageJson = null;
|
|
121
|
+
for (const pkgPath of possiblePaths) {
|
|
122
|
+
if (fs_1.default.existsSync(pkgPath)) {
|
|
123
|
+
packageJson = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!packageJson) {
|
|
128
|
+
return {
|
|
129
|
+
status: 'warning',
|
|
130
|
+
message: 'Cannot determine package version'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const currentVersion = packageJson.version;
|
|
134
|
+
const configVersion = config.version;
|
|
135
|
+
if (configVersion === currentVersion) {
|
|
136
|
+
return {
|
|
137
|
+
status: 'passed',
|
|
138
|
+
message: `Workflows up to date (v${currentVersion})`,
|
|
139
|
+
details: { version: currentVersion }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
status: 'warning',
|
|
144
|
+
message: `Workflows outdated (v${configVersion} → v${currentVersion})`,
|
|
145
|
+
suggestion: 'Update workflows with fraim sync',
|
|
146
|
+
command: 'fraim sync',
|
|
147
|
+
details: { configVersion, currentVersion }
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
status: 'error',
|
|
153
|
+
message: 'Failed to check workflow version',
|
|
154
|
+
details: { error: error.message }
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if override syntax is valid
|
|
162
|
+
*/
|
|
163
|
+
function checkOverrideSyntaxValid() {
|
|
164
|
+
return {
|
|
165
|
+
name: 'Override syntax valid',
|
|
166
|
+
category: 'workflows',
|
|
167
|
+
critical: false,
|
|
168
|
+
run: async () => {
|
|
169
|
+
const overridesDir = path_1.default.join(process.cwd(), '.fraim', 'overrides');
|
|
170
|
+
if (!fs_1.default.existsSync(overridesDir)) {
|
|
171
|
+
return {
|
|
172
|
+
status: 'passed',
|
|
173
|
+
message: 'No overrides (not required)',
|
|
174
|
+
details: { overrideCount: 0 }
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const parser = new inheritance_parser_1.InheritanceParser();
|
|
179
|
+
const overrides = [];
|
|
180
|
+
let invalidCount = 0;
|
|
181
|
+
const errors = [];
|
|
182
|
+
// Scan for all override files
|
|
183
|
+
const scanDir = (dir, base = '') => {
|
|
184
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
const relativePath = path_1.default.join(base, entry.name);
|
|
187
|
+
if (entry.isDirectory()) {
|
|
188
|
+
scanDir(path_1.default.join(dir, entry.name), relativePath);
|
|
189
|
+
}
|
|
190
|
+
else if (entry.name.endsWith('.md')) {
|
|
191
|
+
overrides.push(relativePath);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
scanDir(overridesDir);
|
|
196
|
+
// Validate each override
|
|
197
|
+
for (const override of overrides) {
|
|
198
|
+
const overridePath = path_1.default.join(overridesDir, override);
|
|
199
|
+
const content = fs_1.default.readFileSync(overridePath, 'utf-8');
|
|
200
|
+
const parseResult = parser.parse(content);
|
|
201
|
+
if (parseResult.hasImports) {
|
|
202
|
+
for (const importPath of parseResult.imports) {
|
|
203
|
+
try {
|
|
204
|
+
parser.sanitizePath(importPath);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
invalidCount++;
|
|
208
|
+
errors.push(`${override}: ${error.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (invalidCount === 0) {
|
|
214
|
+
return {
|
|
215
|
+
status: 'passed',
|
|
216
|
+
message: `${overrides.length} overrides valid`,
|
|
217
|
+
details: { overrideCount: overrides.length }
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
status: 'error',
|
|
222
|
+
message: `${invalidCount} invalid overrides found`,
|
|
223
|
+
suggestion: 'Fix override import syntax',
|
|
224
|
+
details: { errors }
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return {
|
|
229
|
+
status: 'error',
|
|
230
|
+
message: 'Failed to validate overrides',
|
|
231
|
+
details: { error: error.message }
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get all workflow checks
|
|
239
|
+
*/
|
|
240
|
+
function getWorkflowChecks() {
|
|
241
|
+
return [
|
|
242
|
+
checkWorkflowsDirectoryExists(),
|
|
243
|
+
checkWorkflowStubsPresent(),
|
|
244
|
+
checkWorkflowsUpToDate(),
|
|
245
|
+
checkOverrideSyntaxValid()
|
|
246
|
+
];
|
|
247
|
+
}
|