wiggum-cli 0.16.0 → 0.17.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.
Files changed (101) hide show
  1. package/bin/ralph.js +0 -0
  2. package/dist/agent/memory/ingest.d.ts +14 -0
  3. package/dist/agent/memory/ingest.js +77 -0
  4. package/dist/agent/memory/store.d.ts +15 -0
  5. package/dist/agent/memory/store.js +98 -0
  6. package/dist/agent/memory/types.d.ts +16 -0
  7. package/dist/agent/memory/types.js +14 -0
  8. package/dist/agent/orchestrator.d.ts +7 -0
  9. package/dist/agent/orchestrator.js +266 -0
  10. package/dist/agent/resolve-config.d.ts +26 -0
  11. package/dist/agent/resolve-config.js +43 -0
  12. package/dist/agent/tools/backlog.d.ts +27 -0
  13. package/dist/agent/tools/backlog.js +51 -0
  14. package/dist/agent/tools/dry-run.d.ts +106 -0
  15. package/dist/agent/tools/dry-run.js +119 -0
  16. package/dist/agent/tools/execution.d.ts +51 -0
  17. package/dist/agent/tools/execution.js +256 -0
  18. package/dist/agent/tools/feature-state.d.ts +43 -0
  19. package/dist/agent/tools/feature-state.js +184 -0
  20. package/dist/agent/tools/introspection.d.ts +23 -0
  21. package/dist/agent/tools/introspection.js +40 -0
  22. package/dist/agent/tools/memory.d.ts +44 -0
  23. package/dist/agent/tools/memory.js +99 -0
  24. package/dist/agent/tools/preflight.d.ts +7 -0
  25. package/dist/agent/tools/preflight.js +137 -0
  26. package/dist/agent/tools/reporting.d.ts +58 -0
  27. package/dist/agent/tools/reporting.js +119 -0
  28. package/dist/agent/tools/schemas.d.ts +2 -0
  29. package/dist/agent/tools/schemas.js +3 -0
  30. package/dist/agent/types.d.ts +45 -0
  31. package/dist/agent/types.js +1 -0
  32. package/dist/ai/conversation/conversation-manager.js +8 -0
  33. package/dist/ai/conversation/url-fetcher.js +27 -0
  34. package/dist/ai/providers.js +5 -5
  35. package/dist/commands/agent.d.ts +17 -0
  36. package/dist/commands/agent.js +114 -0
  37. package/dist/commands/monitor.js +50 -183
  38. package/dist/commands/new-auto.d.ts +15 -0
  39. package/dist/commands/new-auto.js +237 -0
  40. package/dist/commands/run.js +20 -10
  41. package/dist/commands/sync.d.ts +15 -0
  42. package/dist/commands/sync.js +68 -0
  43. package/dist/generator/config.d.ts +1 -41
  44. package/dist/generator/config.js +7 -0
  45. package/dist/generator/index.d.ts +2 -2
  46. package/dist/generator/templates.d.ts +2 -0
  47. package/dist/generator/templates.js +9 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +115 -4
  50. package/dist/repl/command-parser.d.ts +10 -0
  51. package/dist/repl/command-parser.js +10 -0
  52. package/dist/templates/prompts/PROMPT.md.tmpl +13 -10
  53. package/dist/templates/prompts/PROMPT_e2e.md.tmpl +13 -7
  54. package/dist/templates/prompts/PROMPT_feature.md.tmpl +16 -3
  55. package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +32 -12
  56. package/dist/templates/prompts/PROMPT_review_manual.md.tmpl +4 -1
  57. package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +39 -14
  58. package/dist/templates/prompts/PROMPT_verify.md.tmpl +5 -2
  59. package/dist/templates/scripts/feature-loop.sh.tmpl +441 -69
  60. package/dist/tui/app.d.ts +19 -2
  61. package/dist/tui/app.js +23 -4
  62. package/dist/tui/components/IssuePicker.d.ts +27 -0
  63. package/dist/tui/components/IssuePicker.js +73 -0
  64. package/dist/tui/components/RunCompletionSummary.js +6 -3
  65. package/dist/tui/components/StatusLine.d.ts +3 -1
  66. package/dist/tui/components/StatusLine.js +2 -2
  67. package/dist/tui/hooks/useAgentOrchestrator.d.ts +40 -0
  68. package/dist/tui/hooks/useAgentOrchestrator.js +491 -0
  69. package/dist/tui/orchestration/interview-orchestrator.d.ts +5 -1
  70. package/dist/tui/orchestration/interview-orchestrator.js +27 -6
  71. package/dist/tui/screens/AgentScreen.d.ts +24 -0
  72. package/dist/tui/screens/AgentScreen.js +209 -0
  73. package/dist/tui/screens/InitScreen.js +4 -0
  74. package/dist/tui/screens/InterviewScreen.d.ts +3 -1
  75. package/dist/tui/screens/InterviewScreen.js +146 -10
  76. package/dist/tui/screens/MainShell.d.ts +1 -1
  77. package/dist/tui/screens/MainShell.js +115 -4
  78. package/dist/tui/screens/RunScreen.js +72 -16
  79. package/dist/tui/utils/build-run-summary.d.ts +1 -1
  80. package/dist/tui/utils/build-run-summary.js +40 -84
  81. package/dist/tui/utils/clear-screen.d.ts +14 -0
  82. package/dist/tui/utils/clear-screen.js +16 -0
  83. package/dist/tui/utils/git-summary.d.ts +1 -0
  84. package/dist/tui/utils/git-summary.js +16 -0
  85. package/dist/tui/utils/loop-status.d.ts +41 -1
  86. package/dist/tui/utils/loop-status.js +243 -35
  87. package/dist/tui/utils/pr-summary.d.ts +3 -2
  88. package/dist/tui/utils/pr-summary.js +41 -6
  89. package/dist/utils/config.d.ts +8 -0
  90. package/dist/utils/config.js +8 -0
  91. package/dist/utils/github.d.ts +32 -0
  92. package/dist/utils/github.js +106 -0
  93. package/package.json +4 -1
  94. package/src/templates/prompts/PROMPT.md.tmpl +13 -10
  95. package/src/templates/prompts/PROMPT_e2e.md.tmpl +13 -7
  96. package/src/templates/prompts/PROMPT_feature.md.tmpl +16 -3
  97. package/src/templates/prompts/PROMPT_review_auto.md.tmpl +32 -12
  98. package/src/templates/prompts/PROMPT_review_manual.md.tmpl +4 -1
  99. package/src/templates/prompts/PROMPT_review_merge.md.tmpl +39 -14
  100. package/src/templates/prompts/PROMPT_verify.md.tmpl +5 -2
  101. package/src/templates/scripts/feature-loop.sh.tmpl +441 -69
package/dist/tui/app.d.ts CHANGED
@@ -16,7 +16,7 @@ import type { SessionState } from '../repl/session-state.js';
16
16
  /**
17
17
  * Available screen types for the App component
18
18
  */
19
- export type AppScreen = 'shell' | 'interview' | 'init' | 'run';
19
+ export type AppScreen = 'shell' | 'interview' | 'init' | 'run' | 'agent';
20
20
  /**
21
21
  * Props for the interview screen
22
22
  */
@@ -31,6 +31,19 @@ export interface InterviewAppProps {
31
31
  model: string;
32
32
  /** Optional scan result with detected tech stack */
33
33
  scanResult?: ScanResult;
34
+ /** References to auto-add during context phase (from CLI --issue/--context flags) */
35
+ initialReferences?: string[];
36
+ }
37
+ /**
38
+ * Props for the agent screen when launched directly from CLI
39
+ */
40
+ export interface AgentAppProps {
41
+ modelOverride?: string;
42
+ maxItems?: number;
43
+ maxSteps?: number;
44
+ labels?: string[];
45
+ reviewMode?: 'manual' | 'auto' | 'merge';
46
+ dryRun?: boolean;
34
47
  }
35
48
  /**
36
49
  * Props for the run/monitor screen when launched directly from CLI
@@ -57,6 +70,8 @@ export interface AppProps {
57
70
  interviewProps?: InterviewAppProps;
58
71
  /** Props for the run/monitor screen (required when screen is 'run') */
59
72
  runProps?: RunAppProps;
73
+ /** Props for the agent screen (required when screen is 'agent') */
74
+ agentProps?: AgentAppProps;
60
75
  /** Called when the screen completes successfully */
61
76
  onComplete?: (result: string) => void;
62
77
  /** Called when the user exits/cancels */
@@ -69,7 +84,7 @@ export interface AppProps {
69
84
  * and receives a shared headerElement prop.
70
85
  */
71
86
  export declare function App({ screen: initialScreen, initialSessionState, version, // Fallback if package.json read fails (keep in sync with index.ts)
72
- interviewProps, runProps, onComplete, onExit, }: AppProps): React.ReactElement | null;
87
+ interviewProps, runProps, agentProps, onComplete, onExit, }: AppProps): React.ReactElement | null;
73
88
  /**
74
89
  * Render options for renderApp
75
90
  */
@@ -84,6 +99,8 @@ export interface RenderAppOptions {
84
99
  interviewProps?: InterviewAppProps;
85
100
  /** Props for run/monitor screen (if starting directly on run screen) */
86
101
  runProps?: RunAppProps;
102
+ /** Props for agent screen (if starting directly on agent screen) */
103
+ agentProps?: AgentAppProps;
87
104
  /** Called when spec generation completes */
88
105
  onComplete?: (result: string) => void;
89
106
  /** Called when user exits */
package/dist/tui/app.js CHANGED
@@ -19,6 +19,7 @@ import { logger } from '../utils/logger.js';
19
19
  import { InterviewScreen } from './screens/InterviewScreen.js';
20
20
  import { InitScreen } from './screens/InitScreen.js';
21
21
  import { RunScreen } from './screens/RunScreen.js';
22
+ import { AgentScreen } from './screens/AgentScreen.js';
22
23
  import { MainShell } from './screens/MainShell.js';
23
24
  import { HeaderContent } from './components/HeaderContent.js';
24
25
  import { useBackgroundRuns } from './hooks/useBackgroundRuns.js';
@@ -29,7 +30,7 @@ import { useBackgroundRuns } from './hooks/useBackgroundRuns.js';
29
30
  * and receives a shared headerElement prop.
30
31
  */
31
32
  export function App({ screen: initialScreen, initialSessionState, version = '0.12.1', // Fallback if package.json read fails (keep in sync with index.ts)
32
- interviewProps, runProps, onComplete, onExit, }) {
33
+ interviewProps, runProps, agentProps, onComplete, onExit, }) {
33
34
  const [currentScreen, setCurrentScreen] = useState(initialScreen);
34
35
  const [screenProps, setScreenProps] = useState(() => {
35
36
  if (initialScreen === 'run' && runProps) {
@@ -184,7 +185,7 @@ interviewProps, runProps, onComplete, onExit, }) {
184
185
  navigate('shell', { message: 'Feature name is required for the run screen.' });
185
186
  }
186
187
  }
187
- else if (currentScreen !== 'shell' && currentScreen !== 'init') {
188
+ else if (currentScreen !== 'shell' && currentScreen !== 'init' && currentScreen !== 'agent') {
188
189
  // Unknown screen — redirect to shell on next tick
189
190
  navigate('shell', { message: `Internal error: unknown screen "${currentScreen}". Returned to shell.` });
190
191
  }
@@ -198,7 +199,8 @@ interviewProps, runProps, onComplete, onExit, }) {
198
199
  if (!featureName || typeof featureName !== 'string' || !sessionState.provider) {
199
200
  return null; // useEffect will redirect to shell
200
201
  }
201
- return (_jsx(InterviewScreen, { header: headerElement, featureName: featureName, projectRoot: sessionState.projectRoot, provider: sessionState.provider, model: sessionState.model, scanResult: sessionState.scanResult, specsPath: sessionState.config?.paths.specs, onComplete: handleInterviewComplete, onCancel: handleInterviewCancel }));
202
+ return (_jsx(InterviewScreen, { header: headerElement, featureName: featureName, projectRoot: sessionState.projectRoot, provider: sessionState.provider, model: sessionState.model, scanResult: sessionState.scanResult, specsPath: sessionState.config?.paths.specs, initialReferences: screenProps?.initialReferences
203
+ ?? interviewProps?.initialReferences, onComplete: handleInterviewComplete, onCancel: handleInterviewCancel }));
202
204
  }
203
205
  case 'init':
204
206
  return (_jsx(InitScreen, { header: headerElement, projectRoot: sessionState.projectRoot, sessionState: sessionState, onComplete: handleInitComplete, onCancel: () => navigate('shell') }));
@@ -211,6 +213,23 @@ interviewProps, runProps, onComplete, onExit, }) {
211
213
  const reviewMode = screenProps?.reviewMode;
212
214
  return (_jsx(RunScreen, { header: headerElement, featureName: featureName, projectRoot: sessionState.projectRoot, sessionState: sessionState, monitorOnly: monitorOnly, reviewMode: reviewMode, onComplete: handleRunComplete, onBackground: handleRunBackground, onCancel: () => navigate('shell') }));
213
215
  }
216
+ case 'agent': {
217
+ // Merge CLI-provided agentProps with navigation screenProps (from /agent command)
218
+ const resolvedAgentOptions = {
219
+ ...agentProps,
220
+ ...(screenProps?.dryRun != null ? { dryRun: screenProps.dryRun } : {}),
221
+ ...(screenProps?.maxItems != null ? { maxItems: screenProps.maxItems } : {}),
222
+ ...(screenProps?.reviewMode != null ? { reviewMode: screenProps.reviewMode } : {}),
223
+ };
224
+ return (_jsx(AgentScreen, { header: headerElement, projectRoot: sessionState.projectRoot, agentOptions: resolvedAgentOptions, onExit: () => {
225
+ if (initialScreen === 'agent') {
226
+ onExit?.();
227
+ }
228
+ else {
229
+ navigate('shell');
230
+ }
231
+ } }));
232
+ }
214
233
  default: {
215
234
  // Return fallback UI instead of calling navigate() during render (which would be setState during render).
216
235
  // The useEffect guard above will redirect to shell on next tick.
@@ -224,5 +243,5 @@ interviewProps, runProps, onComplete, onExit, }) {
224
243
  * Render the App component to the terminal
225
244
  */
226
245
  export function renderApp(options) {
227
- return render(_jsx(App, { screen: options.screen, initialSessionState: options.initialSessionState, version: options.version, interviewProps: options.interviewProps, runProps: options.runProps, onComplete: options.onComplete, onExit: options.onExit }));
246
+ return render(_jsx(App, { screen: options.screen, initialSessionState: options.initialSessionState, version: options.version, interviewProps: options.interviewProps, runProps: options.runProps, agentProps: options.agentProps, onComplete: options.onComplete, onExit: options.onExit }));
228
247
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * IssuePicker - GitHub issue selection dropdown
3
+ *
4
+ * Displays a bordered list of GitHub issues for the user to navigate
5
+ * and select from. Follows the visual style of CommandDropdown.
6
+ * Supports arrow keys and j/k for navigation, Enter to select, Esc to cancel.
7
+ */
8
+ import React from 'react';
9
+ import type { GitHubIssueListItem } from '../../utils/github.js';
10
+ /**
11
+ * Props for the IssuePicker component
12
+ */
13
+ export interface IssuePickerProps {
14
+ /** List of issues to display */
15
+ issues: GitHubIssueListItem[];
16
+ /** Repository slug (e.g. "owner/repo") shown in header */
17
+ repoSlug: string;
18
+ /** Called when an issue is selected */
19
+ onSelect: (issue: GitHubIssueListItem) => void;
20
+ /** Called when the picker is dismissed (Escape) */
21
+ onCancel: () => void;
22
+ /** Whether issues are currently loading */
23
+ isLoading: boolean;
24
+ /** Optional error message to display */
25
+ error?: string;
26
+ }
27
+ export declare function IssuePicker({ issues, repoSlug, onSelect, onCancel, isLoading, error, }: IssuePickerProps): React.ReactElement;
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * IssuePicker - GitHub issue selection dropdown
4
+ *
5
+ * Displays a bordered list of GitHub issues for the user to navigate
6
+ * and select from. Follows the visual style of CommandDropdown.
7
+ * Supports arrow keys and j/k for navigation, Enter to select, Esc to cancel.
8
+ */
9
+ import React, { useState } from 'react';
10
+ import { Box, Text, useInput, useStdout } from 'ink';
11
+ import Spinner from 'ink-spinner';
12
+ import { colors, box } from '../theme.js';
13
+ const MAX_LABELS = 2;
14
+ function truncate(text, maxLen) {
15
+ if (text.length <= maxLen)
16
+ return text;
17
+ return text.slice(0, maxLen - 1) + '\u2026';
18
+ }
19
+ function pad(text, width) {
20
+ if (text.length >= width)
21
+ return text.slice(0, width);
22
+ return text + ' '.repeat(width - text.length);
23
+ }
24
+ // Fixed column widths
25
+ const COL_NUMBER = 6; // " #104 "
26
+ const COL_STATE = 8; // " closed " or " open "
27
+ const COL_LABELS = 14; // " enhancement "
28
+ const COL_CHROME = 2; // left + right border
29
+ export function IssuePicker({ issues, repoSlug, onSelect, onCancel, isLoading, error, }) {
30
+ const [selectedIndex, setSelectedIndex] = useState(0);
31
+ const { stdout } = useStdout();
32
+ const termColumns = stdout?.columns ?? 80;
33
+ React.useEffect(() => {
34
+ setSelectedIndex(0);
35
+ }, [issues]);
36
+ useInput((input, key) => {
37
+ if (key.escape) {
38
+ onCancel();
39
+ return;
40
+ }
41
+ if (key.return && issues.length > 0) {
42
+ onSelect(issues[selectedIndex]);
43
+ return;
44
+ }
45
+ if (key.upArrow || input === 'k') {
46
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
47
+ return;
48
+ }
49
+ if (key.downArrow || input === 'j') {
50
+ setSelectedIndex((prev) => Math.min(issues.length - 1, prev + 1));
51
+ return;
52
+ }
53
+ });
54
+ const countSuffix = !isLoading && !error ? ` (${issues.length})` : '';
55
+ const headerLabel = ` GitHub Issues ${box.horizontal} ${repoSlug}${countSuffix} `;
56
+ const contentWidth = Math.min(Math.max(60, headerLabel.length + 10), termColumns - 6);
57
+ const topBorderFill = contentWidth - headerLabel.length - 1;
58
+ const topBorder = box.topLeft + box.horizontal + headerLabel + box.horizontal.repeat(Math.max(0, topBorderFill)) + box.topRight;
59
+ const bottomBorder = box.bottomLeft + box.horizontal.repeat(contentWidth) + box.bottomRight;
60
+ // Title column gets all remaining space
61
+ const colTitle = Math.max(20, contentWidth - COL_NUMBER - COL_STATE - COL_LABELS - COL_CHROME);
62
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: topBorder }), isLoading && (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: box.vertical }), _jsx(Text, { children: " " }), _jsx(Spinner, { type: "dots" }), _jsx(Text, { color: colors.yellow, children: " Searching..." }), _jsx(Text, { children: ' '.repeat(Math.max(0, contentWidth - 16)) }), _jsx(Text, { dimColor: true, children: box.vertical })] })), !isLoading && error && (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: box.vertical }), _jsx(Text, { color: colors.pink, children: pad(` ${error}`, contentWidth) }), _jsx(Text, { dimColor: true, children: box.vertical })] })), !isLoading && !error && issues.length === 0 && (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: box.vertical }), _jsx(Text, { dimColor: true, children: pad(' No open issues found', contentWidth) }), _jsx(Text, { dimColor: true, children: box.vertical })] })), !isLoading && !error && issues.map((issue, index) => {
63
+ const isSelected = index === selectedIndex;
64
+ const bg = isSelected ? colors.yellow : undefined;
65
+ const fg = isSelected ? colors.brown : undefined;
66
+ const numberCell = pad(` #${issue.number}`, COL_NUMBER);
67
+ const titleCell = pad(` ${truncate(issue.title, colTitle - 1)}`, colTitle);
68
+ const stateCell = pad(` ${issue.state}`, COL_STATE);
69
+ const labelText = issue.labels.slice(0, MAX_LABELS).join(' ');
70
+ const labelCell = pad(` ${truncate(labelText, COL_LABELS - 1)}`, COL_LABELS);
71
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: box.vertical }), _jsx(Text, { backgroundColor: bg, color: isSelected ? colors.brown : colors.yellow, children: numberCell }), _jsx(Text, { backgroundColor: bg, color: fg, children: titleCell }), _jsx(Text, { backgroundColor: bg, color: isSelected ? colors.brown : issue.state === 'open' ? colors.green : colors.gray, children: stateCell }), _jsx(Text, { backgroundColor: bg, color: isSelected ? colors.brown : colors.gray, children: labelCell }), _jsx(Text, { dimColor: true, children: box.vertical })] }, issue.number));
72
+ }), _jsx(Text, { dimColor: true, children: bottomBorder }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: colors.gray, children: ["(", '\u2191\u2193 navigate, Enter select, Esc cancel', ")"] }) })] }));
73
+ }
@@ -116,10 +116,12 @@ export function RunCompletionSummary({ summary, }) {
116
116
  return (_jsxs(SummaryBox, { minWidth: 60, children: [_jsx(Text, { bold: true, children: summary.feature }), _jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsxs(Text, { bold: true, color: exitStatus.color, children: [statusIcon, " ", exitStatus.label] }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { dimColor: true, children: subtitleParts.join(' · ') })] }), _jsxs(SummaryBoxSection, { children: [_jsx(Text, { bold: true, children: "Phases" }), summary.phases && summary.phases.length > 0 ? (summary.phases.map((phaseInfo) => {
117
117
  const phaseIcon = phaseInfo.status === 'success' ? phase.complete :
118
118
  phaseInfo.status === 'failed' ? phase.error :
119
- phase.pending;
119
+ phaseInfo.status === 'started' ? phase.active :
120
+ phase.pending;
120
121
  const phaseColor = phaseInfo.status === 'success' ? colors.green :
121
122
  phaseInfo.status === 'failed' ? colors.pink :
122
- colors.gray;
123
+ phaseInfo.status === 'started' ? colors.yellow :
124
+ colors.gray;
123
125
  const durationText = phaseInfo.durationMs !== undefined
124
126
  ? formatDurationMs(phaseInfo.durationMs)
125
127
  : 'Not available';
@@ -127,7 +129,8 @@ export function RunCompletionSummary({ summary, }) {
127
129
  ? ` (${phaseInfo.iterations} iterations)`
128
130
  : '';
129
131
  const statusText = phaseInfo.status === 'skipped' ? ' skipped' :
130
- phaseInfo.status === 'failed' ? ' failed' : '';
132
+ phaseInfo.status === 'failed' ? ' failed' :
133
+ phaseInfo.status === 'started' ? ' in progress' : '';
131
134
  return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: phaseColor, children: [phaseIcon, " "] }), _jsxs(Text, { children: [phaseInfo.label, " ", durationText, iterationsText, statusText] })] }, phaseInfo.id));
132
135
  })) : (_jsx(Text, { children: "No phase information available" }))] }), _jsxs(SummaryBoxSection, { children: [_jsx(Text, { bold: true, children: "Changes" }), !summary.changes || !summary.changes.available ? (_jsx(Text, { children: "Changes: Not available" })) : summary.changes.totalFilesChanged === 0 || (summary.changes.files && summary.changes.files.length === 0) ? (_jsx(Text, { children: "No changes" })) : summary.changes.totalFilesChanged !== undefined ? (_jsxs(Text, { children: [summary.changes.totalFilesChanged, " file", summary.changes.totalFilesChanged !== 1 ? 's' : '', " changed"] })) : !summary.changes.files ? (_jsx(Text, { children: "Changes: Could not compute diff" })) : null, (() => {
133
136
  const files = summary.changes?.available ? summary.changes.files : undefined;
@@ -15,6 +15,8 @@ export interface StatusLineProps {
15
15
  phase?: string;
16
16
  /** Path or context info (e.g., working directory or feature name) */
17
17
  path?: string;
18
+ /** Extra trailing segment (e.g., "review: auto") */
19
+ extra?: string;
18
20
  }
19
21
  /**
20
22
  * StatusLine component
@@ -32,4 +34,4 @@ export interface StatusLineProps {
32
34
  * // Renders: Initialize Project │ Analysis (4/5) │ /Users/name/project
33
35
  * ```
34
36
  */
35
- export declare function StatusLine({ action, phase, path, }: StatusLineProps): React.ReactElement;
37
+ export declare function StatusLine({ action, phase, path, extra, }: StatusLineProps): React.ReactElement;
@@ -17,11 +17,11 @@ import { theme, colors } from '../theme.js';
17
17
  * // Renders: Initialize Project │ Analysis (4/5) │ /Users/name/project
18
18
  * ```
19
19
  */
20
- export function StatusLine({ action, phase, path, }) {
20
+ export function StatusLine({ action, phase, path, extra, }) {
21
21
  const separator = theme.statusLine.separator;
22
22
  // Truncate path if too long (keep last 40 chars)
23
23
  const displayPath = path && path.length > 40
24
24
  ? '...' + path.slice(-37)
25
25
  : path;
26
- return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: colors.yellow, bold: true, children: action }), phase && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: separator }), _jsx(Text, { children: phase })] })), displayPath && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: separator }), _jsx(Text, { dimColor: true, children: displayPath })] }))] }));
26
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: colors.yellow, bold: true, children: action }), phase && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: separator }), _jsx(Text, { children: phase })] })), displayPath && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: separator }), _jsx(Text, { dimColor: true, children: displayPath })] })), extra && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: separator }), _jsx(Text, { dimColor: true, children: extra })] }))] }));
27
27
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * useAgentOrchestrator — React hook that bridges the agent orchestrator
3
+ * lifecycle to TUI state via callbacks.
4
+ *
5
+ * Creates the orchestrator, runs it via stream(), and interprets tool
6
+ * calls into structured state (active issue, queue, completed, log).
7
+ * Exposes an abort() function for clean shutdown on q/Esc.
8
+ */
9
+ import type { AgentIssueState, AgentLogEntry, ReviewMode } from '../../agent/types.js';
10
+ import { type LoopStatus, type TaskCounts, type ActivityEvent } from '../utils/loop-status.js';
11
+ import { type CommitLogEntry } from '../utils/git-summary.js';
12
+ export type AgentStatus = 'idle' | 'running' | 'complete' | 'error';
13
+ export interface UseAgentOrchestratorOptions {
14
+ projectRoot: string;
15
+ modelOverride?: string;
16
+ maxItems?: number;
17
+ maxSteps?: number;
18
+ labels?: string[];
19
+ reviewMode?: ReviewMode;
20
+ dryRun?: boolean;
21
+ }
22
+ export interface LoopMonitorData {
23
+ loopStatus: LoopStatus;
24
+ tasks: TaskCounts;
25
+ branch: string;
26
+ recentCommits: CommitLogEntry[];
27
+ activityEvents: ActivityEvent[];
28
+ startTime: number;
29
+ }
30
+ export interface UseAgentOrchestratorResult {
31
+ status: AgentStatus;
32
+ activeIssue: AgentIssueState | null;
33
+ queue: AgentIssueState[];
34
+ completed: AgentIssueState[];
35
+ logEntries: AgentLogEntry[];
36
+ loopMonitor: LoopMonitorData | null;
37
+ error: string | null;
38
+ abort: () => void;
39
+ }
40
+ export declare function useAgentOrchestrator(options: UseAgentOrchestratorOptions): UseAgentOrchestratorResult;