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.
- package/.nvmrc +1 -0
- package/docs/COMPLETE-TESTING-STRATEGY.md +970 -0
- package/docs/DECISIONS.md +10 -12
- package/docs/NODE_VERSION.md +83 -0
- package/docs/TDD-INFRASTRUCTURE-STRATEGY.md +1374 -0
- package/docs/TESTING-FOR-NON-ENGINEERS.md +1588 -0
- package/docs/TESTING-STRATEGY-AUDIT.md +698 -0
- package/hooks/post-checkout +17 -0
- package/hooks/post-merge +17 -0
- package/hooks/pre-commit +30 -0
- package/jettypod.js +259 -120
- package/lib/coverage-tracker.js +218 -0
- package/lib/database.js +2 -0
- package/lib/db-export.js +192 -0
- package/lib/db-import.js +193 -0
- package/lib/external-transition-handler.js +32 -0
- package/lib/git-hook-helpers.js +174 -0
- package/lib/git-root.js +90 -0
- package/lib/infrastructure-chore-generator.js +45 -0
- package/lib/install-hooks.js +52 -0
- package/lib/jettypod-backup.js +238 -0
- package/lib/merge-lock.js +193 -0
- package/lib/migrations/012-add-worktree-path.js +38 -0
- package/lib/migrations/013-worktrees-table.js +86 -0
- package/lib/migrations/014-migrate-worktree-data.js +161 -0
- package/lib/migrations/015-merge-locks-table.js +67 -0
- package/lib/pattern-finder.js +152 -0
- package/lib/process-manager.js +140 -0
- package/lib/production-standards-reader.js +13 -2
- package/lib/production-standards-writer.js +85 -0
- package/lib/skills/feature-planning/dry-run-validator.js +135 -0
- package/lib/skills/feature-planning/validation-formatter.js +160 -0
- package/lib/smart-conflict-detection.js +168 -0
- package/lib/smart-fetch-rebase.js +614 -0
- package/lib/step-definition-parser.js +76 -0
- package/lib/unit-test-generator.js +232 -0
- package/lib/verification-command-generator.js +66 -0
- package/lib/worktree-diagnostics.js +413 -0
- package/lib/worktree-facade.js +174 -0
- package/lib/worktree-manager.js +636 -0
- package/lib/worktree-reconciler.js +429 -0
- package/package.json +30 -3
- package/skills-templates/external-transition/SKILL.md +34 -3
- package/skills-templates/feature-planning/SKILL.md +190 -24
- package/skills-templates/production-mode/SKILL.md +127 -9
- package/skills-templates/speed-mode/SKILL.md +454 -51
- package/skills-templates/stable-mode/SKILL.md +285 -76
- package/.claude/PROTECT_SKILLS.md +0 -28
- package/.claude/settings.json +0 -24
- package/.claude/settings.local.json +0 -16
- package/.claude/skills/epic-planning/SKILL.md +0 -297
- package/.claude/skills/external-transition/SKILL.md +0 -384
- package/.claude/skills/feature-planning/SKILL.md +0 -464
- package/.claude/skills/production-mode/SKILL.md +0 -369
- package/.claude/skills/speed-mode/SKILL.md +0 -481
- package/.claude/skills/stable-mode/SKILL.md +0 -713
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T16-15-10-070Z/epic-discover/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/stable-mode/SKILL.md +0 -673
- package/.devpod/current-work.json +0 -10
- package/.devpod/work.db +0 -0
- package/.github/workflows/test-safety.yml +0 -85
- package/.jettypod/config.json +0 -5
- package/.jettypod/current-work.json +0 -10
- package/.jettypod/hooks/README.md +0 -77
- package/.jettypod/hooks/protect-claude-md.js +0 -338
- package/.jettypod/test-work.db +0 -0
- package/.jettypod/work.db +0 -0
- package/CLAUDE.md +0 -49
- package/SPEED-STABLE-AUDIT.md +0 -853
- package/SYSTEM-BEHAVIOR.md +0 -2199
- package/TEST_SAFETY_AUDIT.md +0 -314
- package/TEST_SAFETY_IMPLEMENTATION.md +0 -97
- package/cucumber-report.html +0 -45
- package/dist/devpod-linux +0 -0
- package/dist/devpod-macos +0 -0
- package/dist/devpod-win.exe +0 -0
- package/docs/features/jettypod-standards-explained.md +0 -543
- package/docs/features/standards-inventory.md +0 -257
- package/features/auto-generate-production-chores.feature +0 -13
- package/features/backlog-command.feature +0 -26
- package/features/backlog-filtering-production.feature +0 -10
- package/features/claude-md-protection/steps.js +0 -498
- package/features/decisions/index.js +0 -490
- package/features/decisions/index.test.js +0 -208
- package/features/fix-text-wrapping.feature +0 -42
- package/features/git-hooks/git-hooks.feature +0 -30
- package/features/git-hooks/index.js +0 -93
- package/features/git-hooks/index.test.js +0 -137
- package/features/git-hooks/post-commit +0 -56
- package/features/git-hooks/post-merge +0 -47
- package/features/git-hooks/pre-commit +0 -28
- package/features/git-hooks/simple-steps.js +0 -53
- package/features/git-hooks/simple-test.feature +0 -10
- package/features/git-hooks/steps.js +0 -196
- package/features/jettypod-update-command.feature +0 -46
- package/features/mode-prompts/index.js +0 -95
- package/features/mode-prompts/simple-steps.js +0 -44
- package/features/mode-prompts/simple-test.feature +0 -9
- package/features/mode-prompts/validation.test.js +0 -120
- package/features/multiple-claude-instances.feature +0 -121
- package/features/production-mode-skill.feature +0 -121
- package/features/refactor-mode/steps.js +0 -217
- package/features/refactor-mode.feature +0 -49
- package/features/simplify-external-transition.feature +0 -166
- package/features/skills-update/index.test.js +0 -216
- package/features/step_definitions/backlog-command.steps.js +0 -37
- package/features/step_definitions/fix-text-wrapping.steps.js +0 -271
- package/features/step_definitions/multiple-claude-instances.steps.js +0 -621
- package/features/step_definitions/production-mode-skill.steps.js +0 -862
- package/features/step_definitions/simplify-external-transition.steps.js +0 -370
- package/features/step_definitions/terminal-logo.steps.js +0 -145
- package/features/step_definitions/update-command.steps.js +0 -183
- package/features/support/hooks.js +0 -9
- package/features/terminal-logo/index.js +0 -39
- package/features/terminal-logo/terminal-logo.feature +0 -30
- package/features/update-command/index.js +0 -181
- package/features/update-command/index.test.js +0 -225
- package/features/work-commands/bug-workflow-display.feature +0 -22
- package/features/work-commands/index.js +0 -498
- package/features/work-commands/simple-steps.js +0 -69
- package/features/work-commands/stable-tests.feature +0 -57
- package/features/work-commands/steps.js +0 -1174
- package/features/work-commands/validation.test.js +0 -88
- package/features/work-commands/work-commands.feature +0 -13
- package/features/work-tracking/discovery-validation.test.js +0 -228
- package/features/work-tracking/index.js +0 -1921
- package/features/work-tracking/mode-required.feature +0 -112
- package/features/work-tracking/phase-tracking.test.js +0 -482
- package/features/work-tracking/prototype-tracking.test.js +0 -485
- package/features/work-tracking/tree-view.test.js +0 -310
- package/features/work-tracking/work-set-mode.feature +0 -71
- package/features/work-tracking/work-start-mode.feature +0 -88
- package/full-test.txt +0 -0
- package/lib/bug-workflow.test.js +0 -177
- package/lib/claudemd.test.js +0 -195
- package/lib/config.test.js +0 -511
- package/lib/constants.test.js +0 -164
- package/lib/current-work.test.js +0 -146
- package/lib/database-project-config.test.js +0 -111
- package/lib/database.test.js +0 -106
- package/lib/decisions-generator.test.js +0 -457
- package/lib/decisions-helpers.test.js +0 -310
- package/lib/git-coordinator.js +0 -167
- package/lib/git.test.js +0 -145
- package/lib/migrations/002-default-work-item-modes.test.js +0 -351
- package/lib/production-chore-generator.test.js +0 -432
- package/lib/production-context-detector.test.js +0 -277
- package/lib/production-scenario-appender.test.js +0 -235
- package/lib/production-scenario-validator.test.js +0 -246
- package/lib/production-standards-reader.test.js +0 -270
- package/lib/project-state.test.js +0 -92
- package/lib/push-queue.js +0 -417
- package/lib/queue-processor.js +0 -74
- package/lib/test-helpers.js +0 -202
- package/lib/test-helpers.test.js +0 -255
- package/prototypes/2025-01-11-production-mode-autonomous.js +0 -119
- package/prototypes/2025-01-11-production-mode-collaborative.js +0 -166
- package/prototypes/2025-01-11-production-mode-guided.js +0 -217
- package/prototypes/2025-01-11-production-mode-smart-context.js +0 -347
- package/prototypes/2025-01-11-production-standards-example.md +0 -204
- package/prototypes/2025-11-10-backlog-filtering-tree-aware.js +0 -242
- package/prototypes/test/index.html +0 -1
- package/setup-dist-repo.sh +0 -68
- package/test-production-standards-engine.js +0 -130
- package/test-results.json +0 -2195
- package/test-safety-check.sh +0 -80
- package/work-item-tracking-plan.md +0 -199
- /package/{.jettypod/devpod.db → jettypod.db} +0 -0
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { createTestEnvironment } = require('../../lib/test-helpers');
|
|
4
|
-
const { execSync } = require('child_process');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* End-to-end tests for skills update functionality
|
|
8
|
-
* Tests the BDD scenarios from features/always-update-skills.feature
|
|
9
|
-
*/
|
|
10
|
-
describe('Skills Update on JettyPod Init', () => {
|
|
11
|
-
let testEnv;
|
|
12
|
-
let jettypodPath;
|
|
13
|
-
let skillsSourceDir;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
testEnv = createTestEnvironment();
|
|
17
|
-
process.chdir(testEnv.testDir);
|
|
18
|
-
|
|
19
|
-
// Path to jettypod script (relative to test directory)
|
|
20
|
-
jettypodPath = path.join(__dirname, '..', '..', 'jettypod.js');
|
|
21
|
-
|
|
22
|
-
// SAFETY: Create mock skills source directory in /tmp/ test environment, NOT in actual project
|
|
23
|
-
skillsSourceDir = path.join(testEnv.testDir, 'mock-jettypod-skills');
|
|
24
|
-
if (!fs.existsSync(skillsSourceDir)) {
|
|
25
|
-
fs.mkdirSync(skillsSourceDir, { recursive: true });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Create sample skills for testing
|
|
29
|
-
const speedModeSkillDir = path.join(skillsSourceDir, 'speed-mode');
|
|
30
|
-
fs.mkdirSync(speedModeSkillDir, { recursive: true });
|
|
31
|
-
fs.writeFileSync(
|
|
32
|
-
path.join(speedModeSkillDir, 'SKILL.md'),
|
|
33
|
-
'# Speed Mode Skill\nTest content for speed mode skill'
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const stableModeSkillDir = path.join(skillsSourceDir, 'stable-mode');
|
|
37
|
-
fs.mkdirSync(stableModeSkillDir, { recursive: true });
|
|
38
|
-
fs.writeFileSync(
|
|
39
|
-
path.join(stableModeSkillDir, 'SKILL.md'),
|
|
40
|
-
'# Stable Mode Skill\nTest content for stable mode skill'
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// Configure jettypod to use mock skills source
|
|
44
|
-
process.env.JETTYPOD_SKILLS_SOURCE_DIR = skillsSourceDir;
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
// Clean up environment variable
|
|
49
|
-
delete process.env.JETTYPOD_SKILLS_SOURCE_DIR;
|
|
50
|
-
testEnv.cleanup();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('Scenario: Skills are updated when initializing existing project', () => {
|
|
54
|
-
test('should backup existing skills before updating', () => {
|
|
55
|
-
// Given: I have a project with old .claude/skills/ directory
|
|
56
|
-
const claudeDir = path.join(testEnv.testDir, '.claude');
|
|
57
|
-
const oldSkillsDir = path.join(claudeDir, 'skills');
|
|
58
|
-
fs.mkdirSync(oldSkillsDir, { recursive: true });
|
|
59
|
-
|
|
60
|
-
const oldSkillDir = path.join(oldSkillsDir, 'old-skill');
|
|
61
|
-
fs.mkdirSync(oldSkillDir, { recursive: true });
|
|
62
|
-
fs.writeFileSync(
|
|
63
|
-
path.join(oldSkillDir, 'SKILL.md'),
|
|
64
|
-
'# Old Skill\nThis is an old skill that should be backed up'
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// When: I run jettypod in the project directory
|
|
68
|
-
execSync(`node ${jettypodPath}`, { stdio: 'pipe', env: { ...process.env } });
|
|
69
|
-
|
|
70
|
-
// Then: old skills are backed up before replacement
|
|
71
|
-
const backupDirs = fs.readdirSync(claudeDir).filter(name => name.startsWith('skills.backup-'));
|
|
72
|
-
expect(backupDirs.length).toBe(1);
|
|
73
|
-
|
|
74
|
-
const backupDir = path.join(claudeDir, backupDirs[0]);
|
|
75
|
-
expect(fs.existsSync(backupDir)).toBe(true);
|
|
76
|
-
|
|
77
|
-
// Verify old skill is in backup
|
|
78
|
-
const backedUpSkillFile = path.join(backupDir, 'old-skill', 'SKILL.md');
|
|
79
|
-
expect(fs.existsSync(backedUpSkillFile)).toBe(true);
|
|
80
|
-
const backedUpContent = fs.readFileSync(backedUpSkillFile, 'utf8');
|
|
81
|
-
expect(backedUpContent).toContain('This is an old skill that should be backed up');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('should replace skills with latest from jettypod', () => {
|
|
85
|
-
// Given: I have a project with old .claude/skills/ directory
|
|
86
|
-
const claudeDir = path.join(testEnv.testDir, '.claude');
|
|
87
|
-
const oldSkillsDir = path.join(claudeDir, 'skills');
|
|
88
|
-
fs.mkdirSync(oldSkillsDir, { recursive: true });
|
|
89
|
-
|
|
90
|
-
const oldSkillDir = path.join(oldSkillsDir, 'old-skill');
|
|
91
|
-
fs.mkdirSync(oldSkillDir, { recursive: true });
|
|
92
|
-
fs.writeFileSync(path.join(oldSkillDir, 'SKILL.md'), '# Old Skill');
|
|
93
|
-
|
|
94
|
-
// When: I run jettypod in the project directory
|
|
95
|
-
execSync(`node ${jettypodPath}`, { stdio: 'pipe', env: { ...process.env } });
|
|
96
|
-
|
|
97
|
-
// Then: .claude/skills/ is replaced with latest skills from jettypod
|
|
98
|
-
const newSkillsDir = path.join(testEnv.testDir, '.claude', 'skills');
|
|
99
|
-
expect(fs.existsSync(newSkillsDir)).toBe(true);
|
|
100
|
-
|
|
101
|
-
// Old skill should be gone
|
|
102
|
-
expect(fs.existsSync(path.join(newSkillsDir, 'old-skill'))).toBe(false);
|
|
103
|
-
|
|
104
|
-
// New skills should be present
|
|
105
|
-
expect(fs.existsSync(path.join(newSkillsDir, 'speed-mode', 'SKILL.md'))).toBe(true);
|
|
106
|
-
expect(fs.existsSync(path.join(newSkillsDir, 'stable-mode', 'SKILL.md'))).toBe(true);
|
|
107
|
-
|
|
108
|
-
// Verify content is from jettypod source
|
|
109
|
-
const speedModeContent = fs.readFileSync(
|
|
110
|
-
path.join(newSkillsDir, 'speed-mode', 'SKILL.md'),
|
|
111
|
-
'utf8'
|
|
112
|
-
);
|
|
113
|
-
expect(speedModeContent).toContain('Test content for speed mode skill');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test('should handle concurrent inits with unique backup names', () => {
|
|
117
|
-
// Given: I have a project with .claude/skills/ directory
|
|
118
|
-
const claudeDir = path.join(testEnv.testDir, '.claude');
|
|
119
|
-
const skillsDir = path.join(claudeDir, 'skills');
|
|
120
|
-
fs.mkdirSync(skillsDir, { recursive: true });
|
|
121
|
-
fs.writeFileSync(path.join(skillsDir, 'test.md'), 'test');
|
|
122
|
-
|
|
123
|
-
// Manually create a backup with current timestamp format
|
|
124
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
125
|
-
const existingBackup = path.join(claudeDir, `skills.backup-${timestamp}`);
|
|
126
|
-
fs.mkdirSync(existingBackup, { recursive: true });
|
|
127
|
-
fs.writeFileSync(path.join(existingBackup, 'existing.md'), 'existing');
|
|
128
|
-
|
|
129
|
-
// Restore skills dir for the actual init
|
|
130
|
-
if (fs.existsSync(skillsDir)) {
|
|
131
|
-
fs.rmSync(skillsDir, { recursive: true, force: true });
|
|
132
|
-
}
|
|
133
|
-
fs.mkdirSync(skillsDir, { recursive: true });
|
|
134
|
-
fs.writeFileSync(path.join(skillsDir, 'test.md'), 'test');
|
|
135
|
-
|
|
136
|
-
// When: I run jettypod (which creates another backup with same timestamp)
|
|
137
|
-
execSync(`node ${jettypodPath}`, { stdio: 'pipe', env: { ...process.env } });
|
|
138
|
-
|
|
139
|
-
// Then: backup directory should have counter appended
|
|
140
|
-
const backupDirs = fs.readdirSync(claudeDir)
|
|
141
|
-
.filter(name => name.startsWith('skills.backup-'))
|
|
142
|
-
.sort();
|
|
143
|
-
|
|
144
|
-
// Should have at least 2 backups (the manual one and the new one)
|
|
145
|
-
expect(backupDirs.length).toBeGreaterThanOrEqual(2);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('Scenario: Skills are copied when initializing new project', () => {
|
|
150
|
-
test('should create .claude/skills/ with latest skills', () => {
|
|
151
|
-
// Given: I have a new project with no .claude/ directory
|
|
152
|
-
// (testEnv starts clean)
|
|
153
|
-
|
|
154
|
-
// When: I run jettypod to initialize
|
|
155
|
-
execSync(`node ${jettypodPath}`, { stdio: 'pipe', env: { ...process.env } });
|
|
156
|
-
|
|
157
|
-
// Then: .claude/skills/ is created with latest skills from jettypod
|
|
158
|
-
const skillsDir = path.join(testEnv.testDir, '.claude', 'skills');
|
|
159
|
-
expect(fs.existsSync(skillsDir)).toBe(true);
|
|
160
|
-
|
|
161
|
-
// And: all skills are ready to use
|
|
162
|
-
expect(fs.existsSync(path.join(skillsDir, 'speed-mode', 'SKILL.md'))).toBe(true);
|
|
163
|
-
expect(fs.existsSync(path.join(skillsDir, 'stable-mode', 'SKILL.md'))).toBe(true);
|
|
164
|
-
|
|
165
|
-
const speedModeContent = fs.readFileSync(
|
|
166
|
-
path.join(skillsDir, 'speed-mode', 'SKILL.md'),
|
|
167
|
-
'utf8'
|
|
168
|
-
);
|
|
169
|
-
expect(speedModeContent).toContain('Test content for speed mode skill');
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test('should not create backup when no existing skills', () => {
|
|
173
|
-
// Given: I have a new project with no .claude/ directory
|
|
174
|
-
// When: I run jettypod to initialize
|
|
175
|
-
execSync(`node ${jettypodPath}`, { stdio: 'pipe', env: { ...process.env } });
|
|
176
|
-
|
|
177
|
-
// Then: no backup should be created
|
|
178
|
-
const claudeDir = path.join(testEnv.testDir, '.claude');
|
|
179
|
-
const backupDirs = fs.readdirSync(claudeDir).filter(name => name.startsWith('skills.backup-'));
|
|
180
|
-
expect(backupDirs.length).toBe(0);
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
describe('Error handling validation', () => {
|
|
185
|
-
test('should handle source directory missing gracefully', () => {
|
|
186
|
-
// Remove skills source directory
|
|
187
|
-
if (fs.existsSync(skillsSourceDir)) {
|
|
188
|
-
fs.rmSync(skillsSourceDir, { recursive: true, force: true });
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Should not throw, just warn
|
|
192
|
-
expect(() => {
|
|
193
|
-
execSync(`node ${jettypodPath}`, { stdio: 'pipe', env: { ...process.env } });
|
|
194
|
-
}).not.toThrow();
|
|
195
|
-
|
|
196
|
-
// .claude should still be created
|
|
197
|
-
expect(fs.existsSync(path.join(testEnv.testDir, '.claude'))).toBe(true);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test('should handle empty source directory gracefully', () => {
|
|
201
|
-
// Empty the skills source directory
|
|
202
|
-
if (fs.existsSync(skillsSourceDir)) {
|
|
203
|
-
fs.rmSync(skillsSourceDir, { recursive: true, force: true });
|
|
204
|
-
}
|
|
205
|
-
fs.mkdirSync(skillsSourceDir, { recursive: true });
|
|
206
|
-
|
|
207
|
-
// Should not throw, just warn
|
|
208
|
-
expect(() => {
|
|
209
|
-
execSync(`node ${jettypodPath}`, { stdio: 'pipe', env: { ...process.env } });
|
|
210
|
-
}).not.toThrow();
|
|
211
|
-
|
|
212
|
-
// .claude should still be created
|
|
213
|
-
expect(fs.existsSync(path.join(testEnv.testDir, '.claude'))).toBe(true);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const { Given, Then } = require('@cucumber/cucumber');
|
|
2
|
-
|
|
3
|
-
// Test data setup
|
|
4
|
-
Given('I have work items in various statuses', async function () {
|
|
5
|
-
// Speed mode will implement: Create test work items with different statuses
|
|
6
|
-
// (backlog, todo, in_progress, done, cancelled)
|
|
7
|
-
this.testWorkItems = null; // Placeholder for speed mode
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
// Output assertions
|
|
11
|
-
Then('I see only active work items \\(backlog, todo, in_progress)', function () {
|
|
12
|
-
// Speed mode will implement: Parse output and verify only active items shown
|
|
13
|
-
// Check output contains items with status: backlog, todo, in_progress
|
|
14
|
-
// Check output does NOT contain items with status: done, cancelled
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
Then('I see all work items regardless of status', function () {
|
|
18
|
-
// Speed mode will implement: Parse output and verify all items shown
|
|
19
|
-
// Check output contains items with all statuses
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
Then('I see only completed work items \\(done, cancelled)', function () {
|
|
23
|
-
// Speed mode will implement: Parse output and verify only completed items shown
|
|
24
|
-
// Check output contains items with status: done, cancelled
|
|
25
|
-
// Check output does NOT contain items with status: backlog, todo, in_progress
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
Then('I see the items in a tree hierarchy', function () {
|
|
29
|
-
// Speed mode will implement: Verify tree structure in output
|
|
30
|
-
// Check for indentation or tree symbols (├──, └──, etc.)
|
|
31
|
-
// Check parent-child relationships are preserved
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
Then('completed items are hidden', function () {
|
|
35
|
-
// Speed mode will implement: Verify no completed items in output
|
|
36
|
-
// Check output does NOT contain items with status: done, cancelled
|
|
37
|
-
});
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
const { Given, When, Then, Before, After, setWorldConstructor } = require('@cucumber/cucumber');
|
|
2
|
-
const assert = require('assert');
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
|
|
7
|
-
const jettypodPath = path.join(__dirname, '../../jettypod.js');
|
|
8
|
-
|
|
9
|
-
// Global testDir for compatibility with work-commands steps
|
|
10
|
-
global.testDir = undefined;
|
|
11
|
-
|
|
12
|
-
// Setup test directory before each scenario
|
|
13
|
-
Before({ tags: '@text-wrapping' }, async function () {
|
|
14
|
-
// Create unique test directory for this scenario
|
|
15
|
-
this.testDir = path.join('/tmp', 'jettypod-text-wrap-test-' + Date.now() + '-' + Math.random().toString(36).substring(7));
|
|
16
|
-
global.testDir = this.testDir;
|
|
17
|
-
|
|
18
|
-
// Clean up if it exists
|
|
19
|
-
if (fs.existsSync(this.testDir) && this.testDir.startsWith('/tmp/')) {
|
|
20
|
-
fs.rmSync(this.testDir, { recursive: true, force: true });
|
|
21
|
-
}
|
|
22
|
-
fs.mkdirSync(this.testDir, { recursive: true });
|
|
23
|
-
|
|
24
|
-
// Initialize jettypod in test directory
|
|
25
|
-
execSync(`node ${jettypodPath} init`, {
|
|
26
|
-
cwd: this.testDir,
|
|
27
|
-
env: { ...process.env, NODE_ENV: 'test' },
|
|
28
|
-
stdio: 'pipe'
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
this.terminalWidth = 80; // Default
|
|
32
|
-
|
|
33
|
-
// Helper to run jettypod commands in this test's directory
|
|
34
|
-
this.runJettypod = (args, termWidth = this.terminalWidth) => {
|
|
35
|
-
const env = {
|
|
36
|
-
...process.env,
|
|
37
|
-
NODE_ENV: 'test',
|
|
38
|
-
COLUMNS: termWidth.toString()
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const output = execSync(`node ${jettypodPath} ${args}`, {
|
|
43
|
-
cwd: this.testDir,
|
|
44
|
-
encoding: 'utf-8',
|
|
45
|
-
env
|
|
46
|
-
});
|
|
47
|
-
this.commandOutput = output;
|
|
48
|
-
this.output = output;
|
|
49
|
-
return output;
|
|
50
|
-
} catch (err) {
|
|
51
|
-
const output = err.stdout || '';
|
|
52
|
-
this.commandOutput = output;
|
|
53
|
-
this.output = output;
|
|
54
|
-
return output;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Cleanup after each scenario
|
|
60
|
-
After({ tags: '@text-wrapping' }, function () {
|
|
61
|
-
if (this.testDir && fs.existsSync(this.testDir) && this.testDir.startsWith('/tmp/')) {
|
|
62
|
-
fs.rmSync(this.testDir, { recursive: true, force: true });
|
|
63
|
-
}
|
|
64
|
-
global.testDir = undefined;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Test setup - terminal width and test items
|
|
68
|
-
Given('I have a feature with a title longer than the terminal width', function () {
|
|
69
|
-
const longTitle = 'This is an extremely long feature title that will definitely exceed any reasonable terminal width and should wrap at word boundaries when displayed in the backlog output';
|
|
70
|
-
this.runJettypod(`work create feature "${longTitle}" "Test description"`);
|
|
71
|
-
this.terminalWidth = 80;
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
Given('I have a feature with a long description', function () {
|
|
75
|
-
const longDesc = 'This is a very detailed description that explains the feature in great depth and will definitely need to wrap across multiple lines when displayed in the terminal because it contains so much information about what the feature does and why it exists and how it should be implemented';
|
|
76
|
-
this.runJettypod(`work create feature "Test Feature" "${longDesc}"`);
|
|
77
|
-
this.terminalWidth = 80;
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
Given('I have nested items with long titles', function () {
|
|
81
|
-
// Create epic
|
|
82
|
-
const epicOutput = this.runJettypod('work create epic "Test Epic" "Epic description"');
|
|
83
|
-
const epicMatch = epicOutput.match(/Created epic #(\d+)/);
|
|
84
|
-
const epicId = epicMatch ? epicMatch[1] : '1';
|
|
85
|
-
|
|
86
|
-
const longTitle1 = 'This is a very long feature title that should wrap when displayed as a nested item under the epic';
|
|
87
|
-
const longTitle2 = 'Another extremely long feature title to test tree structure alignment with wrapped text';
|
|
88
|
-
|
|
89
|
-
this.runJettypod(`work create feature "${longTitle1}" "" --parent=${epicId}`);
|
|
90
|
-
this.runJettypod(`work create feature "${longTitle2}" "" --parent=${epicId}`);
|
|
91
|
-
|
|
92
|
-
this.epicId = epicId;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
Given('the terminal width is {int} columns', function (width) {
|
|
96
|
-
this.terminalWidth = width;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
Given('I have items with titles that wrap at {int} columns', function (columns) {
|
|
100
|
-
this.terminalWidth = columns;
|
|
101
|
-
const longTitle = 'A' + ' very'.repeat(20) + ' long title';
|
|
102
|
-
this.runJettypod(`work create feature "${longTitle}" ""`);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Actions
|
|
106
|
-
When(/^I run jettypod (.+) with current terminal width$/, function (args) {
|
|
107
|
-
this.runJettypod(args, this.terminalWidth);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
When('the terminal is resized to {int} columns', function (columns) {
|
|
111
|
-
this.terminalWidth = columns;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Assertions - word boundary wrapping
|
|
115
|
-
Then('the title wraps at word boundaries', function () {
|
|
116
|
-
assert(this.output, 'Should have output');
|
|
117
|
-
const lines = this.output.split('\n');
|
|
118
|
-
|
|
119
|
-
for (let i = 0; i < lines.length - 1; i++) {
|
|
120
|
-
const currentLine = lines[i];
|
|
121
|
-
const nextLine = lines[i + 1];
|
|
122
|
-
|
|
123
|
-
if (currentLine && nextLine) {
|
|
124
|
-
// Check if the current line ends with a hyphen (mid-word break)
|
|
125
|
-
// or if when we remove trailing/leading spaces there's a word split
|
|
126
|
-
const trimmedCurrent = currentLine.trimEnd();
|
|
127
|
-
const trimmedNext = nextLine.trimStart();
|
|
128
|
-
|
|
129
|
-
if (trimmedCurrent && trimmedNext) {
|
|
130
|
-
const lastChar = trimmedCurrent[trimmedCurrent.length - 1];
|
|
131
|
-
|
|
132
|
-
// If line ends with hyphen, that's intentional for very long words
|
|
133
|
-
if (lastChar === '-') continue;
|
|
134
|
-
|
|
135
|
-
// If line ends with alphanumeric and next line starts with alphanumeric,
|
|
136
|
-
// but current line didn't have trailing space, then word was split
|
|
137
|
-
const hadTrailingSpace = currentLine !== trimmedCurrent;
|
|
138
|
-
const nextHasLeadingSpace = nextLine !== trimmedNext;
|
|
139
|
-
|
|
140
|
-
if (!hadTrailingSpace && !nextHasLeadingSpace) {
|
|
141
|
-
const isLastAlpha = /[a-zA-Z0-9]/.test(lastChar);
|
|
142
|
-
const firstChar = trimmedNext[0];
|
|
143
|
-
const isFirstAlpha = /[a-zA-Z0-9]/.test(firstChar);
|
|
144
|
-
|
|
145
|
-
assert(!(isLastAlpha && isFirstAlpha),
|
|
146
|
-
`Word split between lines ${i} and ${i+1}: "${trimmedCurrent}" | "${trimmedNext}"`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
Then('continuation lines align with the content indentation', function () {
|
|
154
|
-
assert(this.output, 'Should have output');
|
|
155
|
-
// Verified by implementation design
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
Then('no words are split mid-character', function () {
|
|
159
|
-
assert(this.output, 'Should have output');
|
|
160
|
-
const lines = this.output.split('\n');
|
|
161
|
-
|
|
162
|
-
for (let i = 0; i < lines.length - 1; i++) {
|
|
163
|
-
const currentLine = lines[i];
|
|
164
|
-
const nextLine = lines[i + 1];
|
|
165
|
-
|
|
166
|
-
if (currentLine && nextLine) {
|
|
167
|
-
const trimmedCurrent = currentLine.trimEnd();
|
|
168
|
-
const trimmedNext = nextLine.trimStart();
|
|
169
|
-
|
|
170
|
-
if (trimmedCurrent && trimmedNext) {
|
|
171
|
-
const lastChar = trimmedCurrent[trimmedCurrent.length - 1];
|
|
172
|
-
if (lastChar === '-') continue; // Hyphenation is allowed
|
|
173
|
-
|
|
174
|
-
const hadTrailingSpace = currentLine !== trimmedCurrent;
|
|
175
|
-
const nextHasLeadingSpace = nextLine !== trimmedNext;
|
|
176
|
-
|
|
177
|
-
if (!hadTrailingSpace && !nextHasLeadingSpace) {
|
|
178
|
-
const isLastAlpha = /[a-zA-Z0-9]/.test(lastChar);
|
|
179
|
-
const firstChar = trimmedNext[0];
|
|
180
|
-
const isFirstAlpha = /[a-zA-Z0-9]/.test(firstChar);
|
|
181
|
-
|
|
182
|
-
assert(!(isLastAlpha && isFirstAlpha), 'Words should not be split mid-character');
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
Then('the description wraps at word boundaries', function () {
|
|
190
|
-
assert(this.output, 'Should have output');
|
|
191
|
-
const lines = this.output.split('\n');
|
|
192
|
-
|
|
193
|
-
for (let i = 0; i < lines.length - 1; i++) {
|
|
194
|
-
const currentLine = lines[i];
|
|
195
|
-
const nextLine = lines[i + 1];
|
|
196
|
-
|
|
197
|
-
if (currentLine && nextLine && currentLine.includes('Description:')) {
|
|
198
|
-
const trimmedCurrent = currentLine.trimEnd();
|
|
199
|
-
const trimmedNext = nextLine.trimStart();
|
|
200
|
-
|
|
201
|
-
if (trimmedCurrent && trimmedNext) {
|
|
202
|
-
const lastChar = trimmedCurrent[trimmedCurrent.length - 1];
|
|
203
|
-
if (lastChar === '-') continue; // Hyphenation is allowed
|
|
204
|
-
|
|
205
|
-
const hadTrailingSpace = currentLine !== trimmedCurrent;
|
|
206
|
-
const nextHasLeadingSpace = nextLine !== trimmedNext;
|
|
207
|
-
|
|
208
|
-
if (!hadTrailingSpace && !nextHasLeadingSpace) {
|
|
209
|
-
const isLastAlpha = /[a-zA-Z0-9]/.test(lastChar);
|
|
210
|
-
const firstChar = trimmedNext[0];
|
|
211
|
-
const isFirstAlpha = /[a-zA-Z0-9]/.test(firstChar);
|
|
212
|
-
|
|
213
|
-
assert(!(isLastAlpha && isFirstAlpha), 'Description words should not be split');
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
Then('continuation lines have proper {string} prefix indentation', function (prefix) {
|
|
221
|
-
assert(this.output, 'Should have output');
|
|
222
|
-
// Verified by implementation design
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
Then('the text fits within terminal width', function () {
|
|
226
|
-
assert(this.output, 'Should have output');
|
|
227
|
-
const lines = this.output.split('\n');
|
|
228
|
-
const maxWidth = this.terminalWidth;
|
|
229
|
-
|
|
230
|
-
for (const line of lines) {
|
|
231
|
-
// Strip ANSI codes
|
|
232
|
-
const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, '');
|
|
233
|
-
assert(cleanLine.length <= maxWidth,
|
|
234
|
-
`Line exceeds terminal width (${cleanLine.length} > ${maxWidth}): "${cleanLine.substring(0, 50)}..."`);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// Assertions - tree structure
|
|
239
|
-
Then('tree connectors \\(├──, └──) display correctly', function () {
|
|
240
|
-
assert(this.output, 'Should have output');
|
|
241
|
-
const hasTreeConnectors = this.output.includes('├──') || this.output.includes('└──');
|
|
242
|
-
assert(hasTreeConnectors, 'Output should contain tree connectors');
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
Then('wrapped continuation lines align under the item content', function () {
|
|
246
|
-
assert(this.output, 'Should have output');
|
|
247
|
-
// Verified by implementation design
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
Then('tree structure remains visually clear', function () {
|
|
251
|
-
assert(this.output, 'Should have output');
|
|
252
|
-
const hasVerticalLines = this.output.includes('│');
|
|
253
|
-
const hasTreeConnectors = this.output.includes('├──') || this.output.includes('└──');
|
|
254
|
-
assert(hasVerticalLines || hasTreeConnectors, 'Tree structure should be present');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
Then('text wraps to fit {int} columns', function (columns) {
|
|
258
|
-
assert(this.output, 'Should have output');
|
|
259
|
-
const lines = this.output.split('\n');
|
|
260
|
-
|
|
261
|
-
for (const line of lines) {
|
|
262
|
-
const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, '');
|
|
263
|
-
assert(cleanLine.length <= columns,
|
|
264
|
-
`Line exceeds ${columns} columns: ${cleanLine.length}`);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
Then('alignment adjusts to the new width', function () {
|
|
269
|
-
assert(this.output, 'Should have output');
|
|
270
|
-
// Tested by running command again with new width
|
|
271
|
-
});
|