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.
@@ -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
+ }