@ranger-testing/ranger-cli 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +28 -65
  2. package/build/cli.js +105 -102
  3. package/build/cli.js.map +1 -1
  4. package/build/commands/addEnv.js +1 -1
  5. package/build/commands/addEnv.js.map +1 -1
  6. package/build/commands/authEncrypt.js +7 -6
  7. package/build/commands/authEncrypt.js.map +1 -1
  8. package/build/commands/clean.js +1 -1
  9. package/build/commands/clean.js.map +1 -1
  10. package/build/commands/config.js +5 -4
  11. package/build/commands/config.js.map +1 -1
  12. package/build/commands/dataMcpServer.js +1 -1
  13. package/build/commands/dataMcpServer.js.map +1 -1
  14. package/build/commands/env.js +17 -10
  15. package/build/commands/env.js.map +1 -1
  16. package/build/commands/feature.js +208 -273
  17. package/build/commands/feature.js.map +1 -1
  18. package/build/commands/index.js +3 -0
  19. package/build/commands/index.js.map +1 -1
  20. package/build/commands/postEditHook.js +25 -0
  21. package/build/commands/postEditHook.js.map +1 -0
  22. package/build/commands/preCompactHook.js +85 -0
  23. package/build/commands/preCompactHook.js.map +1 -0
  24. package/build/commands/sessionStartHook.js +64 -0
  25. package/build/commands/sessionStartHook.js.map +1 -0
  26. package/build/commands/skillup.js +21 -21
  27. package/build/commands/skillup.js.map +1 -1
  28. package/build/commands/start.js +1 -1
  29. package/build/commands/start.js.map +1 -1
  30. package/build/commands/status.js +30 -44
  31. package/build/commands/status.js.map +1 -1
  32. package/build/commands/update.js +32 -40
  33. package/build/commands/update.js.map +1 -1
  34. package/build/commands/updateEnv.js +1 -1
  35. package/build/commands/updateEnv.js.map +1 -1
  36. package/build/commands/useEnv.js +1 -1
  37. package/build/commands/useEnv.js.map +1 -1
  38. package/build/commands/utils/browserSessionsApi.js +1 -1
  39. package/build/commands/utils/browserSessionsApi.js.map +1 -1
  40. package/build/commands/utils/cliSecret.js +1 -1
  41. package/build/commands/utils/environment.js +0 -6
  42. package/build/commands/utils/environment.js.map +1 -1
  43. package/build/commands/utils/featureApi.js +68 -24
  44. package/build/commands/utils/featureApi.js.map +1 -1
  45. package/build/commands/utils/featureReportGenerator.js +37 -3
  46. package/build/commands/utils/featureReportGenerator.js.map +1 -1
  47. package/build/commands/utils/keychain.js +1 -1
  48. package/build/commands/utils/keychain.js.map +1 -1
  49. package/build/commands/utils/localAgentInstallationsApi.js +1 -1
  50. package/build/commands/utils/mcpConfig.js +1 -1
  51. package/build/commands/utils/rangerRoot.js +30 -0
  52. package/build/commands/utils/rangerRoot.js.map +1 -0
  53. package/build/commands/utils/settings.js +7 -5
  54. package/build/commands/utils/settings.js.map +1 -1
  55. package/build/commands/utils/skillContent.js +28 -0
  56. package/build/commands/utils/skillContent.js.map +1 -0
  57. package/build/commands/utils/skills.js +1 -1
  58. package/build/commands/utils/skills.js.map +1 -1
  59. package/build/commands/utils/userApi.js +32 -0
  60. package/build/commands/utils/userApi.js.map +1 -0
  61. package/build/commands/verifyFeature.js +429 -104
  62. package/build/commands/verifyFeature.js.map +1 -1
  63. package/build/commands/verifyInBrowser.js +1 -1
  64. package/build/commands/verifyInBrowser.js.map +1 -1
  65. package/build/skills/bug-bash.md +31 -10
  66. package/build/skills/feature-tracker/SKILL.md +8 -30
  67. package/build/skills/feature-tracker/create.md +47 -38
  68. package/build/skills/feature-tracker/start.md +4 -4
  69. package/build/skills/feature-tracker/verify.md +10 -14
  70. package/package.json +5 -3
  71. package/scripts/postinstall.js +18 -0
  72. package/build/skills/feature-tracker/manage.md +0 -145
@@ -3,10 +3,10 @@ import { join } from 'path';
3
3
  import { existsSync } from 'fs';
4
4
  import { execSync } from 'child_process';
5
5
  import inquirer from 'inquirer';
6
- import { createFeature, listFeatures, getFeature, updateFeature, addChecklistItem, updateChecklistItem, getFeatureReport, } from './utils/featureApi.js';
7
- import { generateFeatureReport } from './utils/featureReportGenerator.js';
8
- // Active feature file path
9
- const ACTIVE_FEATURE_FILE = '.ranger/active-feature.txt';
6
+ import { createFeature, listFeatures, getFeature, getFeatureReportMarkdown, listFeatureSessions, startSession, concludeSession, } from './utils/featureApi.js';
7
+ import { getRangerDir } from './utils/rangerRoot.js';
8
+ // Active feature file name (relative to .ranger dir)
9
+ const ACTIVE_FEATURE_FILE = 'active-feature.txt';
10
10
  /**
11
11
  * Get the current git remote URL
12
12
  */
@@ -44,7 +44,7 @@ function getGitBranch() {
44
44
  * Get the active feature ID from the local file
45
45
  */
46
46
  export async function getActiveFeatureId() {
47
- const filePath = join(process.cwd(), ACTIVE_FEATURE_FILE);
47
+ const filePath = join(getRangerDir(), ACTIVE_FEATURE_FILE);
48
48
  if (!existsSync(filePath)) {
49
49
  return null;
50
50
  }
@@ -60,24 +60,77 @@ export async function getActiveFeatureId() {
60
60
  * Set the active feature ID
61
61
  */
62
62
  async function setActiveFeatureId(featureId) {
63
- const filePath = join(process.cwd(), ACTIVE_FEATURE_FILE);
64
- const dir = join(process.cwd(), '.ranger');
63
+ const dir = getRangerDir();
64
+ const filePath = join(dir, ACTIVE_FEATURE_FILE);
65
65
  if (!existsSync(dir)) {
66
66
  await mkdir(dir, { recursive: true });
67
67
  }
68
68
  await writeFile(filePath, featureId, 'utf-8');
69
69
  }
70
70
  /**
71
- * Display a feature with its checklist
71
+ * Get Dev Status display info for a feature based on completedAt and currentSession
72
+ */
73
+ function getDevStatusDisplay(feature) {
74
+ if (feature.completedAt) {
75
+ return { emoji: '\u2705', label: 'Completed' };
76
+ }
77
+ if (!feature.currentSession) {
78
+ return { emoji: '\u2b1c', label: 'Unknown' };
79
+ }
80
+ switch (feature.currentSession.status) {
81
+ case 'ready':
82
+ return { emoji: '\u25b6\ufe0f', label: 'Ready' }; // ▶️
83
+ case 'in_progress':
84
+ return { emoji: '\ud83d\udd04', label: 'In Progress' }; // 🔄
85
+ case 'completed':
86
+ return { emoji: '\u2705', label: 'Completed' }; // session completed
87
+ default:
88
+ return { emoji: '\u2753', label: 'Unknown' };
89
+ }
90
+ }
91
+ /**
92
+ * Get Review Status display info for a feature
93
+ */
94
+ function getReviewStatusDisplay(feature) {
95
+ const { latestReview, currentSession, completedAt } = feature;
96
+ // Determine review status
97
+ if (completedAt) {
98
+ return { emoji: '\u2705', label: 'Approved' };
99
+ }
100
+ if (latestReview) {
101
+ if (!latestReview.submittedAt) {
102
+ return { emoji: '\ud83d\udcdd', label: 'In Review' }; // 📝
103
+ }
104
+ if (latestReview.overallVerdict === 'approved') {
105
+ return { emoji: '\u2705', label: 'Approved' };
106
+ }
107
+ return { emoji: '\ud83d\udd04', label: 'Changes Needed' }; // 🔄
108
+ }
109
+ if (currentSession?.status === 'completed') {
110
+ return { emoji: '\u23f3', label: 'Awaiting Review' }; // ⏳
111
+ }
112
+ // Not ready for review
113
+ return null;
114
+ }
115
+ /**
116
+ * Display a feature with its checklist (current session items only)
72
117
  */
73
118
  function displayFeature(feature, checklistItems) {
74
- const statusEmoji = feature.status === 'completed'
75
- ? '\u2705'
76
- : feature.status === 'blocked'
77
- ? '\ud83d\uded1'
78
- : '\ud83d\udd04';
79
- console.log(`\n${statusEmoji} ${feature.name} (${feature.id})`);
80
- console.log(` Status: ${feature.status}`);
119
+ const devStatus = getDevStatusDisplay(feature);
120
+ const reviewStatus = getReviewStatusDisplay(feature);
121
+ console.log(`\n${devStatus.emoji} ${feature.name} (${feature.id})`);
122
+ console.log(` Dev Status: ${devStatus.label}`);
123
+ if (reviewStatus) {
124
+ console.log(` Review Status: ${reviewStatus.emoji} ${reviewStatus.label}`);
125
+ }
126
+ if (feature.stats) {
127
+ const { verifiedItems, totalItems, blockedItems } = feature.stats;
128
+ let progressStr = ` Progress: ${verifiedItems}/${totalItems}`;
129
+ if (blockedItems > 0) {
130
+ progressStr += ` (${blockedItems} blocked)`;
131
+ }
132
+ console.log(progressStr);
133
+ }
81
134
  if (feature.description) {
82
135
  console.log(` Description: ${feature.description}`);
83
136
  }
@@ -92,22 +145,30 @@ function displayFeature(feature, checklistItems) {
92
145
  console.log(` Completed: ${new Date(feature.completedAt).toLocaleString()}`);
93
146
  }
94
147
  if (checklistItems && checklistItems.length > 0) {
95
- console.log('\n Checklist:');
96
- for (let i = 0; i < checklistItems.length; i++) {
97
- const item = checklistItems[i];
98
- const itemEmoji = item.status === 'verified'
99
- ? '\u2705'
100
- : item.status === 'blocked'
101
- ? '\ud83d\uded1'
102
- : item.status === 'canceled'
103
- ? '\u26d4'
104
- : '\u2b1c'; // white square for pending
105
- console.log(` ${i + 1}. ${itemEmoji} ${item.description}`);
106
- if (item.status === 'blocked' && item.blockedReason) {
107
- console.log(` Blocked: ${item.blockedReason}`);
108
- }
109
- if (item.status === 'canceled' && item.canceledReason) {
110
- console.log(` Canceled: ${item.canceledReason}`);
148
+ // Filter to only show items from the current session
149
+ const currentSessionItems = feature.currentSessionId
150
+ ? checklistItems.filter((item) => item.sessionId === feature.currentSessionId)
151
+ : checklistItems;
152
+ if (currentSessionItems.length > 0) {
153
+ console.log('\n Checklist:');
154
+ for (let i = 0; i < currentSessionItems.length; i++) {
155
+ const item = currentSessionItems[i];
156
+ const itemEmoji = item.status === 'verified'
157
+ ? '\u2705'
158
+ : item.status === 'incomplete'
159
+ ? '\ud83d\udfe0' // orange circle
160
+ : item.status === 'blocked'
161
+ ? '\ud83d\uded1'
162
+ : item.status === 'canceled'
163
+ ? '\u26d4'
164
+ : '\u2b1c'; // white square for pending
165
+ console.log(` ${i + 1}. ${itemEmoji} ${item.description}`);
166
+ if (item.status === 'blocked' && item.blockedReason) {
167
+ console.log(` Blocked: ${item.blockedReason}`);
168
+ }
169
+ if (item.status === 'canceled' && item.canceledReason) {
170
+ console.log(` Canceled: ${item.canceledReason}`);
171
+ }
111
172
  }
112
173
  }
113
174
  }
@@ -116,9 +177,9 @@ function displayFeature(feature, checklistItems) {
116
177
  * Create a new feature
117
178
  */
118
179
  export async function featureCreate(name, options) {
119
- // Parse checklist from comma-separated string
180
+ // Get checklist items from array (each -c flag is one item)
120
181
  const checklistItems = options.checklist
121
- ? options.checklist.split(',').map((s) => s.trim())
182
+ ? options.checklist.map((s) => s.trim())
122
183
  : [];
123
184
  // Auto-detect git context
124
185
  const gitRepoUrl = getGitRepoUrl();
@@ -133,6 +194,8 @@ export async function featureCreate(name, options) {
133
194
  });
134
195
  // Set as active feature
135
196
  await setActiveFeatureId(feature.id);
197
+ // Start the session immediately so it's in_progress
198
+ await tryStartCurrentSession(feature.id);
136
199
  console.log(`\n\u2705 Feature created: ${feature.id}`);
137
200
  displayFeature(feature, feature.checklistItems);
138
201
  console.log(`\n\u27a1\ufe0f Set as active feature`);
@@ -142,9 +205,6 @@ export async function featureCreate(name, options) {
142
205
  */
143
206
  export async function featureList(options) {
144
207
  const filters = {};
145
- if (options.status) {
146
- filters.status = options.status;
147
- }
148
208
  if (options.currentBranch) {
149
209
  filters.gitRepoUrl = getGitRepoUrl();
150
210
  filters.gitBranch = getGitBranch();
@@ -156,14 +216,22 @@ export async function featureList(options) {
156
216
  }
157
217
  console.log(`\n${result.items.length} feature(s) found:\n`);
158
218
  for (const feature of result.items) {
159
- const statusEmoji = feature.status === 'completed'
160
- ? '\u2705'
161
- : feature.status === 'blocked'
162
- ? '\ud83d\uded1'
163
- : '\ud83d\udd04';
164
- console.log(`${statusEmoji} ${feature.name}`);
219
+ const devStatus = getDevStatusDisplay(feature);
220
+ const reviewStatus = getReviewStatusDisplay(feature);
221
+ console.log(`${devStatus.emoji} ${feature.name}`);
165
222
  console.log(` ID: ${feature.id}`);
166
- console.log(` Status: ${feature.status}`);
223
+ console.log(` Dev Status: ${devStatus.label}`);
224
+ if (reviewStatus) {
225
+ console.log(` Review Status: ${reviewStatus.emoji} ${reviewStatus.label}`);
226
+ }
227
+ if (feature.stats) {
228
+ const { verifiedItems, totalItems, blockedItems } = feature.stats;
229
+ let progressStr = ` Progress: ${verifiedItems}/${totalItems}`;
230
+ if (blockedItems > 0) {
231
+ progressStr += ` (${blockedItems} blocked)`;
232
+ }
233
+ console.log(progressStr);
234
+ }
167
235
  if (feature.gitBranch) {
168
236
  console.log(` Branch: ${feature.gitBranch}`);
169
237
  }
@@ -194,8 +262,19 @@ export async function featureUse(id) {
194
262
  }
195
263
  /**
196
264
  * Resume feature matching current git context
265
+ * @param id Optional feature ID to resume directly (bypasses search/prompt)
197
266
  */
198
- export async function featureResume() {
267
+ export async function featureResume(id) {
268
+ // If ID provided, use it directly (like featureUse)
269
+ if (id) {
270
+ const feature = await getFeature(id);
271
+ await setActiveFeatureId(feature.id);
272
+ console.log(`\n\u2705 Resumed feature: ${feature.name} (${feature.id})`);
273
+ // Start the current session if it's ready
274
+ await tryStartCurrentSession(feature.id);
275
+ displayFeature(feature, feature.checklistItems);
276
+ return;
277
+ }
199
278
  const gitRepoUrl = getGitRepoUrl();
200
279
  const gitBranch = getGitBranch();
201
280
  if (!gitRepoUrl && !gitBranch) {
@@ -204,18 +283,19 @@ export async function featureResume() {
204
283
  }
205
284
  console.log(`\nSearching for features matching: ${gitRepoUrl || 'any'} / ${gitBranch || 'any'}...`);
206
285
  const result = await listFeatures({
207
- status: 'in_progress',
208
286
  gitRepoUrl,
209
287
  gitBranch,
210
288
  });
211
- if (result.items.length === 0) {
212
- console.log('\nNo matching in-progress features found.');
289
+ // Filter to non-completed features
290
+ const activeFeatures = result.items.filter((f) => !f.completedAt);
291
+ if (activeFeatures.length === 0) {
292
+ console.log('\nNo matching active features found.');
213
293
  console.log('Run: ranger feature create "<name>" to create a new feature');
214
294
  return;
215
295
  }
216
296
  let selectedFeature;
217
- if (result.items.length === 1) {
218
- selectedFeature = result.items[0];
297
+ if (activeFeatures.length === 1) {
298
+ selectedFeature = activeFeatures[0];
219
299
  }
220
300
  else {
221
301
  // Multiple matches - prompt user to select
@@ -224,271 +304,126 @@ export async function featureResume() {
224
304
  type: 'list',
225
305
  name: 'selected',
226
306
  message: 'Multiple features found. Select one:',
227
- choices: result.items.map((f) => ({
307
+ choices: activeFeatures.map((f) => ({
228
308
  name: `${f.name} (${f.id})`,
229
309
  value: f.id,
230
310
  })),
231
311
  },
232
312
  ]);
233
- selectedFeature = result.items.find((f) => f.id === selected);
313
+ selectedFeature = activeFeatures.find((f) => f.id === selected);
234
314
  }
235
315
  await setActiveFeatureId(selectedFeature.id);
236
316
  console.log(`\n\u2705 Resumed feature: ${selectedFeature.name} (${selectedFeature.id})`);
317
+ // Start the current session if it's ready
318
+ await tryStartCurrentSession(selectedFeature.id);
237
319
  // Show current status
238
320
  const fullFeature = await getFeature(selectedFeature.id);
239
321
  displayFeature(fullFeature, fullFeature.checklistItems);
240
322
  }
241
323
  /**
242
- * Block a feature
324
+ * Try to start the current session if it's in ready status
243
325
  */
244
- export async function featureBlock(id, options) {
326
+ async function tryStartCurrentSession(featureId) {
327
+ try {
328
+ const { sessions } = await listFeatureSessions(featureId);
329
+ // Find the current session (most recent ready one)
330
+ const readySession = sessions.find((s) => s.status === 'ready');
331
+ if (readySession) {
332
+ await startSession(featureId, readySession.id);
333
+ console.log(` \u25b6\ufe0f Started session ${readySession.iteration}`);
334
+ }
335
+ }
336
+ catch {
337
+ // Silently ignore - session may already be started or in another state
338
+ }
339
+ }
340
+ /**
341
+ * Generate feature report
342
+ */
343
+ export async function featureReport(id, options) {
245
344
  const featureId = id || (await getActiveFeatureId());
246
345
  if (!featureId) {
247
346
  console.error('\n\u274c No feature ID provided and no active feature set.');
248
347
  process.exit(1);
249
348
  }
250
- const feature = await updateFeature(featureId, { status: 'blocked' });
251
- console.log(`\n\ud83d\uded1 Feature blocked: ${feature.name} (${feature.id})`);
252
- console.log(` Reason: ${options.reason}`);
253
- }
254
- /**
255
- * Helper to get item by index (1-based)
256
- */
257
- function getItemByIndex(items, index) {
258
- const itemIndex = index - 1; // Convert to 0-based
259
- if (itemIndex < 0 || itemIndex >= items.length) {
260
- throw new Error(`Invalid item index: ${index}. Feature has ${items.length} items.`);
349
+ console.log('\nGenerating report...');
350
+ // Fetch markdown report from API (includes embedded screenshots)
351
+ const markdown = await getFeatureReportMarkdown(featureId, {
352
+ style: options.style,
353
+ });
354
+ // Determine output path
355
+ const outputPath = options.output || join(getRangerDir(), 'reports', `${featureId}.md`);
356
+ // Ensure directory exists
357
+ const outputDir = join(outputPath, '..');
358
+ if (!existsSync(outputDir)) {
359
+ await mkdir(outputDir, { recursive: true });
261
360
  }
262
- return items[itemIndex];
361
+ await writeFile(outputPath, markdown, 'utf-8');
362
+ console.log(`\n\u2705 Report generated: ${outputPath}`);
263
363
  }
264
364
  /**
265
- * Manage checklist items (interactive or with flags)
365
+ * Conclude the current session (even with incomplete items)
266
366
  */
267
- export async function featureManage(options = {}) {
268
- const featureId = await getActiveFeatureId();
367
+ export async function featureConcludeSession(id) {
368
+ const featureId = id || (await getActiveFeatureId());
269
369
  if (!featureId) {
270
- console.error('\n\u274c No active feature set.');
370
+ console.error('\n\u274c No feature ID provided and no active feature set.');
271
371
  console.error('Run: ranger feature use <id> to set an active feature');
272
372
  process.exit(1);
273
373
  }
274
374
  const feature = await getFeature(featureId);
275
- // Check if any non-interactive flag is provided
276
- const hasNonInteractiveFlag = options.add !== undefined ||
277
- options.edit !== undefined ||
278
- options.block !== undefined ||
279
- options.cancel !== undefined ||
280
- options.reset !== undefined;
281
- // Non-interactive mode: handle flags directly
282
- if (hasNonInteractiveFlag) {
283
- // --add: Add a new item
284
- if (options.add) {
285
- const item = await addChecklistItem(featureId, {
286
- description: options.add.trim(),
287
- });
288
- console.log(`\u2705 Added: ${item.description}`);
289
- return;
290
- }
291
- // --edit: Edit item description
292
- if (options.edit !== undefined) {
293
- if (!options.description) {
294
- console.error('\u274c --description is required when using --edit');
295
- process.exit(1);
296
- }
297
- const item = getItemByIndex(feature.checklistItems, options.edit);
298
- await updateChecklistItem(featureId, item.id, {
299
- description: options.description.trim(),
300
- });
301
- console.log(`\u270f\ufe0f Item ${options.edit} updated: ${options.description.trim()}`);
302
- return;
303
- }
304
- // --block: Block an item
305
- if (options.block !== undefined) {
306
- const item = getItemByIndex(feature.checklistItems, options.block);
307
- await updateChecklistItem(featureId, item.id, {
308
- status: 'blocked',
309
- blockedReason: options.reason || undefined,
310
- });
311
- console.log(`\ud83d\uded1 Item ${options.block} blocked: ${item.description}`);
312
- return;
313
- }
314
- // --cancel: Cancel an item
315
- if (options.cancel !== undefined) {
316
- const item = getItemByIndex(feature.checklistItems, options.cancel);
317
- await updateChecklistItem(featureId, item.id, {
318
- status: 'canceled',
319
- canceledReason: options.reason || undefined,
320
- });
321
- console.log(`\u26d4 Item ${options.cancel} canceled: ${item.description}`);
322
- return;
323
- }
324
- // --reset: Reset item to pending
325
- if (options.reset !== undefined) {
326
- const item = getItemByIndex(feature.checklistItems, options.reset);
327
- await updateChecklistItem(featureId, item.id, {
328
- status: 'pending',
329
- });
330
- console.log(`\u2b1c Item ${options.reset} reset to pending: ${item.description}`);
331
- return;
332
- }
333
- }
334
- // Interactive mode: show prompts
335
- console.log(`\nManaging: ${feature.name} (${feature.id})\n`);
336
- // Show current checklist
337
- if (feature.checklistItems.length > 0) {
338
- console.log('Current checklist:');
339
- for (let i = 0; i < feature.checklistItems.length; i++) {
340
- const item = feature.checklistItems[i];
341
- const itemEmoji = item.status === 'verified'
342
- ? '\u2705'
343
- : item.status === 'blocked'
344
- ? '\ud83d\uded1'
345
- : item.status === 'canceled'
346
- ? '\u26d4'
347
- : '\u2b1c';
348
- console.log(`${i + 1}. ${itemEmoji} ${item.description}`);
349
- }
350
- console.log('');
375
+ if (!feature.currentSessionId) {
376
+ console.error('\n\u274c No active session for this feature.');
377
+ process.exit(1);
351
378
  }
352
- else {
353
- console.log('No checklist items yet.\n');
354
- }
355
- // Prompt for action
356
- const { action } = await inquirer.prompt([
357
- {
358
- type: 'list',
359
- name: 'action',
360
- message: 'What would you like to do?',
361
- choices: [
362
- { name: 'Add new item', value: 'add' },
363
- { name: 'Update existing item', value: 'update' },
364
- { name: 'Cancel', value: 'cancel' },
365
- ],
366
- },
367
- ]);
368
- if (action === 'cancel') {
369
- return;
379
+ const { sessions } = await listFeatureSessions(featureId);
380
+ const currentSession = sessions.find((s) => s.id === feature.currentSessionId);
381
+ if (!currentSession) {
382
+ console.error('\n\u274c Current session not found.');
383
+ process.exit(1);
370
384
  }
371
- if (action === 'add') {
372
- const { description } = await inquirer.prompt([
373
- {
374
- type: 'input',
375
- name: 'description',
376
- message: 'Enter item description:',
377
- validate: (input) => input.trim() ? true : 'Description is required',
378
- },
379
- ]);
380
- const item = await addChecklistItem(featureId, {
381
- description: description.trim(),
382
- });
383
- console.log(`\n\u2705 Added: ${item.description}`);
385
+ if (currentSession.status === 'completed') {
386
+ console.log('\n\u2705 Session is already completed.');
384
387
  return;
385
388
  }
386
- if (action === 'update') {
387
- if (feature.checklistItems.length === 0) {
388
- console.log('\nNo items to update.');
389
- return;
390
- }
391
- const { itemIndex } = await inquirer.prompt([
392
- {
393
- type: 'list',
394
- name: 'itemIndex',
395
- message: 'Select item to update:',
396
- choices: feature.checklistItems.map((item, i) => ({
397
- name: `${i + 1}. ${item.description}`,
398
- value: i,
399
- })),
400
- },
401
- ]);
402
- const selectedItem = feature.checklistItems[itemIndex];
403
- const { updateAction } = await inquirer.prompt([
404
- {
405
- type: 'list',
406
- name: 'updateAction',
407
- message: 'What would you like to do with this item?',
408
- choices: [
409
- { name: 'Edit description', value: 'edit' },
410
- { name: 'Mark as blocked', value: 'block' },
411
- { name: 'Cancel item', value: 'cancel' },
412
- { name: 'Reset to pending', value: 'pending' },
413
- { name: 'Go back', value: 'back' },
414
- ],
415
- },
416
- ]);
417
- if (updateAction === 'back') {
418
- return;
419
- }
420
- if (updateAction === 'edit') {
421
- const { newDescription } = await inquirer.prompt([
422
- {
423
- type: 'input',
424
- name: 'newDescription',
425
- message: 'Enter new description:',
426
- default: selectedItem.description,
427
- validate: (input) => input.trim() ? true : 'Description is required',
428
- },
429
- ]);
430
- await updateChecklistItem(featureId, selectedItem.id, {
431
- description: newDescription.trim(),
432
- });
433
- console.log(`\n\u270f\ufe0f Item updated: ${newDescription.trim()}`);
434
- }
435
- else if (updateAction === 'block') {
436
- const { reason } = await inquirer.prompt([
437
- {
438
- type: 'input',
439
- name: 'reason',
440
- message: 'Enter blocking reason:',
441
- },
442
- ]);
443
- await updateChecklistItem(featureId, selectedItem.id, {
444
- status: 'blocked',
445
- blockedReason: reason || undefined,
446
- });
447
- console.log(`\n\ud83d\uded1 Item blocked: ${selectedItem.description}`);
448
- }
449
- else if (updateAction === 'cancel') {
450
- const { reason } = await inquirer.prompt([
451
- {
452
- type: 'input',
453
- name: 'reason',
454
- message: 'Enter cancellation reason:',
455
- },
456
- ]);
457
- await updateChecklistItem(featureId, selectedItem.id, {
458
- status: 'canceled',
459
- canceledReason: reason || undefined,
460
- });
461
- console.log(`\n\u26d4 Item canceled: ${selectedItem.description}`);
462
- }
463
- else if (updateAction === 'pending') {
464
- await updateChecklistItem(featureId, selectedItem.id, {
465
- status: 'pending',
466
- });
467
- console.log(`\n\u2b1c Item reset to pending: ${selectedItem.description}`);
468
- }
389
+ if (currentSession.status !== 'in_progress') {
390
+ console.error(`\n\u274c Cannot conclude session with status: ${currentSession.status}`);
391
+ process.exit(1);
469
392
  }
393
+ console.log('\nConcluding session...');
394
+ await concludeSession(featureId, feature.currentSessionId);
395
+ console.log(`\n\u2705 Session ${currentSession.iteration} concluded.`);
396
+ console.log(` The feature is now ready for human review on the Ranger dashboard.`);
470
397
  }
471
398
  /**
472
- * Generate feature report
399
+ * List sessions for a feature
473
400
  */
474
- export async function featureReport(id, options) {
401
+ export async function featureSessions(id) {
475
402
  const featureId = id || (await getActiveFeatureId());
476
403
  if (!featureId) {
477
404
  console.error('\n\u274c No feature ID provided and no active feature set.');
478
405
  process.exit(1);
479
406
  }
480
- console.log('\nGenerating report...');
481
- const reportData = await getFeatureReport(featureId);
482
- const markdown = generateFeatureReport(reportData);
483
- // Determine output path
484
- const outputPath = options.output ||
485
- join(process.cwd(), '.ranger', 'reports', `${featureId}.md`);
486
- // Ensure directory exists
487
- const outputDir = join(outputPath, '..');
488
- if (!existsSync(outputDir)) {
489
- await mkdir(outputDir, { recursive: true });
407
+ const feature = await getFeature(featureId);
408
+ console.log(`\n\ud83d\udcc1 Sessions for: ${feature.name}\n`);
409
+ const { sessions } = await listFeatureSessions(featureId);
410
+ if (sessions.length === 0) {
411
+ console.log('No sessions found.');
412
+ return;
413
+ }
414
+ for (const session of sessions) {
415
+ const statusEmoji = session.status === 'completed'
416
+ ? '\u2705'
417
+ : session.status === 'in_progress'
418
+ ? '\ud83d\udd04'
419
+ : '\u25b6\ufe0f'; // ready
420
+ console.log(`${statusEmoji} Session ${session.iteration} (${session.id})`);
421
+ console.log(` Status: ${session.status}`);
422
+ console.log(` Created: ${new Date(session.createdAt).toLocaleString()}`);
423
+ if (session.completedAt) {
424
+ console.log(` Completed: ${new Date(session.completedAt).toLocaleString()}`);
425
+ }
426
+ console.log('');
490
427
  }
491
- await writeFile(outputPath, markdown, 'utf-8');
492
- console.log(`\n\u2705 Report generated: ${outputPath}`);
493
428
  }
494
429
  //# sourceMappingURL=feature.js.map