jettypod 4.4.21 → 4.4.23
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/jettypod.js +13 -14
- package/lib/decisions/index.js +490 -0
- package/lib/git-hooks/git-hooks.feature +30 -0
- package/lib/git-hooks/index.js +94 -0
- package/lib/git-hooks/post-commit +59 -0
- package/lib/git-hooks/post-merge +71 -0
- package/lib/git-hooks/pre-commit +28 -0
- package/lib/git-hooks/simple-steps.js +53 -0
- package/lib/git-hooks/simple-test.feature +10 -0
- package/lib/git-hooks/steps.js +196 -0
- package/lib/mode-prompts/index.js +95 -0
- package/lib/mode-prompts/simple-steps.js +44 -0
- package/lib/mode-prompts/simple-test.feature +9 -0
- package/lib/update-command/index.js +181 -0
- package/lib/work-commands/bug-workflow-display.feature +22 -0
- package/lib/work-commands/index.js +1603 -0
- package/lib/work-commands/simple-steps.js +69 -0
- package/lib/work-commands/stable-tests.feature +57 -0
- package/lib/work-commands/steps.js +1233 -0
- package/lib/work-commands/work-commands.feature +13 -0
- package/lib/work-commands/worktree-management.feature +63 -0
- package/lib/work-tracking/index.js +2396 -0
- package/lib/work-tracking/mode-required.feature +111 -0
- package/lib/work-tracking/work-set-mode.feature +70 -0
- package/lib/work-tracking/work-start-mode.feature +83 -0
- package/package.json +1 -1
package/jettypod.js
CHANGED
|
@@ -85,10 +85,9 @@ function ensureJettypodGitignores() {
|
|
|
85
85
|
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
86
86
|
const entriesToIgnore = [
|
|
87
87
|
'.claude/session.md',
|
|
88
|
+
'.jettypod/',
|
|
88
89
|
'.jettypod-work/',
|
|
89
|
-
'.jettypod-trash/'
|
|
90
|
-
'.jettypod/*.db-shm',
|
|
91
|
-
'.jettypod/*.db-wal'
|
|
90
|
+
'.jettypod-trash/'
|
|
92
91
|
];
|
|
93
92
|
|
|
94
93
|
// Add entries to .gitignore if not present
|
|
@@ -166,7 +165,7 @@ const claude = {
|
|
|
166
165
|
let currentWork = null;
|
|
167
166
|
let currentWorkSection = '';
|
|
168
167
|
try {
|
|
169
|
-
const workTracking = require('./
|
|
168
|
+
const workTracking = require('./lib/work-tracking/index.js');
|
|
170
169
|
currentWork = await workTracking.getCurrentWork();
|
|
171
170
|
|
|
172
171
|
if (currentWork) {
|
|
@@ -740,7 +739,7 @@ async function initializeProject() {
|
|
|
740
739
|
await generateClaude();
|
|
741
740
|
|
|
742
741
|
// Install git hooks
|
|
743
|
-
const gitHooks = require('./
|
|
742
|
+
const gitHooks = require('./lib/git-hooks/index.js');
|
|
744
743
|
gitHooks.installHooks();
|
|
745
744
|
|
|
746
745
|
// Install Claude Code hooks
|
|
@@ -1082,7 +1081,7 @@ const [,, command, ...args] = process.argv;
|
|
|
1082
1081
|
switch (command) {
|
|
1083
1082
|
case 'update': {
|
|
1084
1083
|
// Update jettypod to latest version
|
|
1085
|
-
const updateCommand = require('./
|
|
1084
|
+
const updateCommand = require('./lib/update-command');
|
|
1086
1085
|
const success = await updateCommand.runUpdate();
|
|
1087
1086
|
|
|
1088
1087
|
// Always refresh skills in current project after update attempt
|
|
@@ -1189,7 +1188,7 @@ switch (command) {
|
|
|
1189
1188
|
const subcommand = args[0];
|
|
1190
1189
|
|
|
1191
1190
|
if (subcommand === 'start') {
|
|
1192
|
-
const workCommands = require('./
|
|
1191
|
+
const workCommands = require('./lib/work-commands/index.js');
|
|
1193
1192
|
const id = parseInt(args[1]);
|
|
1194
1193
|
try {
|
|
1195
1194
|
await workCommands.startWork(id);
|
|
@@ -1210,7 +1209,7 @@ switch (command) {
|
|
|
1210
1209
|
process.exit(1);
|
|
1211
1210
|
}
|
|
1212
1211
|
|
|
1213
|
-
const workCommands = require('./
|
|
1212
|
+
const workCommands = require('./lib/work-commands/index.js');
|
|
1214
1213
|
|
|
1215
1214
|
// Check if status was provided as argument: work stop <id> <status>
|
|
1216
1215
|
const statusArg = args[1]; // args[0] is the id (optional), args[1] is status
|
|
@@ -1244,7 +1243,7 @@ switch (command) {
|
|
|
1244
1243
|
});
|
|
1245
1244
|
}
|
|
1246
1245
|
} else if (subcommand === 'cleanup') {
|
|
1247
|
-
const workCommands = require('./
|
|
1246
|
+
const workCommands = require('./lib/work-commands/index.js');
|
|
1248
1247
|
const dryRun = args[0] === '--dry-run';
|
|
1249
1248
|
|
|
1250
1249
|
try {
|
|
@@ -1264,7 +1263,7 @@ switch (command) {
|
|
|
1264
1263
|
process.exit(1);
|
|
1265
1264
|
}
|
|
1266
1265
|
} else if (subcommand === 'merge') {
|
|
1267
|
-
const workCommands = require('./
|
|
1266
|
+
const workCommands = require('./lib/work-commands/index.js');
|
|
1268
1267
|
try {
|
|
1269
1268
|
// Parse merge flags and optional work item ID from args
|
|
1270
1269
|
const withTransition = args.includes('--with-transition');
|
|
@@ -1291,7 +1290,7 @@ switch (command) {
|
|
|
1291
1290
|
}
|
|
1292
1291
|
|
|
1293
1292
|
// Delegate to work tracking module
|
|
1294
|
-
const workTracking = require('./
|
|
1293
|
+
const workTracking = require('./lib/work-tracking/index.js');
|
|
1295
1294
|
process.argv = ['node', 'work', ...args];
|
|
1296
1295
|
workTracking.main();
|
|
1297
1296
|
}
|
|
@@ -1299,7 +1298,7 @@ switch (command) {
|
|
|
1299
1298
|
|
|
1300
1299
|
case 'backlog':
|
|
1301
1300
|
// Backlog viewing - delegates to work tracking module
|
|
1302
|
-
const workTracking = require('./
|
|
1301
|
+
const workTracking = require('./lib/work-tracking/index.js');
|
|
1303
1302
|
process.argv = ['node', 'work-tracking', 'backlog', ...args];
|
|
1304
1303
|
workTracking.main();
|
|
1305
1304
|
break;
|
|
@@ -1343,7 +1342,7 @@ switch (command) {
|
|
|
1343
1342
|
break;
|
|
1344
1343
|
|
|
1345
1344
|
case 'decisions': {
|
|
1346
|
-
const decisions = require('./
|
|
1345
|
+
const decisions = require('./lib/decisions');
|
|
1347
1346
|
|
|
1348
1347
|
// Check for command-line flags
|
|
1349
1348
|
const hasFlag = args.some(arg => arg.startsWith('--'));
|
|
@@ -1420,7 +1419,7 @@ switch (command) {
|
|
|
1420
1419
|
}
|
|
1421
1420
|
|
|
1422
1421
|
// Create Infrastructure Readiness epic with features and chores
|
|
1423
|
-
const { create } = require('./
|
|
1422
|
+
const { create } = require('./lib/work-tracking');
|
|
1424
1423
|
const { readStandards } = require('./lib/production-standards-reader');
|
|
1425
1424
|
const { generateInfrastructureChores } = require('./lib/production-chore-generator');
|
|
1426
1425
|
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
// Stable Mode: decisions command with error handling and edge cases
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
const { getDb } = require('../../lib/database');
|
|
4
|
+
const config = require('../../lib/config');
|
|
5
|
+
|
|
6
|
+
let db;
|
|
7
|
+
try {
|
|
8
|
+
db = getDb();
|
|
9
|
+
} catch (err) {
|
|
10
|
+
console.error('❌ Database initialization failed');
|
|
11
|
+
console.error(`Error: ${err.message}`);
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log('This could mean:');
|
|
14
|
+
console.log(' - JettyPod is not initialized (run jettypod init)');
|
|
15
|
+
console.log(' - Database file is corrupted');
|
|
16
|
+
console.log(' - Insufficient permissions');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Show interactive decisions menu
|
|
22
|
+
*/
|
|
23
|
+
async function showDecisionsMenu() {
|
|
24
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
25
|
+
console.log('📋 DECISIONS');
|
|
26
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log('What would you like to see?');
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(' 1. All decisions (chronological)');
|
|
31
|
+
console.log(' 2. Project-level decisions (UX, tech stack)');
|
|
32
|
+
console.log(' 3. Epic-level decisions (architecture, patterns)');
|
|
33
|
+
console.log(' 4. Decisions for a specific epic');
|
|
34
|
+
console.log(' 5. View DECISIONS.md');
|
|
35
|
+
console.log('');
|
|
36
|
+
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const rl = readline.createInterface({
|
|
39
|
+
input: process.stdin,
|
|
40
|
+
output: process.stdout
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
rl.question('Enter your choice (1-5): ', async (answer) => {
|
|
44
|
+
rl.close();
|
|
45
|
+
|
|
46
|
+
const trimmed = answer.trim();
|
|
47
|
+
|
|
48
|
+
// Handle empty input
|
|
49
|
+
if (!trimmed) {
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log('❌ No choice entered');
|
|
52
|
+
console.log('Please run the command again and enter a number between 1-5');
|
|
53
|
+
console.log('');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const choice = parseInt(trimmed);
|
|
58
|
+
|
|
59
|
+
// Handle non-numeric input
|
|
60
|
+
if (isNaN(choice)) {
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log(`❌ Invalid input: "${trimmed}"`);
|
|
63
|
+
console.log('Please enter a number between 1-5');
|
|
64
|
+
console.log('');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle out of range
|
|
69
|
+
if (choice < 1 || choice > 5) {
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log(`❌ Choice ${choice} is not valid`);
|
|
72
|
+
console.log('Please enter a number between 1-5:');
|
|
73
|
+
console.log(' 1 = All decisions');
|
|
74
|
+
console.log(' 2 = Project-level decisions');
|
|
75
|
+
console.log(' 3 = Epic-level decisions');
|
|
76
|
+
console.log(' 4 = Decisions for specific epic');
|
|
77
|
+
console.log(' 5 = View DECISIONS.md');
|
|
78
|
+
console.log('');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
switch (choice) {
|
|
84
|
+
case 1:
|
|
85
|
+
await showAllDecisions();
|
|
86
|
+
break;
|
|
87
|
+
case 2:
|
|
88
|
+
showProjectDecisions();
|
|
89
|
+
break;
|
|
90
|
+
case 3:
|
|
91
|
+
await showEpicDecisions();
|
|
92
|
+
break;
|
|
93
|
+
case 4:
|
|
94
|
+
await promptForEpicId();
|
|
95
|
+
break;
|
|
96
|
+
case 5:
|
|
97
|
+
viewDecisionsFile();
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
resolve();
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error('');
|
|
103
|
+
console.error('❌ An error occurred while displaying decisions');
|
|
104
|
+
console.error(`Error: ${err.message}`);
|
|
105
|
+
console.error('');
|
|
106
|
+
reject(err);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Show all decisions chronologically
|
|
114
|
+
*/
|
|
115
|
+
async function showAllDecisions() {
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
118
|
+
console.log('📋 ALL DECISIONS (Chronological)');
|
|
119
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
120
|
+
console.log('');
|
|
121
|
+
|
|
122
|
+
// Show project decisions first
|
|
123
|
+
let projectConfig;
|
|
124
|
+
try {
|
|
125
|
+
projectConfig = config.read();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error('⚠️ Unable to read project configuration');
|
|
128
|
+
console.error(`Error: ${err.message}`);
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log('Skipping project-level decisions...');
|
|
131
|
+
console.log('');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (projectConfig && projectConfig.project_discovery && projectConfig.project_discovery.winner) {
|
|
135
|
+
console.log('🎯 PROJECT DECISIONS');
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log(`Winner: ${projectConfig.project_discovery.winner}`);
|
|
138
|
+
if (projectConfig.project_discovery.rationale) {
|
|
139
|
+
console.log(`Rationale: ${projectConfig.project_discovery.rationale}`);
|
|
140
|
+
}
|
|
141
|
+
if (projectConfig.project_discovery.started_date) {
|
|
142
|
+
try {
|
|
143
|
+
console.log(`Decided: ${new Date(projectConfig.project_discovery.started_date).toLocaleDateString()}`);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.log(`Decided: ${projectConfig.project_discovery.started_date}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
console.log('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Show epic decisions
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
db.all(`
|
|
154
|
+
SELECT dd.*, w.title as epic_title
|
|
155
|
+
FROM discovery_decisions dd
|
|
156
|
+
JOIN work_items w ON dd.work_item_id = w.id
|
|
157
|
+
ORDER BY dd.created_at ASC
|
|
158
|
+
`, [], (err, rows) => {
|
|
159
|
+
if (err) {
|
|
160
|
+
console.error('❌ Database error while retrieving epic decisions');
|
|
161
|
+
console.error(`Error: ${err.message}`);
|
|
162
|
+
console.log('');
|
|
163
|
+
console.log('This could mean:');
|
|
164
|
+
console.log(' - Database schema is out of date (run migrations)');
|
|
165
|
+
console.log(' - Database is corrupted');
|
|
166
|
+
console.log(' - Table discovery_decisions or work_items does not exist');
|
|
167
|
+
console.log('');
|
|
168
|
+
return reject(err);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (rows && rows.length > 0) {
|
|
172
|
+
console.log('🎯 EPIC DECISIONS');
|
|
173
|
+
console.log('');
|
|
174
|
+
|
|
175
|
+
rows.forEach(row => {
|
|
176
|
+
console.log(`Epic #${row.work_item_id}: ${row.epic_title}`);
|
|
177
|
+
console.log(`├─ ${row.aspect}: ${row.decision}`);
|
|
178
|
+
console.log(`├─ Rationale: ${row.rationale || 'No rationale provided'}`);
|
|
179
|
+
try {
|
|
180
|
+
console.log(`└─ Decided: ${new Date(row.created_at).toLocaleDateString()}`);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.log(`└─ Decided: ${row.created_at}`);
|
|
183
|
+
}
|
|
184
|
+
console.log('');
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
console.log('No epic decisions yet.');
|
|
188
|
+
console.log('');
|
|
189
|
+
console.log('💡 Tip: Make architectural decisions during epic planning:');
|
|
190
|
+
console.log(' jettypod work epic-planning <epic-id>');
|
|
191
|
+
console.log('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
resolve();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Show project-level decisions
|
|
201
|
+
*/
|
|
202
|
+
function showProjectDecisions() {
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
205
|
+
console.log('📋 PROJECT-LEVEL DECISIONS');
|
|
206
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
207
|
+
console.log('');
|
|
208
|
+
|
|
209
|
+
let projectConfig;
|
|
210
|
+
try {
|
|
211
|
+
projectConfig = config.read();
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error('❌ Unable to read project configuration');
|
|
214
|
+
console.error(`Error: ${err.message}`);
|
|
215
|
+
console.log('');
|
|
216
|
+
console.log('This could mean:');
|
|
217
|
+
console.log(' - .jettypod/config.json is missing or corrupted');
|
|
218
|
+
console.log(' - JettyPod is not initialized (run jettypod init)');
|
|
219
|
+
console.log(' - Insufficient permissions to read config file');
|
|
220
|
+
console.log('');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (projectConfig && projectConfig.project_discovery && projectConfig.project_discovery.winner) {
|
|
225
|
+
console.log(`Winner: ${projectConfig.project_discovery.winner}`);
|
|
226
|
+
if (projectConfig.project_discovery.rationale) {
|
|
227
|
+
console.log(`Rationale: ${projectConfig.project_discovery.rationale}`);
|
|
228
|
+
}
|
|
229
|
+
if (projectConfig.project_discovery.started_date) {
|
|
230
|
+
try {
|
|
231
|
+
console.log(`Decided: ${new Date(projectConfig.project_discovery.started_date).toLocaleDateString()}`);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
console.log(`Decided: ${projectConfig.project_discovery.started_date}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
console.log('No project-level decisions yet.');
|
|
238
|
+
console.log('');
|
|
239
|
+
console.log('💡 Tip: Start project discovery to make UX and tech stack decisions:');
|
|
240
|
+
console.log(' Talk to Claude Code about what you want to build');
|
|
241
|
+
console.log('');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log('');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Show epic-level decisions
|
|
249
|
+
*/
|
|
250
|
+
async function showEpicDecisions() {
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
253
|
+
console.log('📋 EPIC-LEVEL DECISIONS');
|
|
254
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
255
|
+
console.log('');
|
|
256
|
+
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
db.all(`
|
|
259
|
+
SELECT dd.*, w.title as epic_title
|
|
260
|
+
FROM discovery_decisions dd
|
|
261
|
+
JOIN work_items w ON dd.work_item_id = w.id
|
|
262
|
+
ORDER BY dd.created_at DESC
|
|
263
|
+
`, [], (err, rows) => {
|
|
264
|
+
if (err) {
|
|
265
|
+
console.error('❌ Database error while retrieving epic decisions');
|
|
266
|
+
console.error(`Error: ${err.message}`);
|
|
267
|
+
console.log('');
|
|
268
|
+
console.log('This could mean:');
|
|
269
|
+
console.log(' - Database schema is out of date (run migrations)');
|
|
270
|
+
console.log(' - Database is corrupted');
|
|
271
|
+
console.log(' - Table discovery_decisions or work_items does not exist');
|
|
272
|
+
console.log('');
|
|
273
|
+
return reject(err);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (rows && rows.length > 0) {
|
|
277
|
+
rows.forEach(row => {
|
|
278
|
+
console.log(`Epic #${row.work_item_id}: ${row.epic_title}`);
|
|
279
|
+
console.log(`├─ ${row.aspect}: ${row.decision}`);
|
|
280
|
+
console.log(`├─ Rationale: ${row.rationale || 'No rationale provided'}`);
|
|
281
|
+
try {
|
|
282
|
+
console.log(`└─ Decided: ${new Date(row.created_at).toLocaleDateString()}`);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.log(`└─ Decided: ${row.created_at}`);
|
|
285
|
+
}
|
|
286
|
+
console.log('');
|
|
287
|
+
});
|
|
288
|
+
} else {
|
|
289
|
+
console.log('No epic decisions yet.');
|
|
290
|
+
console.log('');
|
|
291
|
+
console.log('💡 Tip: Make architectural decisions during epic planning:');
|
|
292
|
+
console.log(' jettypod work epic-planning <epic-id>');
|
|
293
|
+
console.log('');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
resolve();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Prompt for epic ID and show its decisions
|
|
303
|
+
*/
|
|
304
|
+
async function promptForEpicId() {
|
|
305
|
+
return new Promise((resolve) => {
|
|
306
|
+
const rl = readline.createInterface({
|
|
307
|
+
input: process.stdin,
|
|
308
|
+
output: process.stdout
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
rl.question('Which epic? (enter ID): ', async (answer) => {
|
|
312
|
+
rl.close();
|
|
313
|
+
|
|
314
|
+
const trimmed = answer.trim();
|
|
315
|
+
|
|
316
|
+
// Handle empty input
|
|
317
|
+
if (!trimmed) {
|
|
318
|
+
console.log('');
|
|
319
|
+
console.log('❌ No epic ID entered');
|
|
320
|
+
console.log('Please run the command again and enter an epic ID');
|
|
321
|
+
console.log('');
|
|
322
|
+
console.log('💡 Tip: See your epics with: jettypod backlog');
|
|
323
|
+
console.log('');
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const epicId = parseInt(trimmed);
|
|
328
|
+
|
|
329
|
+
// Handle non-numeric input
|
|
330
|
+
if (isNaN(epicId)) {
|
|
331
|
+
console.log('');
|
|
332
|
+
console.log(`❌ Invalid epic ID: "${trimmed}"`);
|
|
333
|
+
console.log('Please enter a numeric epic ID');
|
|
334
|
+
console.log('');
|
|
335
|
+
console.log('💡 Tip: See your epics with: jettypod backlog');
|
|
336
|
+
console.log('');
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Handle negative/zero IDs
|
|
341
|
+
if (epicId <= 0) {
|
|
342
|
+
console.log('');
|
|
343
|
+
console.log(`❌ Invalid epic ID: ${epicId}`);
|
|
344
|
+
console.log('Epic IDs must be positive numbers');
|
|
345
|
+
console.log('');
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
await showDecisionsForEpic(epicId);
|
|
351
|
+
resolve();
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.error('');
|
|
354
|
+
console.error('❌ An error occurred while displaying decisions');
|
|
355
|
+
console.error(`Error: ${err.message}`);
|
|
356
|
+
console.error('');
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Show decisions for a specific epic
|
|
365
|
+
*/
|
|
366
|
+
async function showDecisionsForEpic(epicId) {
|
|
367
|
+
return new Promise((resolve, reject) => {
|
|
368
|
+
// First get the epic title
|
|
369
|
+
db.get('SELECT title FROM work_items WHERE id = ? AND type = ?', [epicId, 'epic'], (err, epic) => {
|
|
370
|
+
if (err) {
|
|
371
|
+
console.error('❌ Database error while looking up epic');
|
|
372
|
+
console.error(`Error: ${err.message}`);
|
|
373
|
+
console.log('');
|
|
374
|
+
console.log('This could mean:');
|
|
375
|
+
console.log(' - Database is corrupted');
|
|
376
|
+
console.log(' - Table work_items does not exist');
|
|
377
|
+
console.log('');
|
|
378
|
+
return reject(err);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!epic) {
|
|
382
|
+
console.log('');
|
|
383
|
+
console.log(`❌ Epic #${epicId} not found`);
|
|
384
|
+
console.log('');
|
|
385
|
+
console.log('This could mean:');
|
|
386
|
+
console.log(` - Epic #${epicId} does not exist`);
|
|
387
|
+
console.log(` - Work item #${epicId} is not an epic`);
|
|
388
|
+
console.log('');
|
|
389
|
+
console.log('💡 Tip: See your epics with: jettypod backlog');
|
|
390
|
+
console.log('');
|
|
391
|
+
return resolve();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log('');
|
|
395
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
396
|
+
console.log(`📋 DECISIONS FOR EPIC #${epicId}`);
|
|
397
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
398
|
+
console.log('');
|
|
399
|
+
console.log(`Epic: ${epic.title}`);
|
|
400
|
+
console.log('');
|
|
401
|
+
|
|
402
|
+
db.all('SELECT * FROM discovery_decisions WHERE work_item_id = ? ORDER BY created_at DESC', [epicId], (err, rows) => {
|
|
403
|
+
if (err) {
|
|
404
|
+
console.error('❌ Database error while retrieving decisions');
|
|
405
|
+
console.error(`Error: ${err.message}`);
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log('This could mean:');
|
|
408
|
+
console.log(' - Database is corrupted');
|
|
409
|
+
console.log(' - Table discovery_decisions does not exist');
|
|
410
|
+
console.log('');
|
|
411
|
+
return reject(err);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (rows && rows.length > 0) {
|
|
415
|
+
rows.forEach(row => {
|
|
416
|
+
console.log(`${row.aspect}: ${row.decision}`);
|
|
417
|
+
console.log(`├─ Rationale: ${row.rationale || 'No rationale provided'}`);
|
|
418
|
+
try {
|
|
419
|
+
console.log(`└─ Decided: ${new Date(row.created_at).toLocaleDateString()}`);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
console.log(`└─ Decided: ${row.created_at}`);
|
|
422
|
+
}
|
|
423
|
+
console.log('');
|
|
424
|
+
});
|
|
425
|
+
} else {
|
|
426
|
+
console.log('No decisions for this epic yet.');
|
|
427
|
+
console.log('');
|
|
428
|
+
console.log('💡 Tip: Make architectural decisions during epic planning:');
|
|
429
|
+
console.log(` jettypod work epic-planning ${epicId}`);
|
|
430
|
+
console.log('');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
resolve();
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* View DECISIONS.md file
|
|
441
|
+
*/
|
|
442
|
+
function viewDecisionsFile() {
|
|
443
|
+
const fs = require('fs');
|
|
444
|
+
const path = require('path');
|
|
445
|
+
|
|
446
|
+
const decisionsPath = path.join(process.cwd(), 'docs', 'DECISIONS.md');
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
if (fs.existsSync(decisionsPath)) {
|
|
450
|
+
try {
|
|
451
|
+
const content = fs.readFileSync(decisionsPath, 'utf8');
|
|
452
|
+
console.log('');
|
|
453
|
+
console.log(content);
|
|
454
|
+
} catch (readErr) {
|
|
455
|
+
console.error('❌ Unable to read DECISIONS.md');
|
|
456
|
+
console.error(`Error: ${readErr.message}`);
|
|
457
|
+
console.log('');
|
|
458
|
+
console.log('This could mean:');
|
|
459
|
+
console.log(' - File is corrupted');
|
|
460
|
+
console.log(' - Insufficient permissions to read file');
|
|
461
|
+
console.log(' - File is locked by another process');
|
|
462
|
+
console.log('');
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
console.log('');
|
|
466
|
+
console.log('docs/DECISIONS.md not found.');
|
|
467
|
+
console.log('');
|
|
468
|
+
console.log('💡 This file will be created automatically when:');
|
|
469
|
+
console.log(' - You complete project discovery');
|
|
470
|
+
console.log(' - You make epic architectural decisions');
|
|
471
|
+
console.log('');
|
|
472
|
+
console.log('To make decisions:');
|
|
473
|
+
console.log(' jettypod work epic-planning <epic-id>');
|
|
474
|
+
console.log('');
|
|
475
|
+
}
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.error('❌ Error checking for DECISIONS.md');
|
|
478
|
+
console.error(`Error: ${err.message}`);
|
|
479
|
+
console.log('');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
module.exports = {
|
|
484
|
+
showDecisionsMenu,
|
|
485
|
+
showAllDecisions,
|
|
486
|
+
showProjectDecisions,
|
|
487
|
+
showEpicDecisions,
|
|
488
|
+
showDecisionsForEpic,
|
|
489
|
+
viewDecisionsFile
|
|
490
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Feature: Git Hook Integration
|
|
2
|
+
As a developer
|
|
3
|
+
I want work item status to update automatically on git operations
|
|
4
|
+
So that I don't have to manually track progress
|
|
5
|
+
|
|
6
|
+
Scenario: First commit updates status to in_progress
|
|
7
|
+
Given I have a work item with status "todo"
|
|
8
|
+
And the work item is set as current work
|
|
9
|
+
When I make my first commit
|
|
10
|
+
Then the work item status should be "in_progress"
|
|
11
|
+
|
|
12
|
+
Scenario: Merge to main updates status to done
|
|
13
|
+
Given I have a work item with status "in_progress"
|
|
14
|
+
And the work item is set as current work
|
|
15
|
+
And I am on a feature branch
|
|
16
|
+
When I merge to main
|
|
17
|
+
Then the work item status should be "done"
|
|
18
|
+
|
|
19
|
+
Scenario: No current work item - hooks do nothing
|
|
20
|
+
Given no work item is set as current
|
|
21
|
+
When I make a commit
|
|
22
|
+
Then no errors occur
|
|
23
|
+
|
|
24
|
+
Scenario: Integration - hooks work with existing work commands
|
|
25
|
+
Given I have initialized jettypod with git
|
|
26
|
+
And I create a work item via work commands
|
|
27
|
+
And I start work on the item
|
|
28
|
+
When I commit changes
|
|
29
|
+
Then the work item status updates automatically
|
|
30
|
+
And the current work file still exists
|