jettypod 3.0.1
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/.claude/PROTECT_SKILLS.md +28 -0
- package/.claude/settings.json +24 -0
- package/.claude/settings.local.json +16 -0
- package/.claude/skills/epic-discover/SKILL.md +262 -0
- package/.claude/skills/feature-discover/SKILL.md +393 -0
- package/.claude/skills/speed-mode/SKILL.md +364 -0
- package/.claude/skills/stable-mode/SKILL.md +591 -0
- package/.github/workflows/test-safety.yml +85 -0
- package/README.md +25 -0
- package/SPEED-STABLE-AUDIT.md +853 -0
- package/SYSTEM-BEHAVIOR.md +1241 -0
- package/TEST_SAFETY_AUDIT.md +314 -0
- package/TEST_SAFETY_IMPLEMENTATION.md +97 -0
- package/cucumber.js +8 -0
- package/docs/COMMAND_REFERENCE.md +903 -0
- package/docs/DECISIONS.md +68 -0
- package/docs/README.md +48 -0
- package/docs/STANDARDS-SYSTEM-DOCUMENTATION.md +374 -0
- package/docs/TEST-REWRITE-PLAN.md +261 -0
- package/docs/ai-test-writing-requirements.md +219 -0
- package/docs/claude-code-skills.md +607 -0
- package/docs/core-jettypod-methodology/comprehensive-jettypod-methodology.md +582 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-comprehensive-standards.md +1222 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-operating-guide.md +3399 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-technical-checklist.md +1325 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-vibe-coding-framework.md +1544 -0
- package/docs/core-jettypod-methodology/deprecated/prompt-engineering-guide.md +320 -0
- package/docs/core-jettypod-methodology/deprecated/vibe-coding-cheatsheet (1).md +516 -0
- package/docs/core-jettypod-methodology/deprecated/vibe-coding-framework.md +1544 -0
- package/docs/features/jettypod-standards-explained.md +543 -0
- package/docs/features/standards-inventory.md +257 -0
- package/docs/gap-analysis-current-vs-comprehensive-methodology.md +939 -0
- package/docs/jettypod-system-overview.md +409 -0
- package/features/auto-generate-production-chores.feature +14 -0
- package/features/claude-md-protection/steps.js +487 -0
- package/features/decisions/index.js +490 -0
- package/features/decisions/index.test.js +208 -0
- package/features/git-hooks/git-hooks.feature +30 -0
- package/features/git-hooks/index.js +93 -0
- package/features/git-hooks/index.test.js +137 -0
- package/features/git-hooks/post-commit +56 -0
- package/features/git-hooks/post-merge +47 -0
- package/features/git-hooks/pre-commit +28 -0
- package/features/git-hooks/simple-steps.js +53 -0
- package/features/git-hooks/simple-test.feature +10 -0
- package/features/git-hooks/steps.js +196 -0
- package/features/jettypod-update-command.feature +46 -0
- package/features/mode-prompts/index.js +95 -0
- package/features/mode-prompts/simple-steps.js +44 -0
- package/features/mode-prompts/simple-test.feature +9 -0
- package/features/mode-prompts/validation.test.js +120 -0
- package/features/refactor-mode/steps.js +217 -0
- package/features/refactor-mode.feature +49 -0
- package/features/skills-update/index.test.js +216 -0
- package/features/step_definitions/auto-generate-production-chores.steps.js +162 -0
- package/features/step_definitions/terminal-logo.steps.js +145 -0
- package/features/step_definitions/update-command.steps.js +183 -0
- package/features/terminal-logo/index.js +39 -0
- package/features/terminal-logo/terminal-logo.feature +30 -0
- package/features/update-command/index.js +181 -0
- package/features/update-command/index.test.js +225 -0
- package/features/work-commands/bug-workflow-display.feature +22 -0
- package/features/work-commands/index.js +311 -0
- package/features/work-commands/simple-steps.js +69 -0
- package/features/work-commands/stable-tests.feature +57 -0
- package/features/work-commands/steps.js +1120 -0
- package/features/work-commands/validation.test.js +88 -0
- package/features/work-commands/work-commands.feature +13 -0
- package/features/work-tracking/discovery-validation.test.js +228 -0
- package/features/work-tracking/index.js +1511 -0
- package/features/work-tracking/mode-required.feature +112 -0
- package/features/work-tracking/phase-tracking.test.js +482 -0
- package/features/work-tracking/prototype-tracking.test.js +485 -0
- package/features/work-tracking/tree-view.test.js +310 -0
- package/features/work-tracking/work-set-mode.feature +71 -0
- package/features/work-tracking/work-start-mode.feature +88 -0
- package/full-test.txt +0 -0
- package/install.sh +89 -0
- package/jettypod.js +1640 -0
- package/lib/bug-workflow.js +94 -0
- package/lib/bug-workflow.test.js +177 -0
- package/lib/claudemd.js +130 -0
- package/lib/claudemd.test.js +195 -0
- package/lib/comprehensive-standards-full.json +1778 -0
- package/lib/config.js +181 -0
- package/lib/config.test.js +511 -0
- package/lib/constants.js +107 -0
- package/lib/constants.test.js +164 -0
- package/lib/current-work.js +130 -0
- package/lib/current-work.test.js +146 -0
- package/lib/database-project-config.test.js +107 -0
- package/lib/database.js +256 -0
- package/lib/database.test.js +106 -0
- package/lib/decisions-generator.js +102 -0
- package/lib/decisions-generator.test.js +457 -0
- package/lib/decisions-helpers.js +119 -0
- package/lib/decisions-helpers.test.js +310 -0
- package/lib/discovery-checkpoint.js +83 -0
- package/lib/docs-generator.js +280 -0
- package/lib/external-checklist.js +177 -0
- package/lib/git.js +142 -0
- package/lib/git.test.js +145 -0
- package/lib/logo.js +3 -0
- package/lib/migrations/001-epic-to-parent.js +24 -0
- package/lib/migrations/002-default-work-item-modes.js +37 -0
- package/lib/migrations/002-default-work-item-modes.test.js +351 -0
- package/lib/migrations/003-epic-discovery-fields.js +52 -0
- package/lib/migrations/004-discovery-decisions-table.js +32 -0
- package/lib/migrations/005-migrate-decision-data.js +62 -0
- package/lib/migrations/006-feature-phase-field.js +61 -0
- package/lib/migrations/007-prototype-tracking.js +38 -0
- package/lib/migrations/008-scenario-file-field.js +24 -0
- package/lib/migrations/index.js +74 -0
- package/lib/production-helpers.js +69 -0
- package/lib/project-state.test.js +92 -0
- package/lib/test-helpers.js +184 -0
- package/lib/test-helpers.test.js +255 -0
- package/package.json +36 -0
- package/prototypes/test/index.html +1 -0
- package/setup-dist-repo.sh +68 -0
- package/test-safety-check.sh +80 -0
- package/work-item-tracking-plan.md +199 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Feature: JettyPod update command
|
|
2
|
+
Self-updating jettypod and skills refresh with a single command
|
|
3
|
+
|
|
4
|
+
Epic: JettyPod and Skills Auto-Update
|
|
5
|
+
Approach: Full self-update - Check npm, update jettypod, refresh skills
|
|
6
|
+
|
|
7
|
+
Scenario: User runs jettypod update when new version is available
|
|
8
|
+
Given jettypod version 3.0.0 is installed
|
|
9
|
+
And npm registry has version 3.1.0 available
|
|
10
|
+
When I run update command "jettypod update"
|
|
11
|
+
Then jettypod checks npm registry for latest version
|
|
12
|
+
And jettypod shows "New version available: 3.1.0 (current: 3.0.0)"
|
|
13
|
+
And jettypod downloads and installs version 3.1.0
|
|
14
|
+
And skills are refreshed in current project
|
|
15
|
+
And jettypod shows "✅ JettyPod updated to 3.1.0"
|
|
16
|
+
|
|
17
|
+
Scenario: User runs jettypod update when already on latest version
|
|
18
|
+
Given jettypod version 3.1.0 is installed
|
|
19
|
+
And npm registry has version 3.1.0 available
|
|
20
|
+
When I run update command "jettypod update"
|
|
21
|
+
Then jettypod checks npm registry for latest version
|
|
22
|
+
And jettypod shows "Already on latest version: 3.1.0"
|
|
23
|
+
And skills are refreshed in current project
|
|
24
|
+
|
|
25
|
+
Scenario: User runs jettypod update without internet connection
|
|
26
|
+
Given jettypod is installed
|
|
27
|
+
And there is no internet connection
|
|
28
|
+
When I run update command "jettypod update"
|
|
29
|
+
Then jettypod shows "Cannot check for updates: network error"
|
|
30
|
+
And skills are still refreshed in current project using existing jettypod
|
|
31
|
+
|
|
32
|
+
Scenario: Command rename - jettypod without init creates new project
|
|
33
|
+
Given I am in a directory without .claude/
|
|
34
|
+
When I run update command "jettypod"
|
|
35
|
+
Then jettypod initializes the project
|
|
36
|
+
And .claude/ directory is created
|
|
37
|
+
And CLAUDE.md is created
|
|
38
|
+
And skills are installed
|
|
39
|
+
|
|
40
|
+
Scenario: Backwards compatibility - jettypod init still works
|
|
41
|
+
Given I am in a directory without .claude/
|
|
42
|
+
When I run update command "jettypod init"
|
|
43
|
+
Then jettypod initializes the project
|
|
44
|
+
And .claude/ directory is created
|
|
45
|
+
And CLAUDE.md is created
|
|
46
|
+
And skills are installed
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Prompt user for work item status update
|
|
8
|
+
* @param {Object} currentWork - Current work item with id, title, and status
|
|
9
|
+
* @returns {Promise<string|null>} New status or null if skipped
|
|
10
|
+
* @throws {Error} If currentWork is invalid or missing required fields
|
|
11
|
+
*/
|
|
12
|
+
function promptForStatusUpdate(currentWork) {
|
|
13
|
+
// Validate input
|
|
14
|
+
if (!currentWork || typeof currentWork !== 'object') {
|
|
15
|
+
return Promise.reject(new Error('Invalid currentWork: must be an object'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!currentWork.id || !currentWork.title || !currentWork.status) {
|
|
19
|
+
return Promise.reject(new Error('Invalid currentWork: missing id, title, or status'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input: process.stdin,
|
|
25
|
+
output: process.stdout
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(`\nCurrent work: [#${currentWork.id}] ${currentWork.title} (${currentWork.status})`);
|
|
29
|
+
rl.question('Update status? [in_progress/blocked/done/skip]: ', (answer) => {
|
|
30
|
+
rl.close();
|
|
31
|
+
|
|
32
|
+
const status = answer.trim();
|
|
33
|
+
if (status === 'skip' || status === '') {
|
|
34
|
+
resolve(null);
|
|
35
|
+
} else if (['in_progress', 'blocked', 'done', 'todo', 'backlog'].includes(status)) {
|
|
36
|
+
resolve(status);
|
|
37
|
+
} else {
|
|
38
|
+
console.log('Invalid status, skipping update');
|
|
39
|
+
resolve(null);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Update work item status in database and current work file
|
|
47
|
+
* Uses shared updateStatus from work-tracking for consistency and epic auto-close logic
|
|
48
|
+
* @param {number} workItemId - ID of work item to update
|
|
49
|
+
* @param {string} newStatus - New status value
|
|
50
|
+
* @returns {Promise<void>}
|
|
51
|
+
* @throws {Error} If workItemId is invalid, newStatus is invalid, database not found, or file operations fail
|
|
52
|
+
*/
|
|
53
|
+
async function updateWorkItemStatus(workItemId, newStatus) {
|
|
54
|
+
// Validate inputs
|
|
55
|
+
if (!workItemId || isNaN(workItemId) || workItemId < 1) {
|
|
56
|
+
return Promise.reject(new Error('Invalid work item ID'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const validStatuses = ['in_progress', 'blocked', 'done', 'todo', 'backlog', 'cancelled'];
|
|
60
|
+
if (!newStatus || !validStatuses.includes(newStatus)) {
|
|
61
|
+
return Promise.reject(new Error(`Invalid status: ${newStatus}`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const jettypodDir = path.join(process.cwd(), '.jettypod');
|
|
65
|
+
const { getDbPath } = require('../../lib/database');
|
|
66
|
+
const dbPath = getDbPath();
|
|
67
|
+
const currentWorkPath = path.join(jettypodDir, 'current-work.json');
|
|
68
|
+
|
|
69
|
+
// Check database exists
|
|
70
|
+
if (!fs.existsSync(dbPath)) {
|
|
71
|
+
return Promise.reject(new Error('Work database not found. Run: jettypod init'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Use shared updateStatus from work-tracking (includes epic auto-close logic)
|
|
75
|
+
const { updateStatus } = require('../work-tracking');
|
|
76
|
+
await updateStatus(workItemId, newStatus);
|
|
77
|
+
|
|
78
|
+
// Update current work file if it exists
|
|
79
|
+
if (fs.existsSync(currentWorkPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const currentWork = JSON.parse(fs.readFileSync(currentWorkPath, 'utf-8'));
|
|
82
|
+
currentWork.status = newStatus;
|
|
83
|
+
fs.writeFileSync(currentWorkPath, JSON.stringify(currentWork, null, 2));
|
|
84
|
+
} catch (fileErr) {
|
|
85
|
+
return Promise.reject(new Error(`Failed to update current work file: ${fileErr.message}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`✓ Work item #${workItemId} status updated to ${newStatus}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
promptForStatusUpdate,
|
|
94
|
+
updateWorkItemStatus
|
|
95
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { Given, When, Then } = require('@cucumber/cucumber');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const testDir = path.join('/tmp', 'mode-prompts-' + Date.now());
|
|
8
|
+
let originalDir;
|
|
9
|
+
|
|
10
|
+
Given('I have jettypod with a work item in progress', function () {
|
|
11
|
+
originalDir = process.cwd();
|
|
12
|
+
// SAFETY: Only delete if testDir is in /tmp
|
|
13
|
+
if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
|
|
14
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
17
|
+
process.chdir(testDir);
|
|
18
|
+
|
|
19
|
+
execSync('git init', { stdio: 'pipe' });
|
|
20
|
+
execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
|
|
21
|
+
execSync('git config user.name "Test"', { stdio: 'pipe' });
|
|
22
|
+
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe' });
|
|
23
|
+
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create feature "Test"`, { stdio: 'pipe' });
|
|
24
|
+
execSync(`node ${path.join(originalDir, 'jettypod.js')} work start 1`, { stdio: 'pipe' });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
When('I check current work exists', function () {
|
|
28
|
+
const currentWorkPath = path.join(testDir, '.jettypod', 'current-work.json');
|
|
29
|
+
this.currentWorkExists = fs.existsSync(currentWorkPath);
|
|
30
|
+
if (this.currentWorkExists) {
|
|
31
|
+
this.currentWork = JSON.parse(fs.readFileSync(currentWorkPath, 'utf-8'));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
Then('the work item has a status', function () {
|
|
36
|
+
assert(this.currentWorkExists, 'Current work file should exist');
|
|
37
|
+
assert(this.currentWork.status, 'Work item should have a status');
|
|
38
|
+
|
|
39
|
+
// Cleanup
|
|
40
|
+
if (fs.existsSync(testDir)) {
|
|
41
|
+
process.chdir(originalDir);
|
|
42
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Feature: Mode Change Prompts
|
|
2
|
+
As a developer
|
|
3
|
+
I want to update work status when switching modes
|
|
4
|
+
So my work state stays current
|
|
5
|
+
|
|
6
|
+
Scenario: Integration - mode change with work item present
|
|
7
|
+
Given I have jettypod with a work item in progress
|
|
8
|
+
When I check current work exists
|
|
9
|
+
Then the work item has a status
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Unit tests for mode-prompts validation logic
|
|
2
|
+
// These test the error handling and validation without user interaction
|
|
3
|
+
|
|
4
|
+
const { VALID_STATUSES } = require('../../lib/constants');
|
|
5
|
+
|
|
6
|
+
describe('Mode Prompts - Validation', () => {
|
|
7
|
+
describe('promptForStatusUpdate validation', () => {
|
|
8
|
+
test('should validate currentWork is an object', () => {
|
|
9
|
+
const invalidInputs = [null, undefined, 'string', 123, [], true];
|
|
10
|
+
invalidInputs.forEach(input => {
|
|
11
|
+
const isValid = !!(input && typeof input === 'object' && !Array.isArray(input));
|
|
12
|
+
expect(isValid).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('should validate currentWork has required fields', () => {
|
|
17
|
+
const invalidObjects = [
|
|
18
|
+
{},
|
|
19
|
+
{ id: 1 },
|
|
20
|
+
{ id: 1, title: 'Test' },
|
|
21
|
+
{ title: 'Test', status: 'todo' },
|
|
22
|
+
{ id: 1, status: 'todo' }
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
invalidObjects.forEach(obj => {
|
|
26
|
+
const hasRequired = !!(obj.id && obj.title && obj.status);
|
|
27
|
+
expect(hasRequired).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should accept valid currentWork objects', () => {
|
|
32
|
+
const validObjects = [
|
|
33
|
+
{ id: 1, title: 'Test', status: 'todo' },
|
|
34
|
+
{ id: 2, title: 'Feature', status: 'in_progress', type: 'feature' }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
validObjects.forEach(obj => {
|
|
38
|
+
const hasRequired = !!(obj.id && obj.title && obj.status);
|
|
39
|
+
expect(hasRequired).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should validate status values', () => {
|
|
44
|
+
const validStatuses = ['in_progress', 'blocked', 'done', 'todo', 'backlog'];
|
|
45
|
+
const invalidStatuses = ['invalid', 'foo', '', null, undefined];
|
|
46
|
+
|
|
47
|
+
validStatuses.forEach(status => {
|
|
48
|
+
expect(validStatuses.includes(status)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
invalidStatuses.forEach(status => {
|
|
52
|
+
const isValid = !!(status && validStatuses.includes(status));
|
|
53
|
+
expect(isValid).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('should accept skip or empty as null', () => {
|
|
58
|
+
const nullInputs = ['skip', ''];
|
|
59
|
+
nullInputs.forEach(input => {
|
|
60
|
+
const shouldBeNull = input === 'skip' || input === '';
|
|
61
|
+
expect(shouldBeNull).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('updateWorkItemStatus validation', () => {
|
|
67
|
+
test('should validate work item ID is numeric', () => {
|
|
68
|
+
const invalidIds = [null, undefined, 'abc', '', {}, []];
|
|
69
|
+
invalidIds.forEach(id => {
|
|
70
|
+
const isValid = !!(id && !isNaN(id) && id >= 1);
|
|
71
|
+
expect(isValid).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('should validate work item ID is positive', () => {
|
|
76
|
+
expect(!!(0 && !isNaN(0) && 0 >= 1)).toBe(false);
|
|
77
|
+
expect(!!(-1 && !isNaN(-1) && -1 >= 1)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should accept valid work item IDs', () => {
|
|
81
|
+
const validIds = [1, 2, 100, 999];
|
|
82
|
+
validIds.forEach(id => {
|
|
83
|
+
const isValid = id && !isNaN(id) && id >= 1;
|
|
84
|
+
expect(isValid).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should validate status is in valid statuses', () => {
|
|
89
|
+
const validStatuses = ['in_progress', 'blocked', 'done', 'todo', 'backlog', 'cancelled'];
|
|
90
|
+
const invalidStatuses = ['invalid', 'foo', '', null, undefined];
|
|
91
|
+
|
|
92
|
+
validStatuses.forEach(status => {
|
|
93
|
+
expect(validStatuses.includes(status)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
invalidStatuses.forEach(status => {
|
|
97
|
+
const isValid = !!(status && validStatuses.includes(status));
|
|
98
|
+
expect(isValid).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('Database path validation', () => {
|
|
104
|
+
test('should check jettypod directory exists', () => {
|
|
105
|
+
const fs = require('fs');
|
|
106
|
+
const path = require('path');
|
|
107
|
+
|
|
108
|
+
const nonExistentPath = '/nonexistent/path/.jettypod';
|
|
109
|
+
expect(fs.existsSync(nonExistentPath)).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should check database file exists', () => {
|
|
113
|
+
const fs = require('fs');
|
|
114
|
+
const path = require('path');
|
|
115
|
+
|
|
116
|
+
const nonExistentDb = '/nonexistent/path/.jettypod/work.db';
|
|
117
|
+
expect(fs.existsSync(nonExistentDb)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const { Given, When, Then } = require('@cucumber/cucumber');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
// State for scenarios
|
|
5
|
+
let auditResults = null;
|
|
6
|
+
let selectedCategory = null;
|
|
7
|
+
let detailedBreakdown = null;
|
|
8
|
+
let createdEpic = null;
|
|
9
|
+
let createdChores = [];
|
|
10
|
+
|
|
11
|
+
Given('I have existing work items in the system', function () {
|
|
12
|
+
// For integration test - work items exist in test DB
|
|
13
|
+
this.existingWorkItems = true;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
Given("I'm in a project with technical debt", function () {
|
|
17
|
+
// Mock project with technical debt
|
|
18
|
+
this.projectHasDebt = true;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
Given('I see the audit menu', function () {
|
|
22
|
+
// Previous step set auditResults
|
|
23
|
+
assert(auditResults, 'Audit results should be available');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
Given('I see the detailed breakdown for {string}', function (category) {
|
|
27
|
+
// Previous step set detailedBreakdown
|
|
28
|
+
assert(detailedBreakdown, 'Detailed breakdown should be available');
|
|
29
|
+
assert.strictEqual(detailedBreakdown.category, category);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
Given('I see recommendations for Storage Pattern', function () {
|
|
33
|
+
// Recommendations were shown in previous step
|
|
34
|
+
this.hasRecommendations = true;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
When('I enter refactor mode and audit the codebase', function () {
|
|
38
|
+
// Simulate entering refactor mode with audit
|
|
39
|
+
auditResults = {
|
|
40
|
+
codeSmells: { count: 5, summary: 'Long functions, duplicated code' },
|
|
41
|
+
featureSpecific: { count: 3, summary: 'features/auth/ has patterns' },
|
|
42
|
+
architectural: { count: 15, summary: 'Storage patterns duplicated' },
|
|
43
|
+
performance: { count: 4, summary: 'Unnecessary re-renders' }
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
When('I enter refactor mode', function () {
|
|
48
|
+
// Simulate entering refactor mode
|
|
49
|
+
auditResults = {
|
|
50
|
+
codeSmells: { count: 5, summary: 'Long functions' },
|
|
51
|
+
featureSpecific: { count: 3, summary: 'features/auth/' },
|
|
52
|
+
architectural: { count: 15, summary: 'Storage patterns' },
|
|
53
|
+
performance: { count: 4, summary: 'Re-renders' }
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
When('I select {string} category', function (category) {
|
|
58
|
+
selectedCategory = category;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
When('I pick {string} category', function (category) {
|
|
62
|
+
selectedCategory = category;
|
|
63
|
+
// Generate detailed breakdown
|
|
64
|
+
detailedBreakdown = {
|
|
65
|
+
category,
|
|
66
|
+
items: [
|
|
67
|
+
{
|
|
68
|
+
name: 'Storage Pattern',
|
|
69
|
+
count: 15,
|
|
70
|
+
type: '🟠Cross-Cutting',
|
|
71
|
+
impact: 'High - affects all features',
|
|
72
|
+
time: '2 hours',
|
|
73
|
+
files: ['auth.js', 'expenses.js', '+13 more'],
|
|
74
|
+
example: 'localStorage.getItem() duplicated'
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
When('I approve creating chores for storage pattern', function () {
|
|
81
|
+
// User approved - create chores
|
|
82
|
+
createdEpic = {
|
|
83
|
+
id: 999,
|
|
84
|
+
title: 'Refactor: Architectural patterns',
|
|
85
|
+
type: 'epic'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
createdChores = [
|
|
89
|
+
{
|
|
90
|
+
id: 1000,
|
|
91
|
+
title: 'Create shared storage util',
|
|
92
|
+
epicId: 999,
|
|
93
|
+
type: 'chore'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 1001,
|
|
97
|
+
title: 'Migrate auth to storage util',
|
|
98
|
+
epicId: 999,
|
|
99
|
+
type: 'chore'
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
When('Claude analyzes impact vs time', function () {
|
|
105
|
+
// Claude analyzes and generates recommendations
|
|
106
|
+
this.recommendations = {
|
|
107
|
+
quickWin: 'Storage Pattern (quick win, high impact)',
|
|
108
|
+
warning: 'State Management (needs Discovery Mode first)'
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
When('I say {string}', function (userInput) {
|
|
113
|
+
if (userInput === 'create chores') {
|
|
114
|
+
// User approved - create chores
|
|
115
|
+
createdEpic = {
|
|
116
|
+
id: 999,
|
|
117
|
+
title: 'Refactor: Architectural patterns',
|
|
118
|
+
type: 'epic'
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
createdChores = [
|
|
122
|
+
{
|
|
123
|
+
id: 1000,
|
|
124
|
+
title: 'Create shared storage util',
|
|
125
|
+
epicId: 999,
|
|
126
|
+
type: 'chore'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 1001,
|
|
130
|
+
title: 'Migrate auth to storage util',
|
|
131
|
+
epicId: 999,
|
|
132
|
+
type: 'chore'
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
Then('Claude audits the codebase', function () {
|
|
139
|
+
assert(auditResults, 'Audit should have run');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
Then('I see categorized menu with counts:', function (dataTable) {
|
|
143
|
+
const expectedCategories = dataTable.hashes();
|
|
144
|
+
|
|
145
|
+
// Verify we have results for each category
|
|
146
|
+
assert(auditResults.codeSmells, 'Should have code smells results');
|
|
147
|
+
assert(auditResults.featureSpecific, 'Should have feature-specific results');
|
|
148
|
+
assert(auditResults.architectural, 'Should have architectural results');
|
|
149
|
+
assert(auditResults.performance, 'Should have performance results');
|
|
150
|
+
|
|
151
|
+
// Verify counts are present
|
|
152
|
+
assert(auditResults.codeSmells.count > 0, 'Code smells count should be > 0');
|
|
153
|
+
assert(auditResults.architectural.count > 0, 'Architectural count should be > 0');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
Then('I see detailed items with:', function (dataTable) {
|
|
157
|
+
const expectedFields = dataTable.hashes();
|
|
158
|
+
|
|
159
|
+
assert(detailedBreakdown, 'Should have detailed breakdown');
|
|
160
|
+
assert(detailedBreakdown.items.length > 0, 'Should have at least one item');
|
|
161
|
+
|
|
162
|
+
const item = detailedBreakdown.items[0];
|
|
163
|
+
assert(item.type, 'Item should have type');
|
|
164
|
+
assert(item.impact, 'Item should have impact');
|
|
165
|
+
assert(item.time, 'Item should have time estimate');
|
|
166
|
+
assert(item.files, 'Item should have files list');
|
|
167
|
+
assert(item.example, 'Item should have example');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
Then('I see recommendation: {string}', function (recommendation) {
|
|
171
|
+
assert(this.recommendations, 'Should have recommendations');
|
|
172
|
+
assert(this.recommendations.quickWin.includes('Storage Pattern'), 'Should recommend Storage Pattern');
|
|
173
|
+
assert(this.recommendations.quickWin.includes('quick win'), 'Should mention quick win');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
Then('I see warning: {string}', function (warning) {
|
|
177
|
+
assert(this.recommendations, 'Should have recommendations');
|
|
178
|
+
assert(this.recommendations.warning.includes('State Management'), 'Should warn about State Management');
|
|
179
|
+
assert(this.recommendations.warning.includes('Discovery Mode'), 'Should mention Discovery Mode');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
Then('epic {string} is created', function (epicTitle) {
|
|
183
|
+
assert(createdEpic, 'Epic should be created');
|
|
184
|
+
assert.strictEqual(createdEpic.title, epicTitle);
|
|
185
|
+
assert.strictEqual(createdEpic.type, 'epic');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
Then('chore {string} is created', function (choreTitle) {
|
|
189
|
+
const chore = createdChores.find(c => c.title === choreTitle);
|
|
190
|
+
assert(chore, `Chore "${choreTitle}" should be created`);
|
|
191
|
+
assert.strictEqual(chore.type, 'chore');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
Then('refactor chores are created under an epic', function () {
|
|
195
|
+
assert(createdEpic, 'Epic should be created');
|
|
196
|
+
assert(createdChores.length > 0, 'Chores should be created');
|
|
197
|
+
|
|
198
|
+
// Verify chores are linked to epic
|
|
199
|
+
createdChores.forEach(chore => {
|
|
200
|
+
assert.strictEqual(chore.epicId, createdEpic.id, 'Chore should be linked to epic');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
Then('the epic is linked to the refactor mode work item', function () {
|
|
205
|
+
// Epic is created as part of refactor mode workflow
|
|
206
|
+
assert(createdEpic, 'Epic should exist');
|
|
207
|
+
assert(this.existingWorkItems, 'Work items should exist');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
Then('all chores are linked to the epic', function () {
|
|
211
|
+
assert(createdEpic, 'Epic should be created');
|
|
212
|
+
assert(createdChores.length > 0, 'Chores should be created');
|
|
213
|
+
|
|
214
|
+
createdChores.forEach(chore => {
|
|
215
|
+
assert.strictEqual(chore.epicId, createdEpic.id, 'All chores should be linked to epic');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Feature: Refactor Mode
|
|
2
|
+
As a developer
|
|
3
|
+
I want an interactive audit menu for refactoring
|
|
4
|
+
So that I can systematically clean up technical debt
|
|
5
|
+
|
|
6
|
+
# INTEGRATION TEST - Must work with existing work tracking
|
|
7
|
+
Scenario: Refactor mode integrates with work tracking
|
|
8
|
+
Given I have existing work items in the system
|
|
9
|
+
When I enter refactor mode and audit the codebase
|
|
10
|
+
And I select "Architectural" category
|
|
11
|
+
And I approve creating chores for storage pattern
|
|
12
|
+
Then refactor chores are created under an epic
|
|
13
|
+
And the epic is linked to the refactor mode work item
|
|
14
|
+
|
|
15
|
+
Scenario: User enters refactor mode and sees audit menu
|
|
16
|
+
Given I'm in a project with technical debt
|
|
17
|
+
When I enter refactor mode
|
|
18
|
+
Then Claude audits the codebase
|
|
19
|
+
And I see categorized menu with counts:
|
|
20
|
+
| Category | Example |
|
|
21
|
+
| Code Smells | Long functions (5 found) |
|
|
22
|
+
| Feature-Specific | features/auth/ (3 files) |
|
|
23
|
+
| Architectural | Storage patterns (15 uses) |
|
|
24
|
+
| Performance | Re-renders (4 components) |
|
|
25
|
+
|
|
26
|
+
Scenario: User picks category and sees detailed breakdown
|
|
27
|
+
Given I see the audit menu
|
|
28
|
+
When I pick "Architectural" category
|
|
29
|
+
Then I see detailed items with:
|
|
30
|
+
| Field | Example |
|
|
31
|
+
| Type | 🟠Cross-Cutting |
|
|
32
|
+
| Impact | High - affects all features |
|
|
33
|
+
| Time | 2 hours |
|
|
34
|
+
| Files | auth.js, expenses.js, +13 more |
|
|
35
|
+
| Example | localStorage.getItem() duplicated |
|
|
36
|
+
|
|
37
|
+
Scenario: Claude recommends quick wins
|
|
38
|
+
Given I see the detailed breakdown for "Architectural"
|
|
39
|
+
When Claude analyzes impact vs time
|
|
40
|
+
Then I see recommendation: "Start with Storage Pattern (quick win, high impact)"
|
|
41
|
+
And I see warning: "Skip State Management (needs Discovery Mode first)"
|
|
42
|
+
|
|
43
|
+
Scenario: User approves and chores are created
|
|
44
|
+
Given I see recommendations for Storage Pattern
|
|
45
|
+
When I say "create chores"
|
|
46
|
+
Then epic "Refactor: Architectural patterns" is created
|
|
47
|
+
And chore "Create shared storage util" is created
|
|
48
|
+
And chore "Migrate auth to storage util" is created
|
|
49
|
+
And all chores are linked to the epic
|