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,112 @@
|
|
|
1
|
+
Feature: Mode defaults to discovery on work item creation
|
|
2
|
+
As a developer
|
|
3
|
+
I want new work items to default to discovery mode
|
|
4
|
+
So I can start exploring without specifying mode every time
|
|
5
|
+
|
|
6
|
+
# Epic scenarios - epics don't have mode
|
|
7
|
+
Scenario: Create epic without mode
|
|
8
|
+
Given jettypod is initialized
|
|
9
|
+
When I create an epic "Test Epic" without mode
|
|
10
|
+
Then the work item is created successfully
|
|
11
|
+
And the work item has NULL mode
|
|
12
|
+
|
|
13
|
+
Scenario: Create epic with mode (optional)
|
|
14
|
+
Given jettypod is initialized
|
|
15
|
+
When I create an epic "Test Epic" with mode "stable"
|
|
16
|
+
Then the work item is created successfully
|
|
17
|
+
And the work item has mode "stable"
|
|
18
|
+
|
|
19
|
+
# Feature scenarios - defaults to discovery
|
|
20
|
+
Scenario: Create feature with explicit mode
|
|
21
|
+
Given jettypod is initialized
|
|
22
|
+
When I create a feature "Test Feature" with mode "speed"
|
|
23
|
+
Then the work item is created successfully
|
|
24
|
+
And the work item has mode "speed"
|
|
25
|
+
|
|
26
|
+
Scenario: Create feature without mode defaults to discovery
|
|
27
|
+
Given jettypod is initialized
|
|
28
|
+
When I create a feature "Test Feature" without mode
|
|
29
|
+
Then the work item is created successfully
|
|
30
|
+
And the work item has mode "discovery"
|
|
31
|
+
|
|
32
|
+
Scenario: Create feature with invalid mode
|
|
33
|
+
Given jettypod is initialized
|
|
34
|
+
When I try to create a feature "Test Feature" with mode "invalid"
|
|
35
|
+
Then I get an error "Invalid mode"
|
|
36
|
+
And no work item is created
|
|
37
|
+
|
|
38
|
+
# Bug scenarios - defaults to discovery
|
|
39
|
+
Scenario: Create bug with explicit mode
|
|
40
|
+
Given jettypod is initialized
|
|
41
|
+
When I create a bug "Test Bug" with mode "stable"
|
|
42
|
+
Then the work item is created successfully
|
|
43
|
+
And the work item has mode "stable"
|
|
44
|
+
|
|
45
|
+
Scenario: Create bug without mode defaults to discovery
|
|
46
|
+
Given jettypod is initialized
|
|
47
|
+
When I create a bug "Test Bug" without mode
|
|
48
|
+
Then the work item is created successfully
|
|
49
|
+
And the work item has mode "discovery"
|
|
50
|
+
|
|
51
|
+
# Chore scenarios - chores don't have modes
|
|
52
|
+
Scenario: Create chore with mode fails
|
|
53
|
+
Given jettypod is initialized
|
|
54
|
+
When I try to create a chore "Test Chore" with mode "production"
|
|
55
|
+
Then I get an error "Chores do not have modes"
|
|
56
|
+
And no work item is created
|
|
57
|
+
|
|
58
|
+
Scenario: Create chore without mode succeeds with NULL
|
|
59
|
+
Given jettypod is initialized
|
|
60
|
+
When I create a chore "Test Chore" without mode
|
|
61
|
+
Then the work item is created successfully
|
|
62
|
+
And the work item has NULL mode
|
|
63
|
+
|
|
64
|
+
# All valid modes
|
|
65
|
+
Scenario Outline: Create feature with all valid modes
|
|
66
|
+
Given jettypod is initialized
|
|
67
|
+
When I create a feature "Test Feature" with mode "<mode>"
|
|
68
|
+
Then the work item is created successfully
|
|
69
|
+
And the work item has mode "<mode>"
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
| mode |
|
|
73
|
+
| speed |
|
|
74
|
+
| discovery |
|
|
75
|
+
| stable |
|
|
76
|
+
| production |
|
|
77
|
+
|
|
78
|
+
# Integration - hierarchical structure
|
|
79
|
+
Scenario: Epic with children in different modes
|
|
80
|
+
Given jettypod is initialized
|
|
81
|
+
And I create an epic "Test Epic" without mode
|
|
82
|
+
And I create a feature "Speed Feature" with mode "speed" and parent epic
|
|
83
|
+
And I create a bug "Stable Bug" with mode "stable" and parent epic
|
|
84
|
+
And I create a chore "Test Chore" without mode and parent epic
|
|
85
|
+
When I view the work tree
|
|
86
|
+
Then I see the epic without mode indicator
|
|
87
|
+
And I see the feature with mode "speed"
|
|
88
|
+
And I see the bug with mode "stable"
|
|
89
|
+
And I see the chore without mode indicator
|
|
90
|
+
|
|
91
|
+
# Edge cases
|
|
92
|
+
Scenario: Create multiple items with same title but different modes
|
|
93
|
+
Given jettypod is initialized
|
|
94
|
+
When I create a feature "Duplicate" with mode "speed"
|
|
95
|
+
And I create a feature "Duplicate" with mode "stable"
|
|
96
|
+
Then both work items are created successfully
|
|
97
|
+
And they have different modes
|
|
98
|
+
|
|
99
|
+
Scenario: Mode is case-sensitive
|
|
100
|
+
Given jettypod is initialized
|
|
101
|
+
When I try to create a feature "Test" with mode "Speed"
|
|
102
|
+
Then I get an error "Invalid mode"
|
|
103
|
+
|
|
104
|
+
Scenario: Mode with whitespace is invalid
|
|
105
|
+
Given jettypod is initialized
|
|
106
|
+
When I try to create a feature "Test" with mode " speed "
|
|
107
|
+
Then I get an error "Invalid mode"
|
|
108
|
+
|
|
109
|
+
Scenario: Empty string mode is treated as NULL
|
|
110
|
+
Given jettypod is initialized
|
|
111
|
+
When I try to create a feature "Test" with mode ""
|
|
112
|
+
Then I get an error "Invalid mode"
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
const { createTestEnvironment } = require('../../lib/test-helpers');
|
|
2
|
+
const { getDb, closeDb, resetDb } = require('../../lib/database');
|
|
3
|
+
|
|
4
|
+
describe('Feature Phase Tracking', () => {
|
|
5
|
+
let testEnv;
|
|
6
|
+
let db;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
resetDb();
|
|
10
|
+
testEnv = createTestEnvironment();
|
|
11
|
+
process.chdir(testEnv.testDir);
|
|
12
|
+
db = getDb();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
await closeDb();
|
|
19
|
+
testEnv.cleanup();
|
|
20
|
+
resetDb();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Database Schema', () => {
|
|
24
|
+
test('phase column exists in work_items table', async () => {
|
|
25
|
+
await new Promise((resolve) => {
|
|
26
|
+
db.get('SELECT sql FROM sqlite_master WHERE type="table" AND name="work_items"', [], (err, row) => {
|
|
27
|
+
expect(err).toBeNull();
|
|
28
|
+
expect(row.sql).toContain('phase');
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('phase column allows NULL values', async () => {
|
|
35
|
+
await new Promise((resolve) => {
|
|
36
|
+
db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['epic', 'Test Epic', null], function(err) {
|
|
37
|
+
expect(err).toBeNull();
|
|
38
|
+
resolve();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('phase column accepts discovery value', async () => {
|
|
44
|
+
await new Promise((resolve) => {
|
|
45
|
+
db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Test Feature', 'discovery'], function(err) {
|
|
46
|
+
expect(err).toBeNull();
|
|
47
|
+
resolve();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('phase column accepts implementation value', async () => {
|
|
53
|
+
await new Promise((resolve) => {
|
|
54
|
+
db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Test Feature', 'implementation'], function(err) {
|
|
55
|
+
expect(err).toBeNull();
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Feature Creation with Default Phase', () => {
|
|
63
|
+
test('new features default to discovery phase', async () => {
|
|
64
|
+
await new Promise((resolve) => {
|
|
65
|
+
db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
66
|
+
['feature', 'Test Feature', 'Description', null, 'discovery', 0, 'discovery'],
|
|
67
|
+
function(err) {
|
|
68
|
+
expect(err).toBeNull();
|
|
69
|
+
const featureId = this.lastID;
|
|
70
|
+
|
|
71
|
+
db.get('SELECT phase FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
72
|
+
expect(err).toBeNull();
|
|
73
|
+
expect(row.phase).toBe('discovery');
|
|
74
|
+
resolve();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('epics have NULL phase', async () => {
|
|
82
|
+
await new Promise((resolve) => {
|
|
83
|
+
db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
84
|
+
['epic', 'Test Epic', 'Description', null, null, 0, null],
|
|
85
|
+
function(err) {
|
|
86
|
+
expect(err).toBeNull();
|
|
87
|
+
const epicId = this.lastID;
|
|
88
|
+
|
|
89
|
+
db.get('SELECT phase FROM work_items WHERE id = ?', [epicId], (err, row) => {
|
|
90
|
+
expect(err).toBeNull();
|
|
91
|
+
expect(row.phase).toBeNull();
|
|
92
|
+
resolve();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('chores have NULL phase', async () => {
|
|
100
|
+
await new Promise((resolve) => {
|
|
101
|
+
db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
102
|
+
['chore', 'Test Chore', 'Description', null, 'discovery', 0, null],
|
|
103
|
+
function(err) {
|
|
104
|
+
expect(err).toBeNull();
|
|
105
|
+
const choreId = this.lastID;
|
|
106
|
+
|
|
107
|
+
db.get('SELECT phase FROM work_items WHERE id = ?', [choreId], (err, row) => {
|
|
108
|
+
expect(err).toBeNull();
|
|
109
|
+
expect(row.phase).toBeNull();
|
|
110
|
+
resolve();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('bugs have NULL phase', async () => {
|
|
118
|
+
await new Promise((resolve) => {
|
|
119
|
+
db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
120
|
+
['bug', 'Test Bug', 'Description', null, 'stable', 0, null],
|
|
121
|
+
function(err) {
|
|
122
|
+
expect(err).toBeNull();
|
|
123
|
+
const bugId = this.lastID;
|
|
124
|
+
|
|
125
|
+
db.get('SELECT phase FROM work_items WHERE id = ?', [bugId], (err, row) => {
|
|
126
|
+
expect(err).toBeNull();
|
|
127
|
+
expect(row.phase).toBeNull();
|
|
128
|
+
resolve();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('Phase Transition (Discovery → Implementation)', () => {
|
|
137
|
+
test('can transition feature from discovery to implementation', async () => {
|
|
138
|
+
// Create feature in discovery phase
|
|
139
|
+
await new Promise((resolve) => {
|
|
140
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
|
|
141
|
+
['feature', 'Test Feature', 'discovery', 'discovery'],
|
|
142
|
+
resolve
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Transition to implementation
|
|
147
|
+
await new Promise((resolve) => {
|
|
148
|
+
db.run('UPDATE work_items SET phase = ?, mode = ? WHERE id = ?',
|
|
149
|
+
['implementation', 'speed', 1],
|
|
150
|
+
resolve
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Verify phase and mode
|
|
155
|
+
await new Promise((resolve) => {
|
|
156
|
+
db.get('SELECT phase, mode FROM work_items WHERE id = ?', [1], (err, row) => {
|
|
157
|
+
expect(err).toBeNull();
|
|
158
|
+
expect(row.phase).toBe('implementation');
|
|
159
|
+
expect(row.mode).toBe('speed');
|
|
160
|
+
resolve();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('implementation phase sets mode to speed', async () => {
|
|
166
|
+
await new Promise((resolve) => {
|
|
167
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
|
|
168
|
+
['feature', 'Test Feature', 'discovery', 'discovery'],
|
|
169
|
+
function(err) {
|
|
170
|
+
const featureId = this.lastID;
|
|
171
|
+
|
|
172
|
+
db.run('UPDATE work_items SET phase = ?, mode = ? WHERE id = ?',
|
|
173
|
+
['implementation', 'speed', featureId],
|
|
174
|
+
() => {
|
|
175
|
+
db.get('SELECT mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
176
|
+
expect(row.mode).toBe('speed');
|
|
177
|
+
resolve();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('cannot transition epic (has NULL phase)', async () => {
|
|
187
|
+
await new Promise((resolve) => {
|
|
188
|
+
db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)',
|
|
189
|
+
['epic', 'Test Epic', null],
|
|
190
|
+
function(err) {
|
|
191
|
+
const epicId = this.lastID;
|
|
192
|
+
|
|
193
|
+
db.get('SELECT phase, type FROM work_items WHERE id = ?', [epicId], (err, row) => {
|
|
194
|
+
expect(row.phase).toBeNull();
|
|
195
|
+
expect(row.type).toBe('epic');
|
|
196
|
+
resolve();
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('cannot transition chore (has NULL phase)', async () => {
|
|
204
|
+
await new Promise((resolve) => {
|
|
205
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
|
|
206
|
+
['chore', 'Test Chore', null, 'discovery'],
|
|
207
|
+
function(err) {
|
|
208
|
+
const choreId = this.lastID;
|
|
209
|
+
|
|
210
|
+
db.get('SELECT phase, type FROM work_items WHERE id = ?', [choreId], (err, row) => {
|
|
211
|
+
expect(row.phase).toBeNull();
|
|
212
|
+
expect(row.type).toBe('chore');
|
|
213
|
+
resolve();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('Phase Display Logic', () => {
|
|
222
|
+
test('discovery phase shows with 🔍 icon', () => {
|
|
223
|
+
const item = { type: 'feature', phase: 'discovery', mode: 'discovery' };
|
|
224
|
+
|
|
225
|
+
let modeIndicator = '';
|
|
226
|
+
if (item.type === 'feature') {
|
|
227
|
+
if (item.phase === 'discovery') {
|
|
228
|
+
modeIndicator = ' [🔍 discovery]';
|
|
229
|
+
} else if (item.mode) {
|
|
230
|
+
modeIndicator = ` [${item.mode}]`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
expect(modeIndicator).toBe(' [🔍 discovery]');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('implementation phase shows mode', () => {
|
|
238
|
+
const item = { type: 'feature', phase: 'implementation', mode: 'speed' };
|
|
239
|
+
|
|
240
|
+
let modeIndicator = '';
|
|
241
|
+
if (item.type === 'feature') {
|
|
242
|
+
if (item.phase === 'discovery') {
|
|
243
|
+
modeIndicator = ' [🔍 discovery]';
|
|
244
|
+
} else if (item.mode) {
|
|
245
|
+
modeIndicator = ` [${item.mode}]`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
expect(modeIndicator).toBe(' [speed]');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('implementation phase with stable mode', () => {
|
|
253
|
+
const item = { type: 'feature', phase: 'implementation', mode: 'stable' };
|
|
254
|
+
|
|
255
|
+
let modeIndicator = '';
|
|
256
|
+
if (item.type === 'feature') {
|
|
257
|
+
if (item.phase === 'discovery') {
|
|
258
|
+
modeIndicator = ' [🔍 discovery]';
|
|
259
|
+
} else if (item.mode) {
|
|
260
|
+
modeIndicator = ` [${item.mode}]`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
expect(modeIndicator).toBe(' [stable]');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('implementation phase with production mode', () => {
|
|
268
|
+
const item = { type: 'feature', phase: 'implementation', mode: 'production' };
|
|
269
|
+
|
|
270
|
+
let modeIndicator = '';
|
|
271
|
+
if (item.type === 'feature') {
|
|
272
|
+
if (item.phase === 'discovery') {
|
|
273
|
+
modeIndicator = ' [🔍 discovery]';
|
|
274
|
+
} else if (item.mode) {
|
|
275
|
+
modeIndicator = ` [${item.mode}]`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
expect(modeIndicator).toBe(' [production]');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('epic shows no phase indicator', () => {
|
|
283
|
+
const item = { type: 'epic', phase: null, mode: null };
|
|
284
|
+
|
|
285
|
+
let modeIndicator = '';
|
|
286
|
+
if (item.type === 'feature') {
|
|
287
|
+
if (item.phase === 'discovery') {
|
|
288
|
+
modeIndicator = ' [🔍 discovery]';
|
|
289
|
+
} else if (item.mode) {
|
|
290
|
+
modeIndicator = ` [${item.mode}]`;
|
|
291
|
+
}
|
|
292
|
+
} else if (item.type !== 'epic' && item.mode) {
|
|
293
|
+
modeIndicator = ` [${item.mode}]`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
expect(modeIndicator).toBe('');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('chore shows mode indicator', () => {
|
|
300
|
+
const item = { type: 'chore', phase: null, mode: 'discovery' };
|
|
301
|
+
|
|
302
|
+
let modeIndicator = '';
|
|
303
|
+
if (item.type === 'feature') {
|
|
304
|
+
if (item.phase === 'discovery') {
|
|
305
|
+
modeIndicator = ' [🔍 discovery]';
|
|
306
|
+
} else if (item.mode) {
|
|
307
|
+
modeIndicator = ` [${item.mode}]`;
|
|
308
|
+
}
|
|
309
|
+
} else if (item.type !== 'epic' && item.mode) {
|
|
310
|
+
modeIndicator = ` [${item.mode}]`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
expect(modeIndicator).toBe(' [discovery]');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('bug shows mode indicator', () => {
|
|
317
|
+
const item = { type: 'bug', phase: null, mode: 'stable' };
|
|
318
|
+
|
|
319
|
+
let modeIndicator = '';
|
|
320
|
+
if (item.type === 'feature') {
|
|
321
|
+
if (item.phase === 'discovery') {
|
|
322
|
+
modeIndicator = ' [🔍 discovery]';
|
|
323
|
+
} else if (item.mode) {
|
|
324
|
+
modeIndicator = ` [${item.mode}]`;
|
|
325
|
+
}
|
|
326
|
+
} else if (item.type !== 'epic' && item.mode) {
|
|
327
|
+
modeIndicator = ` [${item.mode}]`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
expect(modeIndicator).toBe(' [stable]');
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('Edge Cases', () => {
|
|
335
|
+
test('feature with NULL phase treated as implementation', async () => {
|
|
336
|
+
await new Promise((resolve) => {
|
|
337
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
|
|
338
|
+
['feature', 'Legacy Feature', null, 'stable'],
|
|
339
|
+
function(err) {
|
|
340
|
+
const featureId = this.lastID;
|
|
341
|
+
|
|
342
|
+
db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
343
|
+
expect(row.phase).toBeNull();
|
|
344
|
+
expect(row.mode).toBe('stable');
|
|
345
|
+
resolve();
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('feature in discovery with NULL mode', async () => {
|
|
353
|
+
await new Promise((resolve) => {
|
|
354
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
|
|
355
|
+
['feature', 'Test Feature', 'discovery', null],
|
|
356
|
+
function(err) {
|
|
357
|
+
const featureId = this.lastID;
|
|
358
|
+
|
|
359
|
+
db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
360
|
+
expect(row.phase).toBe('discovery');
|
|
361
|
+
expect(row.mode).toBeNull();
|
|
362
|
+
resolve();
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('feature in implementation with NULL mode defaults to speed', async () => {
|
|
370
|
+
await new Promise((resolve) => {
|
|
371
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
|
|
372
|
+
['feature', 'Test Feature', 'implementation', null],
|
|
373
|
+
function(err) {
|
|
374
|
+
const featureId = this.lastID;
|
|
375
|
+
|
|
376
|
+
// Simulate what implement command would do
|
|
377
|
+
db.run('UPDATE work_items SET mode = ? WHERE id = ? AND mode IS NULL',
|
|
378
|
+
['speed', featureId],
|
|
379
|
+
() => {
|
|
380
|
+
db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
381
|
+
expect(row.phase).toBe('implementation');
|
|
382
|
+
expect(row.mode).toBe('speed');
|
|
383
|
+
resolve();
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('handles empty phase value', async () => {
|
|
393
|
+
await new Promise((resolve) => {
|
|
394
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
|
|
395
|
+
['feature', 'Test Feature', '', 'discovery'],
|
|
396
|
+
function(err) {
|
|
397
|
+
const featureId = this.lastID;
|
|
398
|
+
|
|
399
|
+
db.get('SELECT phase FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
400
|
+
// Empty string is stored as is
|
|
401
|
+
expect(row.phase).toBe('');
|
|
402
|
+
resolve();
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('multiple features can be in discovery simultaneously', async () => {
|
|
410
|
+
await new Promise((resolve) => {
|
|
411
|
+
db.serialize(() => {
|
|
412
|
+
db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Feature 1', 'discovery']);
|
|
413
|
+
db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Feature 2', 'discovery']);
|
|
414
|
+
db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Feature 3', 'discovery'], () => {
|
|
415
|
+
db.all('SELECT COUNT(*) as count FROM work_items WHERE phase = ?', ['discovery'], (err, rows) => {
|
|
416
|
+
expect(rows[0].count).toBe(3);
|
|
417
|
+
resolve();
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test('multiple features can be in implementation simultaneously', async () => {
|
|
425
|
+
await new Promise((resolve) => {
|
|
426
|
+
db.serialize(() => {
|
|
427
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)', ['feature', 'Feature 1', 'implementation', 'speed']);
|
|
428
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)', ['feature', 'Feature 2', 'implementation', 'stable']);
|
|
429
|
+
db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)', ['feature', 'Feature 3', 'implementation', 'production'], () => {
|
|
430
|
+
db.all('SELECT COUNT(*) as count FROM work_items WHERE phase = ?', ['implementation'], (err, rows) => {
|
|
431
|
+
expect(rows[0].count).toBe(3);
|
|
432
|
+
resolve();
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
describe('Backwards Compatibility', () => {
|
|
441
|
+
test('existing features without phase field work correctly', async () => {
|
|
442
|
+
// Simulate old feature (before migration)
|
|
443
|
+
await new Promise((resolve) => {
|
|
444
|
+
db.run('INSERT INTO work_items (type, title, mode) VALUES (?, ?, ?)',
|
|
445
|
+
['feature', 'Legacy Feature', 'stable'],
|
|
446
|
+
function(err) {
|
|
447
|
+
const featureId = this.lastID;
|
|
448
|
+
|
|
449
|
+
db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
450
|
+
// Phase will be NULL or default value from schema
|
|
451
|
+
expect(row.mode).toBe('stable');
|
|
452
|
+
resolve();
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test('migration sets existing features to implementation phase', async () => {
|
|
460
|
+
// Create feature without phase
|
|
461
|
+
await new Promise((resolve) => {
|
|
462
|
+
db.run('INSERT INTO work_items (type, title, mode, phase) VALUES (?, ?, ?, ?)',
|
|
463
|
+
['feature', 'Old Feature', 'stable', null],
|
|
464
|
+
function(err) {
|
|
465
|
+
const featureId = this.lastID;
|
|
466
|
+
|
|
467
|
+
// Simulate migration
|
|
468
|
+
db.run('UPDATE work_items SET phase = ? WHERE type = ? AND phase IS NULL',
|
|
469
|
+
['implementation', 'feature'],
|
|
470
|
+
() => {
|
|
471
|
+
db.get('SELECT phase FROM work_items WHERE id = ?', [featureId], (err, row) => {
|
|
472
|
+
expect(row.phase).toBe('implementation');
|
|
473
|
+
resolve();
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
});
|