jettypod 4.1.2 → 4.1.4

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 (179) hide show
  1. package/.nvmrc +1 -0
  2. package/docs/COMPLETE-TESTING-STRATEGY.md +970 -0
  3. package/docs/DECISIONS.md +10 -12
  4. package/docs/NODE_VERSION.md +83 -0
  5. package/docs/TDD-INFRASTRUCTURE-STRATEGY.md +1374 -0
  6. package/docs/TESTING-FOR-NON-ENGINEERS.md +1588 -0
  7. package/docs/TESTING-STRATEGY-AUDIT.md +698 -0
  8. package/hooks/post-checkout +17 -0
  9. package/hooks/post-merge +17 -0
  10. package/hooks/pre-commit +30 -0
  11. package/jettypod.js +259 -120
  12. package/lib/coverage-tracker.js +218 -0
  13. package/lib/database.js +2 -0
  14. package/lib/db-export.js +192 -0
  15. package/lib/db-import.js +193 -0
  16. package/lib/external-transition-handler.js +32 -0
  17. package/lib/git-hook-helpers.js +174 -0
  18. package/lib/git-root.js +90 -0
  19. package/lib/infrastructure-chore-generator.js +45 -0
  20. package/lib/install-hooks.js +52 -0
  21. package/lib/jettypod-backup.js +238 -0
  22. package/lib/merge-lock.js +193 -0
  23. package/lib/migrations/012-add-worktree-path.js +38 -0
  24. package/lib/migrations/013-worktrees-table.js +86 -0
  25. package/lib/migrations/014-migrate-worktree-data.js +161 -0
  26. package/lib/migrations/015-merge-locks-table.js +67 -0
  27. package/lib/pattern-finder.js +152 -0
  28. package/lib/process-manager.js +140 -0
  29. package/lib/production-standards-reader.js +13 -2
  30. package/lib/production-standards-writer.js +85 -0
  31. package/lib/skills/feature-planning/dry-run-validator.js +135 -0
  32. package/lib/skills/feature-planning/validation-formatter.js +160 -0
  33. package/lib/smart-conflict-detection.js +168 -0
  34. package/lib/smart-fetch-rebase.js +614 -0
  35. package/lib/step-definition-parser.js +76 -0
  36. package/lib/unit-test-generator.js +232 -0
  37. package/lib/verification-command-generator.js +66 -0
  38. package/lib/worktree-diagnostics.js +413 -0
  39. package/lib/worktree-facade.js +174 -0
  40. package/lib/worktree-manager.js +636 -0
  41. package/lib/worktree-reconciler.js +429 -0
  42. package/package.json +30 -3
  43. package/skills-templates/external-transition/SKILL.md +34 -3
  44. package/skills-templates/feature-planning/SKILL.md +190 -24
  45. package/skills-templates/production-mode/SKILL.md +127 -9
  46. package/skills-templates/speed-mode/SKILL.md +454 -51
  47. package/skills-templates/stable-mode/SKILL.md +285 -76
  48. package/.claude/PROTECT_SKILLS.md +0 -28
  49. package/.claude/settings.json +0 -24
  50. package/.claude/settings.local.json +0 -16
  51. package/.claude/skills/epic-planning/SKILL.md +0 -297
  52. package/.claude/skills/external-transition/SKILL.md +0 -384
  53. package/.claude/skills/feature-planning/SKILL.md +0 -464
  54. package/.claude/skills/production-mode/SKILL.md +0 -369
  55. package/.claude/skills/speed-mode/SKILL.md +0 -481
  56. package/.claude/skills/stable-mode/SKILL.md +0 -713
  57. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/epic-planning/SKILL.md +0 -297
  58. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/feature-planning/SKILL.md +0 -464
  59. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/speed-mode/SKILL.md +0 -467
  60. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/stable-mode/SKILL.md +0 -673
  61. package/.claude/skills.backup-2025-11-11T16-15-10-070Z/epic-discover/SKILL.md +0 -297
  62. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/epic-planning/SKILL.md +0 -297
  63. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/feature-planning/SKILL.md +0 -464
  64. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/speed-mode/SKILL.md +0 -467
  65. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/stable-mode/SKILL.md +0 -673
  66. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/epic-planning/SKILL.md +0 -297
  67. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/feature-planning/SKILL.md +0 -464
  68. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/speed-mode/SKILL.md +0 -467
  69. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/stable-mode/SKILL.md +0 -673
  70. package/.devpod/current-work.json +0 -10
  71. package/.devpod/work.db +0 -0
  72. package/.github/workflows/test-safety.yml +0 -85
  73. package/.jettypod/config.json +0 -5
  74. package/.jettypod/current-work.json +0 -10
  75. package/.jettypod/hooks/README.md +0 -77
  76. package/.jettypod/hooks/protect-claude-md.js +0 -338
  77. package/.jettypod/test-work.db +0 -0
  78. package/.jettypod/work.db +0 -0
  79. package/CLAUDE.md +0 -49
  80. package/SPEED-STABLE-AUDIT.md +0 -853
  81. package/SYSTEM-BEHAVIOR.md +0 -2199
  82. package/TEST_SAFETY_AUDIT.md +0 -314
  83. package/TEST_SAFETY_IMPLEMENTATION.md +0 -97
  84. package/cucumber-report.html +0 -45
  85. package/dist/devpod-linux +0 -0
  86. package/dist/devpod-macos +0 -0
  87. package/dist/devpod-win.exe +0 -0
  88. package/docs/features/jettypod-standards-explained.md +0 -543
  89. package/docs/features/standards-inventory.md +0 -257
  90. package/features/auto-generate-production-chores.feature +0 -13
  91. package/features/backlog-command.feature +0 -26
  92. package/features/backlog-filtering-production.feature +0 -10
  93. package/features/claude-md-protection/steps.js +0 -498
  94. package/features/decisions/index.js +0 -490
  95. package/features/decisions/index.test.js +0 -208
  96. package/features/fix-text-wrapping.feature +0 -42
  97. package/features/git-hooks/git-hooks.feature +0 -30
  98. package/features/git-hooks/index.js +0 -93
  99. package/features/git-hooks/index.test.js +0 -137
  100. package/features/git-hooks/post-commit +0 -56
  101. package/features/git-hooks/post-merge +0 -47
  102. package/features/git-hooks/pre-commit +0 -28
  103. package/features/git-hooks/simple-steps.js +0 -53
  104. package/features/git-hooks/simple-test.feature +0 -10
  105. package/features/git-hooks/steps.js +0 -196
  106. package/features/jettypod-update-command.feature +0 -46
  107. package/features/mode-prompts/index.js +0 -95
  108. package/features/mode-prompts/simple-steps.js +0 -44
  109. package/features/mode-prompts/simple-test.feature +0 -9
  110. package/features/mode-prompts/validation.test.js +0 -120
  111. package/features/multiple-claude-instances.feature +0 -121
  112. package/features/production-mode-skill.feature +0 -121
  113. package/features/refactor-mode/steps.js +0 -217
  114. package/features/refactor-mode.feature +0 -49
  115. package/features/simplify-external-transition.feature +0 -166
  116. package/features/skills-update/index.test.js +0 -216
  117. package/features/step_definitions/backlog-command.steps.js +0 -37
  118. package/features/step_definitions/fix-text-wrapping.steps.js +0 -271
  119. package/features/step_definitions/multiple-claude-instances.steps.js +0 -621
  120. package/features/step_definitions/production-mode-skill.steps.js +0 -862
  121. package/features/step_definitions/simplify-external-transition.steps.js +0 -370
  122. package/features/step_definitions/terminal-logo.steps.js +0 -145
  123. package/features/step_definitions/update-command.steps.js +0 -183
  124. package/features/support/hooks.js +0 -9
  125. package/features/terminal-logo/index.js +0 -39
  126. package/features/terminal-logo/terminal-logo.feature +0 -30
  127. package/features/update-command/index.js +0 -181
  128. package/features/update-command/index.test.js +0 -225
  129. package/features/work-commands/bug-workflow-display.feature +0 -22
  130. package/features/work-commands/index.js +0 -498
  131. package/features/work-commands/simple-steps.js +0 -69
  132. package/features/work-commands/stable-tests.feature +0 -57
  133. package/features/work-commands/steps.js +0 -1174
  134. package/features/work-commands/validation.test.js +0 -88
  135. package/features/work-commands/work-commands.feature +0 -13
  136. package/features/work-tracking/discovery-validation.test.js +0 -228
  137. package/features/work-tracking/index.js +0 -1921
  138. package/features/work-tracking/mode-required.feature +0 -112
  139. package/features/work-tracking/phase-tracking.test.js +0 -482
  140. package/features/work-tracking/prototype-tracking.test.js +0 -485
  141. package/features/work-tracking/tree-view.test.js +0 -310
  142. package/features/work-tracking/work-set-mode.feature +0 -71
  143. package/features/work-tracking/work-start-mode.feature +0 -88
  144. package/full-test.txt +0 -0
  145. package/lib/bug-workflow.test.js +0 -177
  146. package/lib/claudemd.test.js +0 -195
  147. package/lib/config.test.js +0 -511
  148. package/lib/constants.test.js +0 -164
  149. package/lib/current-work.test.js +0 -146
  150. package/lib/database-project-config.test.js +0 -111
  151. package/lib/database.test.js +0 -106
  152. package/lib/decisions-generator.test.js +0 -457
  153. package/lib/decisions-helpers.test.js +0 -310
  154. package/lib/git-coordinator.js +0 -167
  155. package/lib/git.test.js +0 -145
  156. package/lib/migrations/002-default-work-item-modes.test.js +0 -351
  157. package/lib/production-chore-generator.test.js +0 -432
  158. package/lib/production-context-detector.test.js +0 -277
  159. package/lib/production-scenario-appender.test.js +0 -235
  160. package/lib/production-scenario-validator.test.js +0 -246
  161. package/lib/production-standards-reader.test.js +0 -270
  162. package/lib/project-state.test.js +0 -92
  163. package/lib/push-queue.js +0 -417
  164. package/lib/queue-processor.js +0 -74
  165. package/lib/test-helpers.js +0 -202
  166. package/lib/test-helpers.test.js +0 -255
  167. package/prototypes/2025-01-11-production-mode-autonomous.js +0 -119
  168. package/prototypes/2025-01-11-production-mode-collaborative.js +0 -166
  169. package/prototypes/2025-01-11-production-mode-guided.js +0 -217
  170. package/prototypes/2025-01-11-production-mode-smart-context.js +0 -347
  171. package/prototypes/2025-01-11-production-standards-example.md +0 -204
  172. package/prototypes/2025-11-10-backlog-filtering-tree-aware.js +0 -242
  173. package/prototypes/test/index.html +0 -1
  174. package/setup-dist-repo.sh +0 -68
  175. package/test-production-standards-engine.js +0 -130
  176. package/test-results.json +0 -2195
  177. package/test-safety-check.sh +0 -80
  178. package/work-item-tracking-plan.md +0 -199
  179. /package/{.jettypod/devpod.db → jettypod.db} +0 -0
@@ -0,0 +1,140 @@
1
+ const { exec } = require('child_process');
2
+ const { promisify } = require('util');
3
+ const execPromise = promisify(exec);
4
+
5
+ /**
6
+ * Find all processes with open file handles in the specified directory
7
+ * @param {string} dirPath - Directory path to search for processes
8
+ * @returns {Promise<Array<{pid: number, name: string}>>} Array of process info
9
+ */
10
+ async function findProcessesInDirectory(dirPath) {
11
+ const processes = [];
12
+
13
+ try {
14
+ // Use lsof to find processes with file handles in the directory
15
+ // -t returns PIDs only, +D searches directory recursively
16
+ // 2>/dev/null suppresses errors, || true ensures command doesn't fail
17
+ const lsofCmd = `lsof +D "${dirPath}" -t 2>/dev/null || true`;
18
+ const { stdout } = await execPromise(lsofCmd);
19
+
20
+ const pids = stdout.trim().split('\n').filter(pid => pid);
21
+
22
+ // Get process names for each PID
23
+ for (const pid of pids) {
24
+ if (!pid) continue;
25
+
26
+ try {
27
+ const psCmd = `ps -p ${pid} -o comm= 2>/dev/null || echo "unknown"`;
28
+ const { stdout: name } = await execPromise(psCmd);
29
+
30
+ processes.push({
31
+ pid: parseInt(pid, 10),
32
+ name: name.trim()
33
+ });
34
+ } catch (err) {
35
+ // Process may have exited, skip it
36
+ continue;
37
+ }
38
+ }
39
+ } catch (err) {
40
+ // lsof command failed - log details and continue gracefully
41
+ console.warn('⚠️ Warning: Failed to detect processes in directory');
42
+ console.warn(` Directory: ${dirPath}`);
43
+
44
+ // Provide actionable error details
45
+ if (err.code === 'ENOENT') {
46
+ console.warn(' Reason: lsof command not found');
47
+ console.warn(' Solution: Install lsof (brew install lsof on macOS, apt-get install lsof on Linux)');
48
+ } else if (err.code === 'EACCES') {
49
+ console.warn(' Reason: Permission denied');
50
+ console.warn(' Solution: Run with appropriate permissions or check directory permissions');
51
+ } else if (err.killed) {
52
+ console.warn(' Reason: Command timed out');
53
+ console.warn(' Solution: Directory may have too many files or processes');
54
+ } else {
55
+ console.warn(` Reason: ${err.message}`);
56
+ }
57
+
58
+ console.warn(' Continuing cleanup without process termination...');
59
+ }
60
+
61
+ return processes;
62
+ }
63
+
64
+ /**
65
+ * Terminate a process gracefully with SIGTERM, falling back to SIGKILL if needed
66
+ * @param {number} pid - Process ID to terminate
67
+ * @param {number} timeout - Maximum milliseconds to wait for graceful termination (default: 2000)
68
+ * @returns {Promise<{pid: number, name: string, method: string}>} Termination result
69
+ */
70
+ async function terminateProcessGracefully(pid, timeout = 2000) {
71
+ let name = 'unknown';
72
+
73
+ // Get process name
74
+ try {
75
+ const { stdout } = await execPromise(`ps -p ${pid} -o comm= 2>/dev/null || echo "unknown"`);
76
+ name = stdout.trim();
77
+ } catch (err) {
78
+ // Process may have exited, name stays "unknown"
79
+ }
80
+
81
+ // Try graceful termination with SIGTERM
82
+ try {
83
+ process.kill(pid, 'SIGTERM');
84
+ } catch (err) {
85
+ // Handle termination errors
86
+ if (err.code === 'EPERM') {
87
+ console.warn(`⚠️ Warning: Permission denied when terminating process ${pid} (${name})`);
88
+ console.warn(' Reason: Insufficient permissions to terminate this process');
89
+ console.warn(' Solution: Run with elevated permissions (sudo) or check process ownership');
90
+ return { pid, name, method: 'permission-denied' };
91
+ } else if (err.code === 'ESRCH') {
92
+ // Process already dead or not found - this is fine
93
+ return { pid, name, method: 'already-terminated' };
94
+ } else {
95
+ console.warn(`⚠️ Warning: Failed to terminate process ${pid} (${name}): ${err.message}`);
96
+ return { pid, name, method: 'failed' };
97
+ }
98
+ }
99
+
100
+ // Wait for process to exit gracefully
101
+ const startTime = Date.now();
102
+ while (Date.now() - startTime < timeout) {
103
+ try {
104
+ // Check if process still exists (kill with signal 0)
105
+ process.kill(pid, 0);
106
+
107
+ // Still alive, wait a bit longer
108
+ await new Promise(resolve => setTimeout(resolve, 100));
109
+ } catch (err) {
110
+ // Process is dead
111
+ return { pid, name, method: 'graceful' };
112
+ }
113
+ }
114
+
115
+ // Timeout reached, force kill with SIGKILL
116
+ try {
117
+ process.kill(pid, 'SIGKILL');
118
+ } catch (err) {
119
+ // Handle force kill errors
120
+ if (err.code === 'EPERM') {
121
+ console.warn(`⚠️ Warning: Permission denied when force-killing process ${pid} (${name})`);
122
+ console.warn(' Reason: Insufficient permissions to force-kill this process');
123
+ console.warn(' Solution: Run with elevated permissions (sudo) or check process ownership');
124
+ return { pid, name, method: 'permission-denied' };
125
+ } else if (err.code === 'ESRCH') {
126
+ // Process exited during timeout - this is fine
127
+ return { pid, name, method: 'graceful' };
128
+ } else {
129
+ console.warn(`⚠️ Warning: Failed to force-kill process ${pid} (${name}): ${err.message}`);
130
+ return { pid, name, method: 'failed' };
131
+ }
132
+ }
133
+
134
+ return { pid, name, method: 'force-killed' };
135
+ }
136
+
137
+ module.exports = {
138
+ findProcessesInDirectory,
139
+ terminateProcessGracefully
140
+ };
@@ -11,7 +11,7 @@ async function readStandards() {
11
11
 
12
12
  // Check file exists
13
13
  if (!fs.existsSync(standardsPath)) {
14
- throw new Error('Production standards file not found. Run external-transition first.');
14
+ throw new Error('Production standards not found');
15
15
  }
16
16
 
17
17
  // Read file with specific error handling
@@ -98,7 +98,18 @@ function validateStandardsFormat(standards) {
98
98
  }
99
99
 
100
100
  if (!standard.acceptance) {
101
- throw new Error(`Standard ${standard.id} missing required field: acceptance`);
101
+ throw new Error(`Invalid standard format: missing acceptance field`);
102
+ }
103
+
104
+ // Validate infrastructure-scoped standards have all required fields
105
+ if (standard.scope === 'infrastructure') {
106
+ if (!standard.reasoning) {
107
+ throw new Error(`Invalid standard format: missing reasoning field`);
108
+ }
109
+
110
+ if (!standard.pattern) {
111
+ throw new Error(`Invalid standard format: missing pattern field`);
112
+ }
102
113
  }
103
114
  }
104
115
  }
@@ -0,0 +1,85 @@
1
+ // Production standards writer - saves production standards to file
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { validateStandardsFormat } = require('./production-standards-reader');
5
+
6
+ /**
7
+ * Save production standards to file
8
+ * @param {Object} standards - Production standards object to save
9
+ * @returns {void}
10
+ */
11
+ function saveStandards(standards) {
12
+ const standardsPath = path.join(process.cwd(), '.jettypod', 'production-standards.json');
13
+
14
+ // Validate standards format before saving
15
+ try {
16
+ validateStandardsFormat(standards);
17
+ } catch (validationErr) {
18
+ throw new Error(
19
+ `Invalid production standards format:\n` +
20
+ ` ${validationErr.message}\n` +
21
+ ` File: ${standardsPath}`
22
+ );
23
+ }
24
+
25
+ // Ensure .jettypod directory exists
26
+ ensureJettypodDir();
27
+
28
+ // Write JSON with pretty formatting
29
+ try {
30
+ fs.writeFileSync(standardsPath, JSON.stringify(standards, null, 2), 'utf8');
31
+ } catch (writeErr) {
32
+ // Provide specific error messages based on error code
33
+ let errorMessage = `Failed to save production standards to ${standardsPath}:\n`;
34
+
35
+ if (writeErr.code === 'EACCES') {
36
+ errorMessage += ` Permission denied. Check file permissions:\n`;
37
+ errorMessage += ` chmod 644 ${standardsPath}`;
38
+ } else if (writeErr.code === 'ENOSPC') {
39
+ errorMessage += ` No space left on device. Free up disk space and try again.`;
40
+ } else if (writeErr.code === 'EROFS') {
41
+ errorMessage += ` File system is read-only. Check mount permissions.`;
42
+ } else {
43
+ errorMessage += ` ${writeErr.message}`;
44
+ }
45
+
46
+ throw new Error(errorMessage);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Ensure .jettypod directory exists
52
+ * @returns {void}
53
+ */
54
+ function ensureJettypodDir() {
55
+ const jettypodDir = path.join(process.cwd(), '.jettypod');
56
+
57
+ if (!fs.existsSync(jettypodDir)) {
58
+ try {
59
+ fs.mkdirSync(jettypodDir, { recursive: true });
60
+ } catch (mkdirErr) {
61
+ // Provide specific error messages based on error code
62
+ let errorMessage = `Failed to create .jettypod directory at ${jettypodDir}:\n`;
63
+
64
+ if (mkdirErr.code === 'EACCES') {
65
+ errorMessage += ` Permission denied. Check parent directory permissions:\n`;
66
+ errorMessage += ` chmod 755 ${path.dirname(jettypodDir)}`;
67
+ } else if (mkdirErr.code === 'ENOSPC') {
68
+ errorMessage += ` No space left on device. Free up disk space and try again.`;
69
+ } else if (mkdirErr.code === 'EROFS') {
70
+ errorMessage += ` File system is read-only. Check mount permissions.`;
71
+ } else if (mkdirErr.code === 'EEXIST') {
72
+ errorMessage += ` Path exists but is not a directory. Remove the file:\n`;
73
+ errorMessage += ` rm ${jettypodDir}`;
74
+ } else {
75
+ errorMessage += ` ${mkdirErr.message}`;
76
+ }
77
+
78
+ throw new Error(errorMessage);
79
+ }
80
+ }
81
+ }
82
+
83
+ module.exports = {
84
+ saveStandards
85
+ };
@@ -0,0 +1,135 @@
1
+ const { execSync } = require('child_process');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Validates BDD infrastructure by running cucumber dry-run
6
+ * @param {string} featureFilePath - Absolute path to .feature file
7
+ * @returns {Promise<{success: boolean, errors: Array, warnings: Array}>}
8
+ */
9
+ async function validateBddInfrastructure(featureFilePath) {
10
+ const result = {
11
+ success: false,
12
+ errors: [],
13
+ warnings: []
14
+ };
15
+
16
+ try {
17
+ // Run cucumber-js with --dry-run flag
18
+ const output = execSync(
19
+ `npx cucumber-js --dry-run "${featureFilePath}"`,
20
+ {
21
+ cwd: path.dirname(path.dirname(path.dirname(featureFilePath))),
22
+ encoding: 'utf-8',
23
+ timeout: 5000,
24
+ stdio: 'pipe'
25
+ }
26
+ );
27
+
28
+ // If we get here, dry-run passed
29
+ result.success = true;
30
+
31
+ // Check for warnings in output (like deprecation notices)
32
+ if (output.includes('deprecated') || output.includes('warning')) {
33
+ result.warnings.push({
34
+ type: 'deprecation',
35
+ message: output.match(/warning.*/gi)?.[0] || 'Deprecation warning found'
36
+ });
37
+ }
38
+
39
+ } catch (error) {
40
+ // Dry-run failed - parse the error output
41
+ const errorOutput = error.stderr || error.stdout || error.message;
42
+
43
+ result.success = false;
44
+ result.errors = parseValidationErrors(errorOutput, featureFilePath);
45
+ }
46
+
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * Parses cucumber dry-run error output to extract specific issues
52
+ * @param {string} errorOutput - stderr/stdout from cucumber-js
53
+ * @param {string} featureFilePath - Path to feature file for context
54
+ * @returns {Array<{type: string, message: string, details: object}>}
55
+ */
56
+ function parseValidationErrors(errorOutput, featureFilePath) {
57
+ const errors = [];
58
+
59
+ // Check for undefined steps
60
+ const undefinedStepsMatch = errorOutput.match(/Undefined\. Implement with the following snippet:([\s\S]*?)(?=\n\n|\nScenario:|\nFeature:|$)/g);
61
+ if (undefinedStepsMatch) {
62
+ undefinedStepsMatch.forEach(match => {
63
+ const stepText = match.match(/(?:Given|When|Then|And|But)\('(.+?)'/)?.[1];
64
+ if (stepText) {
65
+ errors.push({
66
+ type: 'undefined_step',
67
+ message: `Undefined step: "${stepText}"`,
68
+ details: {
69
+ stepText,
70
+ file: featureFilePath,
71
+ suggestedImplementation: match
72
+ }
73
+ });
74
+ }
75
+ });
76
+ }
77
+
78
+ // Check for syntax errors
79
+ const syntaxErrorMatch = errorOutput.match(/Parse error[^\n]*\n[^\n]*/);
80
+ if (syntaxErrorMatch) {
81
+ errors.push({
82
+ type: 'syntax_error',
83
+ message: syntaxErrorMatch[0],
84
+ details: {
85
+ file: featureFilePath
86
+ }
87
+ });
88
+ }
89
+
90
+ // Check for duplicate step definitions
91
+ const duplicateMatch = errorOutput.match(/Multiple step definitions match:([\s\S]*?)(?=\n\n|$)/g);
92
+ if (duplicateMatch) {
93
+ duplicateMatch.forEach(match => {
94
+ const stepFiles = match.match(/features\/[^\s]+\.js:\d+/g) || [];
95
+ errors.push({
96
+ type: 'duplicate_step',
97
+ message: 'Multiple step definitions match the same pattern',
98
+ details: {
99
+ files: stepFiles,
100
+ fullMessage: match
101
+ }
102
+ });
103
+ });
104
+ }
105
+
106
+ // Check for missing cucumber installation
107
+ if (errorOutput.includes('cucumber-js: command not found') ||
108
+ errorOutput.includes('Cannot find module')) {
109
+ errors.push({
110
+ type: 'missing_dependency',
111
+ message: 'Cucumber is not installed or not found',
112
+ details: {
113
+ suggestion: 'Run: npm install --save-dev @cucumber/cucumber'
114
+ }
115
+ });
116
+ }
117
+
118
+ // If no specific errors found but validation failed, add generic error
119
+ if (errors.length === 0) {
120
+ errors.push({
121
+ type: 'unknown',
122
+ message: 'Validation failed with unknown error',
123
+ details: {
124
+ output: errorOutput.substring(0, 500)
125
+ }
126
+ });
127
+ }
128
+
129
+ return errors;
130
+ }
131
+
132
+ module.exports = {
133
+ validateBddInfrastructure,
134
+ parseValidationErrors
135
+ };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Formats dry-run validation results with colors and actionable guidance
3
+ */
4
+
5
+ const chalk = require('chalk');
6
+
7
+ /**
8
+ * Formats validation result for display
9
+ * @param {object} result - Result from validateBddInfrastructure()
10
+ * @param {string} featurePath - Path to feature file being validated
11
+ * @returns {string} Formatted output
12
+ */
13
+ function formatValidationResult(result, featurePath) {
14
+ const lines = [];
15
+
16
+ // Always show the validation header first
17
+ lines.push(chalk.cyan('🧪 Validating BDD infrastructure...'));
18
+ lines.push('');
19
+
20
+ if (result.success) {
21
+ lines.push(chalk.green('✅ BDD infrastructure validated'));
22
+ lines.push(chalk.green('✅ Step definitions match scenario steps'));
23
+
24
+ if (result.warnings && result.warnings.length > 0) {
25
+ lines.push('');
26
+ lines.push(chalk.yellow('⚠️ Warnings:'));
27
+ result.warnings.forEach(warning => {
28
+ lines.push(chalk.yellow(` • ${warning.message}`));
29
+ });
30
+ }
31
+ } else {
32
+ lines.push(chalk.red('❌ BDD infrastructure validation failed'));
33
+ lines.push('');
34
+
35
+ // Check error types to provide specific messaging
36
+ const hasUndefinedSteps = result.errors.some(e => e.type === 'undefined_step');
37
+ const hasSyntaxErrors = result.errors.some(e => e.type === 'syntax_error');
38
+
39
+ if (hasUndefinedSteps) {
40
+ lines.push(chalk.red('❌ Undefined steps detected:'));
41
+ lines.push('');
42
+ }
43
+
44
+ if (result.errors && result.errors.length > 0) {
45
+ result.errors.forEach(error => {
46
+ const formatted = formatError(error, featurePath);
47
+ lines.push(formatted);
48
+ lines.push('');
49
+ });
50
+
51
+ // Provide specific guidance based on error types
52
+ if (hasUndefinedSteps) {
53
+ lines.push(chalk.yellow('⚠️ Fix step definitions before proceeding'));
54
+ } else if (hasSyntaxErrors) {
55
+ lines.push(chalk.yellow('⚠️ Fix the feature file before proceeding'));
56
+ } else {
57
+ lines.push(chalk.yellow('⚠️ Fix the issues above before proceeding to implementation.'));
58
+ }
59
+ }
60
+ }
61
+
62
+ return lines.join('\n');
63
+ }
64
+
65
+ /**
66
+ * Formats a single validation error with guidance
67
+ * @param {object} error - Error object from parseValidationErrors()
68
+ * @param {string} featurePath - Path to feature file
69
+ * @returns {string} Formatted error message
70
+ */
71
+ function formatError(error, featurePath) {
72
+ const lines = [];
73
+
74
+ switch (error.type) {
75
+ case 'undefined_step':
76
+ lines.push(chalk.red(` Undefined step: ${chalk.bold(error.details.stepText)}`));
77
+ lines.push(chalk.gray(` File: ${error.details.file}`));
78
+ lines.push('');
79
+ lines.push(chalk.cyan(' 💡 Add this step definition:'));
80
+ lines.push(chalk.gray(indent(extractStepDefinition(error.details.suggestedImplementation), 5)));
81
+ break;
82
+
83
+ case 'syntax_error':
84
+ lines.push(chalk.red(` Syntax error in feature file`));
85
+ lines.push(chalk.gray(` ${error.message}`));
86
+ lines.push('');
87
+ lines.push(chalk.cyan(' 💡 Check your Gherkin syntax:'));
88
+ lines.push(chalk.gray(' • Ensure proper indentation'));
89
+ lines.push(chalk.gray(' • Verify Feature/Scenario keywords are correctly placed'));
90
+ lines.push(chalk.gray(' • Check for missing colons after keywords'));
91
+ break;
92
+
93
+ case 'duplicate_step':
94
+ lines.push(chalk.red(` Duplicate step definitions detected`));
95
+ lines.push(chalk.gray(` ${error.message}`));
96
+ if (error.details.files && error.details.files.length > 0) {
97
+ lines.push('');
98
+ lines.push(chalk.cyan(' 💡 Found in these files:'));
99
+ error.details.files.forEach(file => {
100
+ lines.push(chalk.gray(` • ${file}`));
101
+ });
102
+ lines.push('');
103
+ lines.push(chalk.gray(' Remove or rename one of the duplicate definitions'));
104
+ }
105
+ break;
106
+
107
+ case 'missing_dependency':
108
+ lines.push(chalk.red(` Missing dependency: Cucumber`));
109
+ lines.push(chalk.gray(` ${error.message}`));
110
+ lines.push('');
111
+ lines.push(chalk.cyan(' 💡 Install Cucumber:'));
112
+ lines.push(chalk.gray(` ${error.details.suggestion}`));
113
+ break;
114
+
115
+ case 'unknown':
116
+ default:
117
+ lines.push(chalk.red(` Validation failed: ${error.message}`));
118
+ if (error.details && error.details.output) {
119
+ lines.push('');
120
+ lines.push(chalk.gray(' Error output:'));
121
+ lines.push(chalk.gray(indent(error.details.output, 5)));
122
+ }
123
+ break;
124
+ }
125
+
126
+ return lines.join('\n');
127
+ }
128
+
129
+ /**
130
+ * Extracts step definition code from cucumber suggestion
131
+ * @param {string} suggestion - Cucumber's suggested implementation
132
+ * @returns {string} Clean step definition code
133
+ */
134
+ function extractStepDefinition(suggestion) {
135
+ // Extract the step definition code (everything after "Implement with the following snippet:")
136
+ const match = suggestion.match(/(?:Given|When|Then|And|But)\([^)]+\)[^{]*\{[^}]*\}/);
137
+ if (match) {
138
+ return match[0];
139
+ }
140
+
141
+ // Fallback: return first few lines of the suggestion
142
+ const lines = suggestion.split('\n').slice(1, 4);
143
+ return lines.join('\n');
144
+ }
145
+
146
+ /**
147
+ * Indents text by a given number of spaces
148
+ * @param {string} text - Text to indent
149
+ * @param {number} spaces - Number of spaces to indent
150
+ * @returns {string} Indented text
151
+ */
152
+ function indent(text, spaces) {
153
+ const prefix = ' '.repeat(spaces);
154
+ return text.split('\n').map(line => prefix + line).join('\n');
155
+ }
156
+
157
+ module.exports = {
158
+ formatValidationResult,
159
+ formatError
160
+ };