@shepai/cli 1.49.1 → 1.50.0

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 (59) hide show
  1. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +1 -0
  2. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -1
  3. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +20 -11
  4. package/dist/src/presentation/cli/commands/feat/ls.command.d.ts.map +1 -1
  5. package/dist/src/presentation/cli/commands/feat/ls.command.js +92 -39
  6. package/dist/src/presentation/cli/commands/feat/show.command.js +10 -10
  7. package/dist/tsconfig.build.tsbuildinfo +1 -1
  8. package/package.json +1 -1
  9. package/web/.next/BUILD_ID +1 -1
  10. package/web/.next/build-manifest.json +2 -2
  11. package/web/.next/cache/.previewinfo +1 -1
  12. package/web/.next/cache/.rscinfo +1 -1
  13. package/web/.next/cache/config.json +3 -3
  14. package/web/.next/fallback-build-manifest.json +2 -2
  15. package/web/.next/prerender-manifest.json +3 -3
  16. package/web/.next/required-server-files.js +1 -1
  17. package/web/.next/required-server-files.json +1 -1
  18. package/web/.next/server/app/_global-error.html +2 -2
  19. package/web/.next/server/app/_global-error.rsc +1 -1
  20. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  21. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  22. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  23. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  24. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  25. package/web/.next/server/app/page/server-reference-manifest.json +14 -14
  26. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  28. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  29. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  30. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  31. package/web/.next/server/pages/500.html +2 -2
  32. package/web/.next/server/server-reference-manifest.js +1 -1
  33. package/web/.next/server/server-reference-manifest.json +15 -15
  34. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  35. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  36. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  37. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  38. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  39. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  40. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  41. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  42. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  43. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  44. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  45. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
  46. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  47. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  48. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  49. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  50. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  51. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  52. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
  53. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  54. package/web/.next/static/chunks/{25cab79117866d51.js → 86340848468ecffa.js} +2 -2
  55. package/web/.next/trace +1 -1
  56. package/web/.next/trace-build +1 -1
  57. /package/web/.next/static/{y92RZZcCAiudDG54kYSQf → Hbv-BqJZOvcXHMnbSKEtM}/_buildManifest.js +0 -0
  58. /package/web/.next/static/{y92RZZcCAiudDG54kYSQf → Hbv-BqJZOvcXHMnbSKEtM}/_clientMiddlewareManifest.json +0 -0
  59. /package/web/.next/static/{y92RZZcCAiudDG54kYSQf → Hbv-BqJZOvcXHMnbSKEtM}/_ssgManifest.js +0 -0
@@ -22,6 +22,7 @@ export declare class NotificationWatcherService {
22
22
  private readonly pollIntervalMs;
23
23
  private readonly trackedRuns;
24
24
  private intervalId;
25
+ private isBootstrapped;
25
26
  constructor(runRepository: IAgentRunRepository, phaseTimingRepository: IPhaseTimingRepository, notificationService: INotificationService, pollIntervalMs?: number);
26
27
  isRunning(): boolean;
27
28
  start(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"notification-watcher.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4EAA4E,CAAC;AACtH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+EAA+E,CAAC;AAC5H,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8EAA8E,CAAC;AAmDzH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;IAC/D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAuB;IAC3D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAC/D,OAAO,CAAC,UAAU,CAA+C;gBAG/D,aAAa,EAAE,mBAAmB,EAClC,qBAAqB,EAAE,sBAAsB,EAC7C,mBAAmB,EAAE,oBAAoB,EACzC,cAAc,GAAE,MAAiC;IAQnD,SAAS,IAAI,OAAO;IAIpB,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;YAOE,IAAI;YAUJ,WAAW;IAoDzB,OAAO,CAAC,eAAe;YAgBT,qBAAqB;IAsBnC,OAAO,CAAC,kBAAkB;CAS3B;AAMD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,mBAAmB,EAClC,qBAAqB,EAAE,sBAAsB,EAC7C,mBAAmB,EAAE,oBAAoB,EACzC,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI,CAWN;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAQnE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C"}
1
+ {"version":3,"file":"notification-watcher.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/notifications/notification-watcher.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4EAA4E,CAAC;AACtH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+EAA+E,CAAC;AAC5H,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8EAA8E,CAAC;AAmDzH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;IAC/D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAuB;IAC3D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAC/D,OAAO,CAAC,UAAU,CAA+C;IAEjE,OAAO,CAAC,cAAc,CAAS;gBAG7B,aAAa,EAAE,mBAAmB,EAClC,qBAAqB,EAAE,sBAAsB,EAC7C,mBAAmB,EAAE,oBAAoB,EACzC,cAAc,GAAE,MAAiC;IAQnD,SAAS,IAAI,OAAO;IAIpB,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;YAOE,IAAI;YAUJ,WAAW;IA0DzB,OAAO,CAAC,eAAe;YAgBT,qBAAqB;IAwBnC,OAAO,CAAC,kBAAkB;CAS3B;AAMD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,mBAAmB,EAClC,qBAAqB,EAAE,sBAAsB,EAC7C,mBAAmB,EAAE,oBAAoB,EACzC,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI,CAWN;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAQnE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C"}
@@ -56,6 +56,8 @@ export class NotificationWatcherService {
56
56
  pollIntervalMs;
57
57
  trackedRuns = new Map();
58
58
  intervalId = null;
59
+ // Suppresses notifications on the first poll to avoid replaying historical state
60
+ isBootstrapped = false;
59
61
  constructor(runRepository, phaseTimingRepository, notificationService, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
60
62
  this.runRepository = runRepository;
61
63
  this.phaseTimingRepository = phaseTimingRepository;
@@ -108,7 +110,9 @@ export class NotificationWatcherService {
108
110
  featureName,
109
111
  };
110
112
  this.trackedRuns.set(run.id, newState);
111
- this.emitStatusEvent(run, newState);
113
+ if (this.isBootstrapped) {
114
+ this.emitStatusEvent(run, newState);
115
+ }
112
116
  // Check for completed phases on first observation
113
117
  await this.checkPhaseCompletions(run.id, newState);
114
118
  }
@@ -130,6 +134,9 @@ export class NotificationWatcherService {
130
134
  await this.checkPhaseCompletions(run.id, prevState);
131
135
  }
132
136
  }
137
+ if (!this.isBootstrapped) {
138
+ this.isBootstrapped = true;
139
+ }
133
140
  // Clean up tracking for runs that disappeared from the list
134
141
  for (const trackedId of this.trackedRuns.keys()) {
135
142
  if (!currentRunIds.has(trackedId)) {
@@ -156,16 +163,18 @@ export class NotificationWatcherService {
156
163
  for (const timing of timings) {
157
164
  if (timing.completedAt && !state.completedPhases.has(timing.phase)) {
158
165
  state.completedPhases.add(timing.phase);
159
- const event = {
160
- eventType: NotificationEventType.PhaseCompleted,
161
- agentRunId: runId,
162
- featureName: state.featureName,
163
- phaseName: timing.phase,
164
- message: `Completed ${timing.phase} phase`,
165
- severity: NotificationSeverity.Info,
166
- timestamp: new Date().toISOString(),
167
- };
168
- this.notificationService.notify(event);
166
+ if (this.isBootstrapped) {
167
+ const event = {
168
+ eventType: NotificationEventType.PhaseCompleted,
169
+ agentRunId: runId,
170
+ featureName: state.featureName,
171
+ phaseName: timing.phase,
172
+ message: `Completed ${timing.phase} phase`,
173
+ severity: NotificationSeverity.Info,
174
+ timestamp: new Date().toISOString(),
175
+ };
176
+ this.notificationService.notify(event);
177
+ }
169
178
  }
170
179
  }
171
180
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+GpC,wBAAgB,eAAe,IAAI,OAAO,CAqDzC"}
1
+ {"version":3,"file":"ls.command.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/cli/commands/feat/ls.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8JpC,wBAAgB,eAAe,IAAI,OAAO,CA8DzC"}
@@ -23,13 +23,23 @@ const NODE_TO_PHASE = {
23
23
  plan: 'Planning',
24
24
  implement: 'Implementing',
25
25
  };
26
- /** Map graph node names to approval action labels. */
27
- const NODE_TO_APPROVE = {
28
- analyze: 'Approve Analysis',
29
- requirements: 'Approve PRD',
30
- research: 'Approve Research',
31
- plan: 'Approve Plan',
32
- implement: 'Approve Merge',
26
+ /** Map graph node names to review action labels. */
27
+ const NODE_TO_REVIEW = {
28
+ analyze: 'Review Analysis',
29
+ requirements: 'Review Requirements',
30
+ research: 'Review Research',
31
+ plan: 'Review Plan',
32
+ implement: 'Review Merge',
33
+ merge: 'Review Merge',
34
+ };
35
+ /** Status priority for sorting (lower = higher priority). */
36
+ const STATUS_PRIORITY = {
37
+ running: 0,
38
+ pending: 0,
39
+ waiting_approval: 1,
40
+ failed: 2,
41
+ interrupted: 2,
42
+ completed: 3,
33
43
  };
34
44
  /**
35
45
  * Derive the display status from the agent run.
@@ -37,16 +47,14 @@ const NODE_TO_APPROVE = {
37
47
  */
38
48
  function formatStatus(feature, run) {
39
49
  if (!run) {
40
- // No agent run — fall back to static lifecycle
41
50
  return `${colors.muted(symbols.dotEmpty)} ${colors.muted(feature.lifecycle)}`;
42
51
  }
43
52
  const isRunning = run.status === 'running' || run.status === 'pending';
44
53
  const nodeName = run.result?.startsWith('node:') ? run.result.slice(5) : undefined;
45
54
  const phase = nodeName ? (NODE_TO_PHASE[nodeName] ?? nodeName) : feature.lifecycle;
46
55
  if (isRunning) {
47
- // Check PID liveness for running agents
48
56
  if (run.pid && !isProcessAlive(run.pid)) {
49
- return `${colors.error(symbols.error)} ${colors.error('crashed')}`;
57
+ return `${colors.error(symbols.error)} ${colors.error('Crashed')}`;
50
58
  }
51
59
  return `${colors.info(symbols.spinner[0])} ${colors.info(phase)}`;
52
60
  }
@@ -57,8 +65,8 @@ function formatStatus(feature, run) {
57
65
  return `${colors.error(symbols.error)} ${colors.error('Failed')}`;
58
66
  }
59
67
  if (run.status === 'waiting_approval') {
60
- const action = nodeName ? (NODE_TO_APPROVE[nodeName] ?? phase) : phase;
61
- return `${colors.warning(symbols.warning)} ${colors.warning(action)}`;
68
+ const action = nodeName ? (NODE_TO_REVIEW[nodeName] ?? phase) : phase;
69
+ return `${colors.brand(symbols.pointer)} ${colors.brand(action)}`;
62
70
  }
63
71
  if (run.status === 'interrupted') {
64
72
  return `${colors.error(symbols.error)} ${colors.error('Interrupted')}`;
@@ -74,6 +82,12 @@ function isProcessAlive(pid) {
74
82
  return false;
75
83
  }
76
84
  }
85
+ /** Truncate a string to maxLen, appending ellipsis if needed. */
86
+ function truncate(text, maxLen) {
87
+ if (text.length <= maxLen)
88
+ return text;
89
+ return text.slice(0, maxLen - 1) + symbols.ellipsis;
90
+ }
77
91
  /** Format a duration in ms to a compact human-readable string. */
78
92
  function formatDuration(ms) {
79
93
  const seconds = Math.floor(ms / 1000);
@@ -89,23 +103,54 @@ function formatDuration(ms) {
89
103
  const days = Math.floor(hours / 24);
90
104
  return `${days}d`;
91
105
  }
92
- /** Format the time column based on run status. */
93
- function formatTime(feature, run) {
94
- const now = Date.now();
95
- if (run) {
96
- const isRunning = run.status === 'running' || run.status === 'pending';
97
- if (isRunning && run.startedAt) {
98
- const started = new Date(run.startedAt).getTime();
99
- return colors.info(formatDuration(now - started));
100
- }
101
- if (run.status === 'completed' && run.completedAt) {
102
- const completed = new Date(run.completedAt).getTime();
103
- return colors.muted(`${formatDuration(now - completed)} ago`);
104
- }
106
+ /** Compute total elapsed from phase timings (sum of all phases, including live delta). */
107
+ function formatElapsed(run, phaseTimings) {
108
+ if (phaseTimings.length > 0) {
109
+ const now = Date.now();
110
+ const totalMs = phaseTimings.reduce((sum, pt) => {
111
+ if (pt.durationMs != null)
112
+ return sum + Number(pt.durationMs);
113
+ // Phase still running — add live delta
114
+ if (pt.startedAt)
115
+ return sum + (now - new Date(pt.startedAt).getTime());
116
+ return sum;
117
+ }, 0);
118
+ const isRunning = run?.status === 'running' || run?.status === 'pending';
119
+ return isRunning ? colors.info(formatDuration(totalMs)) : colors.muted(formatDuration(totalMs));
105
120
  }
106
- // Fallback to updatedAt
107
- const updated = new Date(feature.updatedAt).getTime();
108
- return colors.muted(`${formatDuration(now - updated)} ago`);
121
+ // Fallback to run timestamps if no phase timings
122
+ if (!run?.startedAt)
123
+ return '';
124
+ const started = new Date(run.startedAt).getTime();
125
+ const isRunning = run.status === 'running' || run.status === 'pending';
126
+ const end = isRunning
127
+ ? Date.now()
128
+ : run.completedAt
129
+ ? new Date(run.completedAt).getTime()
130
+ : Date.now();
131
+ return isRunning
132
+ ? colors.info(formatDuration(end - started))
133
+ : colors.muted(formatDuration(end - started));
134
+ }
135
+ /** Format when the feature finished (only for completed runs). */
136
+ function formatDone(run) {
137
+ if (!run?.completedAt || run.status !== 'completed')
138
+ return '';
139
+ const completed = new Date(run.completedAt).getTime();
140
+ return colors.muted(`${formatDuration(Date.now() - completed)} ago`);
141
+ }
142
+ /** Format approval gates and push flag as compact checkboxes: R P M ↑ */
143
+ function formatGates(feature) {
144
+ const { allowPrd, allowPlan, allowMerge } = feature.approvalGates;
145
+ const gate = (on) => (on ? colors.success('■') : colors.muted('□'));
146
+ const push = feature.push ? colors.accent('■') : colors.muted('□');
147
+ return `${gate(allowPrd)} ${gate(allowPlan)} ${gate(allowMerge)} ${push}`;
148
+ }
149
+ /** Get sort priority for a run status (lower = shown first). */
150
+ function getStatusPriority(run) {
151
+ if (!run)
152
+ return 4;
153
+ return STATUS_PRIORITY[run.status] ?? 4;
109
154
  }
110
155
  export function createLsCommand() {
111
156
  return new Command('ls')
@@ -115,21 +160,28 @@ export function createLsCommand() {
115
160
  try {
116
161
  const useCase = container.resolve(ListFeaturesUseCase);
117
162
  const runRepo = container.resolve('IAgentRunRepository');
163
+ const phaseRepo = container.resolve('IPhaseTimingRepository');
118
164
  const filters = options.repo ? { repositoryPath: options.repo } : undefined;
119
165
  const features = await useCase.execute(filters);
120
- // Load agent runs for all features in parallel
121
- const runs = await Promise.all(features.map((f) => f.agentRunId ? runRepo.findById(f.agentRunId) : Promise.resolve(null)));
122
- const rows = features.map((feature, i) => {
123
- const run = runs[i];
166
+ // Load agent runs and phase timings for all features in parallel
167
+ const [runs, timings] = await Promise.all([
168
+ Promise.all(features.map((f) => f.agentRunId ? runRepo.findById(f.agentRunId) : Promise.resolve(null))),
169
+ Promise.all(features.map((f) => phaseRepo.findByFeatureId(f.id))),
170
+ ]);
171
+ // Pair features with runs/timings and sort by status priority
172
+ const paired = features
173
+ .map((feature, i) => ({ feature, run: runs[i], phases: timings[i] }))
174
+ .sort((a, b) => getStatusPriority(a.run) - getStatusPriority(b.run));
175
+ const rows = paired.map(({ feature, run, phases }) => {
124
176
  const repo = path.basename(feature.repositoryPath);
125
- const agent = run?.agentType ?? colors.muted('—');
126
177
  return [
127
178
  feature.id.slice(0, 8),
128
- feature.name.slice(0, 30),
179
+ truncate(feature.name, 30),
129
180
  formatStatus(feature, run),
130
181
  colors.muted(repo),
131
- agent,
132
- formatTime(feature, run),
182
+ formatGates(feature),
183
+ formatElapsed(run, phases),
184
+ formatDone(run),
133
185
  ];
134
186
  });
135
187
  renderListView({
@@ -137,10 +189,11 @@ export function createLsCommand() {
137
189
  columns: [
138
190
  { label: 'ID', width: 10 },
139
191
  { label: 'Name', width: 32 },
140
- { label: 'Status', width: 20 },
192
+ { label: 'Status', width: 21 },
141
193
  { label: 'Repo', width: 20 },
142
- { label: 'Agent', width: 14 },
143
- { label: 'Time', width: 12 },
194
+ { label: 'R P M ↑', width: 10 },
195
+ { label: 'Elapsed', width: 10 },
196
+ { label: 'Done', width: 12 },
144
197
  ],
145
198
  rows,
146
199
  emptyMessage: 'No features found',
@@ -23,14 +23,14 @@ const NODE_TO_PHASE = {
23
23
  implement: 'Implementing',
24
24
  merge: 'Merging',
25
25
  };
26
- /** Map graph node names to approval action labels. */
27
- const NODE_TO_APPROVE = {
28
- analyze: 'Approve Analysis',
29
- requirements: 'Approve PRD',
30
- research: 'Approve Research',
31
- plan: 'Approve Plan',
32
- implement: 'Approve Implementation',
33
- merge: 'Approve Merge',
26
+ /** Map graph node names to review action labels. */
27
+ const NODE_TO_REVIEW = {
28
+ analyze: 'Review Analysis',
29
+ requirements: 'Review Requirements',
30
+ research: 'Review Research',
31
+ plan: 'Review Plan',
32
+ implement: 'Review Merge',
33
+ merge: 'Review Merge',
34
34
  };
35
35
  /** Map lifecycle event phases to display labels and symbols. */
36
36
  const LIFECYCLE_EVENTS = {
@@ -63,8 +63,8 @@ function formatStatus(feature, run) {
63
63
  if (run.status === 'failed')
64
64
  return `${colors.error(symbols.error)} ${colors.error('Failed')}`;
65
65
  if (run.status === 'waiting_approval') {
66
- const action = nodeName ? (NODE_TO_APPROVE[nodeName] ?? phase) : phase;
67
- return `${colors.warning(symbols.warning)} ${colors.warning(action)}`;
66
+ const action = nodeName ? (NODE_TO_REVIEW[nodeName] ?? phase) : phase;
67
+ return `${colors.brand(symbols.pointer)} ${colors.brand(action)}`;
68
68
  }
69
69
  if (run.status === 'interrupted')
70
70
  return `${colors.error(symbols.error)} ${colors.error('Interrupted')}`;