smithers-orchestrator 0.2.1 → 0.2.3

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 (141) hide show
  1. package/package.json +4 -1
  2. package/src/components/agents/claude-cli/arg-builder.ts +4 -3
  3. package/src/reconciler/jsx-runtime.ts +13 -1
  4. package/templates/main.tsx.template +1 -0
  5. package/src/commands/cli-utils.test.ts +0 -178
  6. package/src/commands/db/current-view.test.ts +0 -379
  7. package/src/commands/db/executions-view.test.ts +0 -270
  8. package/src/commands/db/help.test.ts +0 -145
  9. package/src/commands/db/index.test.ts +0 -118
  10. package/src/commands/db/memories-view.test.ts +0 -366
  11. package/src/commands/db/recovery-view.test.ts +0 -279
  12. package/src/commands/db/state-view.test.ts +0 -192
  13. package/src/commands/db/stats-view.test.ts +0 -194
  14. package/src/commands/db/transitions-view.test.ts +0 -243
  15. package/src/commands/init.test.ts +0 -263
  16. package/src/commands/monitor.test.ts +0 -230
  17. package/src/commands/run.test.ts +0 -240
  18. package/src/components/Claude.test.tsx +0 -2008
  19. package/src/components/End.test.tsx +0 -421
  20. package/src/components/Git/Commit.test.tsx +0 -586
  21. package/src/components/Git/Notes.test.tsx +0 -578
  22. package/src/components/Hooks/OnCIFailure.test.tsx +0 -683
  23. package/src/components/Hooks/PostCommit.test.tsx +0 -792
  24. package/src/components/JJ/Commit.test.tsx +0 -206
  25. package/src/components/JJ/Describe.test.tsx +0 -131
  26. package/src/components/JJ/Rebase.test.tsx +0 -187
  27. package/src/components/JJ/Snapshot.test.tsx +0 -187
  28. package/src/components/JJ/Status.test.tsx +0 -374
  29. package/src/components/MCP/Sqlite.test.tsx +0 -744
  30. package/src/components/Parallel.test.tsx +0 -743
  31. package/src/components/Phase.test.tsx +0 -827
  32. package/src/components/PhaseRegistry.test.tsx +0 -428
  33. package/src/components/Ralph.test.tsx +0 -765
  34. package/src/components/Review.test.tsx +0 -606
  35. package/src/components/Smithers.test.tsx +0 -89
  36. package/src/components/SmithersProvider.test.ts +0 -422
  37. package/src/components/Step.test.tsx +0 -783
  38. package/src/components/Worktree.test.tsx +0 -243
  39. package/src/components/agents/SmithersCLI.test.ts +0 -329
  40. package/src/components/agents/claude-cli/arg-builder.test.ts +0 -312
  41. package/src/components/agents/claude-cli/executor.test.ts +0 -805
  42. package/src/components/agents/claude-cli/message-parser.test.ts +0 -390
  43. package/src/components/agents/claude-cli/output-parser.test.ts +0 -217
  44. package/src/components/agents/claude-cli/stop-conditions.test.ts +0 -517
  45. package/src/components/components.test.tsx +0 -799
  46. package/src/core/index.test.ts +0 -123
  47. package/src/db/agents.test.ts +0 -703
  48. package/src/db/artifacts.test.ts +0 -436
  49. package/src/db/build-state.test.ts +0 -101
  50. package/src/db/execution.test.ts +0 -803
  51. package/src/db/human.test.ts +0 -71
  52. package/src/db/index.test.ts +0 -436
  53. package/src/db/memories.test.ts +0 -1016
  54. package/src/db/phases.test.ts +0 -932
  55. package/src/db/query.test.ts +0 -226
  56. package/src/db/render-frames.test.ts +0 -420
  57. package/src/db/state.test.ts +0 -667
  58. package/src/db/steps.test.ts +0 -772
  59. package/src/db/tasks.test.ts +0 -600
  60. package/src/db/tools.test.ts +0 -442
  61. package/src/db/utils.test.ts +0 -191
  62. package/src/db/vcs-queue.test.ts +0 -312
  63. package/src/db/vcs.test.ts +0 -569
  64. package/src/debug/index.test.ts +0 -307
  65. package/src/hooks/index.test.ts +0 -85
  66. package/src/hooks/useCaptureRenderFrame.test.tsx +0 -267
  67. package/src/hooks/useCommitWithRetry.test.tsx +0 -161
  68. package/src/hooks/useHuman.test.ts +0 -138
  69. package/src/hooks/useHuman.test.tsx +0 -159
  70. package/src/hooks/useHumanInteractive.test.ts +0 -446
  71. package/src/hooks/useHumanInteractive.test.tsx +0 -59
  72. package/src/hooks/useRalphCount.test.tsx +0 -173
  73. package/src/jsx-runtime.test.ts +0 -74
  74. package/src/middleware/middleware.test.ts +0 -318
  75. package/src/monitor/haiku-summarizer.test.ts +0 -253
  76. package/src/monitor/log-writer.test.ts +0 -257
  77. package/src/monitor/output-parser.test.ts +0 -424
  78. package/src/monitor/stream-formatter.test.ts +0 -471
  79. package/src/rate-limits/middleware.test.ts +0 -163
  80. package/src/rate-limits/monitor.test.ts +0 -38
  81. package/src/rate-limits/providers/anthropic.test.ts +0 -36
  82. package/src/rate-limits/providers/openai.test.ts +0 -81
  83. package/src/rate-limits/store.test.ts +0 -139
  84. package/src/rate-limits/throttle.test.ts +0 -172
  85. package/src/reactive-sqlite/database.test.ts +0 -787
  86. package/src/reactive-sqlite/hooks/context.test.tsx +0 -418
  87. package/src/reactive-sqlite/hooks/useMutation.test.tsx +0 -583
  88. package/src/reactive-sqlite/hooks/useQuery.test.tsx +0 -609
  89. package/src/reactive-sqlite/hooks/useQueryOne.test.tsx +0 -419
  90. package/src/reactive-sqlite/hooks/useQueryValue.test.tsx +0 -539
  91. package/src/reactive-sqlite/parser.test.ts +0 -1330
  92. package/src/reactive-sqlite/row-tracking.test.ts +0 -176
  93. package/src/reconciler/hooks-integration.test.tsx +0 -383
  94. package/src/reconciler/hooks.test.tsx +0 -613
  95. package/src/reconciler/host-config.test.ts +0 -568
  96. package/src/reconciler/jsx-runtime.test.tsx +0 -449
  97. package/src/reconciler/methods.test.ts +0 -657
  98. package/src/reconciler/root.test.tsx +0 -54
  99. package/src/reconciler/serialize-direct.test.ts +0 -819
  100. package/src/reconciler/serialize.test.ts +0 -679
  101. package/src/streaming/claude-parser.test.ts +0 -207
  102. package/src/streaming/v3-compat.test.ts +0 -348
  103. package/src/tools/ReportTool.test.ts +0 -282
  104. package/src/tools/createSmithersTool.test.ts +0 -75
  105. package/src/tools/registry.test.ts +0 -556
  106. package/src/tools/smithers-mcp-server.test.ts +0 -107
  107. package/src/tools/tool-to-mcp.test.ts +0 -216
  108. package/src/transport/smithers-chat-transport.test.ts +0 -719
  109. package/src/tui/App.test.tsx +0 -356
  110. package/src/tui/components/layout/Header.test.tsx +0 -192
  111. package/src/tui/components/layout/StatusBar.test.tsx +0 -211
  112. package/src/tui/components/layout/TabBar.test.tsx +0 -251
  113. package/src/tui/components/shared/ScrollableList.test.tsx +0 -212
  114. package/src/tui/components/shared/XMLViewer.test.tsx +0 -196
  115. package/src/tui/components/views/ChatInterface.test.tsx +0 -33
  116. package/src/tui/components/views/DatabaseExplorer.test.tsx +0 -33
  117. package/src/tui/hooks/useClaudeChat.test.ts +0 -257
  118. package/src/tui/hooks/useHumanRequests.test.ts +0 -434
  119. package/src/tui/hooks/usePollEvents.test.ts +0 -400
  120. package/src/tui/hooks/usePollTableData.test.ts +0 -280
  121. package/src/tui/hooks/useRenderFrames.test.ts +0 -449
  122. package/src/tui/hooks/useReportGenerator.test.ts +0 -316
  123. package/src/tui/hooks/useSmithersConnection.test.ts +0 -173
  124. package/src/tui/index.test.ts +0 -43
  125. package/src/tui/services/claude-assistant.test.ts +0 -56
  126. package/src/tui/services/report-generator.test.ts +0 -73
  127. package/src/tui/utils/colors.test.ts +0 -49
  128. package/src/tui/utils/format.test.ts +0 -83
  129. package/src/utils/capture.test.ts +0 -547
  130. package/src/utils/extract-text.test.ts +0 -161
  131. package/src/utils/mcp-config.test.ts +0 -490
  132. package/src/utils/scope.test.ts +0 -184
  133. package/src/utils/structured-output/prompt-generator.test.ts +0 -356
  134. package/src/utils/structured-output/validator.test.ts +0 -308
  135. package/src/utils/structured-output/zod-converter.test.ts +0 -276
  136. package/src/utils/structured-output.test.ts +0 -305
  137. package/src/utils/vcs/git.test.ts +0 -138
  138. package/src/utils/vcs/git.worktree.test.ts +0 -139
  139. package/src/utils/vcs/jj.test.ts +0 -359
  140. package/src/utils/vcs/parsers.test.ts +0 -441
  141. package/src/utils/vcs.test.ts +0 -213
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smithers-orchestrator",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Build AI agents with React - Declarative JSX for Claude orchestration",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -38,6 +38,9 @@
38
38
  },
39
39
  "files": [
40
40
  "src",
41
+ "!src/**/*.test.ts",
42
+ "!src/**/*.test.tsx",
43
+ "!src/.smithers",
41
44
  "bin",
42
45
  "skills",
43
46
  "templates",
@@ -5,11 +5,12 @@ import type { CLIExecutionOptions, ClaudePermissionMode, ClaudeOutputFormat } fr
5
5
 
6
6
  /**
7
7
  * Model name mapping from shorthand to full model ID
8
+ * Uses latest dated versions for API compatibility
8
9
  */
9
10
  export const modelMap: Record<string, string> = {
10
- opus: 'claude-opus-4',
11
- sonnet: 'claude-sonnet-4',
12
- haiku: 'claude-haiku-3',
11
+ opus: 'claude-opus-4-20250514',
12
+ sonnet: 'claude-sonnet-4-20250514',
13
+ haiku: 'claude-haiku-3-20250514',
13
14
  }
14
15
 
15
16
  /**
@@ -9,7 +9,15 @@ import {
9
9
  jsx as reactJsx,
10
10
  jsxs as reactJsxs,
11
11
  } from 'react/jsx-runtime'
12
- import { jsxDEV as reactJsxDEV } from 'react/jsx-dev-runtime'
12
+
13
+ // In production mode, jsxDEV is undefined - fallback to jsx
14
+ let reactJsxDEV: typeof import('react/jsx-dev-runtime').jsxDEV | undefined
15
+ try {
16
+ const devRuntime = await import('react/jsx-dev-runtime')
17
+ reactJsxDEV = devRuntime.jsxDEV
18
+ } catch {
19
+ // Fallback handled below
20
+ }
13
21
 
14
22
  type Props = Record<string, unknown> | null
15
23
 
@@ -44,6 +52,10 @@ export function jsxDEV(
44
52
  source: { fileName: string; lineNumber: number; columnNumber: number } | undefined,
45
53
  self: unknown
46
54
  ) {
55
+ // Fallback to jsx in production mode where jsxDEV is undefined
56
+ if (!reactJsxDEV) {
57
+ return reactJsx(type as React.ElementType, withSmithersKey(props, key), key)
58
+ }
47
59
  return reactJsxDEV(type as React.ElementType, withSmithersKey(props, key), key, isStaticChildren, source, self)
48
60
  }
49
61
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+ /** @jsxImportSource smithers-orchestrator */
2
3
  import { createSmithersRoot } from 'smithers-orchestrator'
3
4
  import { createSmithersDB } from 'smithers-orchestrator/db'
4
5
  import {
@@ -1,178 +0,0 @@
1
- /**
2
- * Tests for cli-utils
3
- *
4
- * Covers: Path resolution, file permissions, database paths, package root detection
5
- */
6
-
7
- import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
8
- import * as fs from 'fs'
9
- import * as path from 'path'
10
- import {
11
- DEFAULT_MAIN_FILE,
12
- DEFAULT_DB_DIR,
13
- DB_FILE_NAME,
14
- resolveEntrypoint,
15
- ensureExecutable,
16
- findPreloadPath,
17
- findPackageRoot,
18
- resolveDbPaths,
19
- } from './cli-utils'
20
- import { cleanupTempDir, createTempDir } from './test-utils'
21
-
22
- describe('constants', () => {
23
- test('DEFAULT_MAIN_FILE is .smithers/main.tsx', () => {
24
- expect(DEFAULT_MAIN_FILE).toBe('.smithers/main.tsx')
25
- })
26
-
27
- test('DEFAULT_DB_DIR is .smithers/data', () => {
28
- expect(DEFAULT_DB_DIR).toBe('.smithers/data')
29
- })
30
-
31
- test('DB_FILE_NAME is smithers.db', () => {
32
- expect(DB_FILE_NAME).toBe('smithers.db')
33
- })
34
- })
35
-
36
- describe('resolveEntrypoint', () => {
37
- test('returns fileArg when provided', () => {
38
- const result = resolveEntrypoint('/path/to/file.tsx')
39
- expect(result).toBe('/path/to/file.tsx')
40
- })
41
-
42
- test('returns optionFile when fileArg not provided', () => {
43
- const result = resolveEntrypoint(undefined, '/option/file.tsx')
44
- expect(result).toBe('/option/file.tsx')
45
- })
46
-
47
- test('returns defaultFile when neither provided', () => {
48
- const result = resolveEntrypoint(undefined, undefined, '/default/file.tsx')
49
- expect(result).toBe('/default/file.tsx')
50
- })
51
-
52
- test('uses DEFAULT_MAIN_FILE as ultimate fallback', () => {
53
- const result = resolveEntrypoint()
54
- expect(result).toBe(path.resolve(DEFAULT_MAIN_FILE))
55
- })
56
-
57
- test('resolves relative paths to absolute', () => {
58
- const result = resolveEntrypoint('relative/path.tsx')
59
- expect(path.isAbsolute(result)).toBe(true)
60
- })
61
-
62
- test('fileArg takes precedence over optionFile', () => {
63
- const result = resolveEntrypoint('/fileArg.tsx', '/optionFile.tsx')
64
- expect(result).toBe('/fileArg.tsx')
65
- })
66
-
67
- test('optionFile takes precedence over defaultFile', () => {
68
- const result = resolveEntrypoint(undefined, '/optionFile.tsx', '/defaultFile.tsx')
69
- expect(result).toBe('/optionFile.tsx')
70
- })
71
- })
72
-
73
- describe('ensureExecutable', () => {
74
- let tempDir: string
75
-
76
- beforeEach(() => {
77
- tempDir = createTempDir(import.meta.dir, '.test-executable')
78
- })
79
-
80
- afterEach(() => {
81
- cleanupTempDir(tempDir)
82
- })
83
-
84
- test('makes non-executable file executable', () => {
85
- const testFile = path.join(tempDir, 'test.tsx')
86
- fs.writeFileSync(testFile, '#!/usr/bin/env bun\n')
87
- fs.chmodSync(testFile, '644')
88
-
89
- ensureExecutable(testFile)
90
-
91
- const stats = fs.statSync(testFile)
92
- expect((stats.mode & 0o100) !== 0).toBe(true)
93
- })
94
-
95
- test('leaves already executable file unchanged', () => {
96
- const testFile = path.join(tempDir, 'exec.tsx')
97
- fs.writeFileSync(testFile, '#!/usr/bin/env bun\n')
98
- fs.chmodSync(testFile, '755')
99
-
100
- ensureExecutable(testFile)
101
-
102
- const stats = fs.statSync(testFile)
103
- expect((stats.mode & 0o755) !== 0).toBe(true)
104
- })
105
-
106
- test('sets mode 755', () => {
107
- const testFile = path.join(tempDir, 'mode.tsx')
108
- fs.writeFileSync(testFile, '#!/usr/bin/env bun\n')
109
- fs.chmodSync(testFile, '600')
110
-
111
- ensureExecutable(testFile)
112
-
113
- const stats = fs.statSync(testFile)
114
- expect((stats.mode & 0o100) !== 0).toBe(true)
115
- })
116
- })
117
-
118
- describe('findPreloadPath', () => {
119
- test('throws error when preload.ts not found', () => {
120
- const fakeUrl = 'file:///nonexistent/path/module.ts'
121
- expect(() => findPreloadPath(fakeUrl)).toThrow('preload.ts')
122
- expect(() => findPreloadPath(fakeUrl)).toThrow('smithers-orchestrator')
123
- })
124
-
125
- test('error message is descriptive', () => {
126
- try {
127
- findPreloadPath('file:///fake/path/module.ts')
128
- } catch (e) {
129
- expect(e instanceof Error).toBe(true)
130
- expect((e as Error).message).toContain('incorrectly installed')
131
- }
132
- })
133
- })
134
-
135
- describe('findPackageRoot', () => {
136
- test('finds package.json from this module location', () => {
137
- const result = findPackageRoot(import.meta.url)
138
- const packageJsonPath = path.join(result, 'package.json')
139
- expect(fs.existsSync(packageJsonPath)).toBe(true)
140
- })
141
-
142
- test('returns startDir when package.json not found', () => {
143
- const fakeUrl = 'file:///nonexistent/deep/path/module.ts'
144
- const result = findPackageRoot(fakeUrl)
145
- expect(result).toBe('/nonexistent/deep/path')
146
- })
147
- })
148
-
149
- describe('resolveDbPaths', () => {
150
- test('uses default path when input not provided', () => {
151
- const result = resolveDbPaths()
152
- expect(result.requestedPath).toBe(DEFAULT_DB_DIR)
153
- expect(result.dbFile).toBe(path.join(DEFAULT_DB_DIR, DB_FILE_NAME))
154
- })
155
-
156
- test('uses provided input path', () => {
157
- const result = resolveDbPaths('/custom/path')
158
- expect(result.requestedPath).toBe('/custom/path')
159
- expect(result.dbFile).toBe('/custom/path/smithers.db')
160
- })
161
-
162
- test('handles input path ending with .db', () => {
163
- const result = resolveDbPaths('/custom/mydb.db')
164
- expect(result.requestedPath).toBe('/custom/mydb.db')
165
- expect(result.dbFile).toBe('/custom/mydb.db')
166
- })
167
-
168
- test('appends DB_FILE_NAME to directory paths', () => {
169
- const result = resolveDbPaths('/some/directory')
170
- expect(result.dbFile).toBe('/some/directory/smithers.db')
171
- })
172
-
173
- test('uses custom default path when provided', () => {
174
- const result = resolveDbPaths(undefined, '/my/default')
175
- expect(result.requestedPath).toBe('/my/default')
176
- expect(result.dbFile).toBe('/my/default/smithers.db')
177
- })
178
- })
@@ -1,379 +0,0 @@
1
- /**
2
- * Tests for current-view
3
- *
4
- * Covers: Current execution details, phase, agent, tool calls, state
5
- */
6
-
7
- import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
8
- import { showCurrent } from './current-view'
9
-
10
- describe('showCurrent', () => {
11
- let consoleOutput: string[]
12
- let originalConsoleLog: typeof console.log
13
-
14
- beforeEach(() => {
15
- consoleOutput = []
16
- originalConsoleLog = console.log
17
- console.log = (...args: unknown[]) => {
18
- consoleOutput.push(args.map(String).join(' '))
19
- }
20
- })
21
-
22
- afterEach(() => {
23
- console.log = originalConsoleLog
24
- })
25
-
26
- interface Execution {
27
- id: string
28
- name?: string
29
- status: string
30
- file_path: string
31
- }
32
-
33
- interface Phase {
34
- name: string
35
- iteration: number
36
- status: string
37
- }
38
-
39
- interface Agent {
40
- id: string
41
- model: string
42
- status: string
43
- prompt: string
44
- }
45
-
46
- interface ToolCall {
47
- tool_name: string
48
- status: string
49
- }
50
-
51
- interface MockDbOptions {
52
- execution?: Execution | null
53
- phase?: Phase | null
54
- agent?: Agent | null
55
- tools?: ToolCall[]
56
- state?: Record<string, unknown>
57
- }
58
-
59
- function createMockDb(options: MockDbOptions = {}) {
60
- return {
61
- execution: {
62
- current: async () => options.execution ?? null
63
- },
64
- phases: {
65
- current: async () => options.phase ?? null
66
- },
67
- agents: {
68
- current: async () => options.agent ?? null
69
- },
70
- tools: {
71
- list: async () => options.tools ?? []
72
- },
73
- state: {
74
- getAll: async () => options.state ?? {}
75
- }
76
- }
77
- }
78
-
79
- describe('no active execution', () => {
80
- test('prints "(no active execution)" when null', async () => {
81
- const db = createMockDb({ execution: null })
82
- await showCurrent(db)
83
-
84
- expect(consoleOutput.some(line => line.includes('(no active execution)'))).toBe(true)
85
- })
86
-
87
- test('returns early when no execution', async () => {
88
- const db = createMockDb({ execution: null })
89
- await showCurrent(db)
90
-
91
- // Should not try to show phase/agent details
92
- expect(consoleOutput.some(line => line.includes('Current Phase'))).toBe(false)
93
- })
94
- })
95
-
96
- describe('execution display', () => {
97
- test('shows execution name or "Unnamed"', async () => {
98
- const db = createMockDb({
99
- execution: { id: '1', name: 'Test Execution', status: 'running', file_path: '/test.tsx' }
100
- })
101
- await showCurrent(db)
102
-
103
- expect(consoleOutput.some(line => line.includes('Name: Test Execution'))).toBe(true)
104
- })
105
-
106
- test('shows "Unnamed" when name is missing', async () => {
107
- const db = createMockDb({
108
- execution: { id: '1', status: 'running', file_path: '/test.tsx' }
109
- })
110
- await showCurrent(db)
111
-
112
- expect(consoleOutput.some(line => line.includes('Unnamed'))).toBe(true)
113
- })
114
-
115
- test('shows execution ID', async () => {
116
- const db = createMockDb({
117
- execution: { id: 'exec-abc123', status: 'running', file_path: '/test.tsx' }
118
- })
119
- await showCurrent(db)
120
-
121
- expect(consoleOutput.some(line => line.includes('ID: exec-abc123'))).toBe(true)
122
- })
123
-
124
- test('shows uppercase status', async () => {
125
- const db = createMockDb({
126
- execution: { id: '1', status: 'running', file_path: '/test.tsx' }
127
- })
128
- await showCurrent(db)
129
-
130
- expect(consoleOutput.some(line => line.includes('Status: RUNNING'))).toBe(true)
131
- })
132
-
133
- test('shows file path', async () => {
134
- const db = createMockDb({
135
- execution: { id: '1', status: 'running', file_path: '/path/to/main.tsx' }
136
- })
137
- await showCurrent(db)
138
-
139
- expect(consoleOutput.some(line => line.includes('File: /path/to/main.tsx'))).toBe(true)
140
- })
141
- })
142
-
143
- describe('phase display', () => {
144
- test('shows current phase name', async () => {
145
- const db = createMockDb({
146
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
147
- phase: { name: 'Implementation', iteration: 1, status: 'running' }
148
- })
149
- await showCurrent(db)
150
-
151
- expect(consoleOutput.some(line => line.includes('Current Phase: Implementation'))).toBe(true)
152
- })
153
-
154
- test('shows phase iteration number', async () => {
155
- const db = createMockDb({
156
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
157
- phase: { name: 'Review', iteration: 3, status: 'running' }
158
- })
159
- await showCurrent(db)
160
-
161
- expect(consoleOutput.some(line => line.includes('iteration 3'))).toBe(true)
162
- })
163
-
164
- test('shows uppercase phase status', async () => {
165
- const db = createMockDb({
166
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
167
- phase: { name: 'Test', iteration: 1, status: 'pending' }
168
- })
169
- await showCurrent(db)
170
-
171
- expect(consoleOutput.some(line => line.includes('Phase Status: PENDING'))).toBe(true)
172
- })
173
-
174
- test('handles null phase gracefully', async () => {
175
- const db = createMockDb({
176
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
177
- phase: null
178
- })
179
- await showCurrent(db)
180
-
181
- expect(consoleOutput.some(line => line.includes('Current Phase'))).toBe(false)
182
- })
183
- })
184
-
185
- describe('agent display', () => {
186
- test('shows current agent model', async () => {
187
- const db = createMockDb({
188
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
189
- agent: { id: 'a1', model: 'claude-sonnet', status: 'running', prompt: 'Test prompt' }
190
- })
191
- await showCurrent(db)
192
-
193
- expect(consoleOutput.some(line => line.includes('Current Agent: claude-sonnet'))).toBe(true)
194
- })
195
-
196
- test('shows uppercase agent status', async () => {
197
- const db = createMockDb({
198
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
199
- agent: { id: 'a1', model: 'claude-haiku', status: 'waiting', prompt: 'Test' }
200
- })
201
- await showCurrent(db)
202
-
203
- expect(consoleOutput.some(line => line.includes('Agent Status: WAITING'))).toBe(true)
204
- })
205
-
206
- test('shows truncated prompt (100 chars with ...)', async () => {
207
- const longPrompt = 'A'.repeat(150)
208
- const db = createMockDb({
209
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
210
- agent: { id: 'a1', model: 'claude', status: 'running', prompt: longPrompt }
211
- })
212
- await showCurrent(db)
213
-
214
- expect(consoleOutput.some(line => line.includes('...'))).toBe(true)
215
- expect(consoleOutput.some(line => line.includes('A'.repeat(100)))).toBe(true)
216
- })
217
-
218
- test('handles null agent gracefully', async () => {
219
- const db = createMockDb({
220
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
221
- agent: null
222
- })
223
- await showCurrent(db)
224
-
225
- expect(consoleOutput.some(line => line.includes('Current Agent'))).toBe(false)
226
- })
227
- })
228
-
229
- describe('tool calls display', () => {
230
- test('shows recent tool calls count', async () => {
231
- const db = createMockDb({
232
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
233
- agent: { id: 'a1', model: 'claude', status: 'running', prompt: 'Test' },
234
- tools: [
235
- { tool_name: 'Read', status: 'complete' },
236
- { tool_name: 'Write', status: 'complete' },
237
- { tool_name: 'Bash', status: 'running' }
238
- ]
239
- })
240
- await showCurrent(db)
241
-
242
- expect(consoleOutput.some(line => line.includes('Recent Tool Calls (3)'))).toBe(true)
243
- })
244
-
245
- test('shows last 5 tool calls', async () => {
246
- const tools = Array.from({ length: 10 }, (_, i) => ({
247
- tool_name: `Tool${i}`,
248
- status: 'complete'
249
- }))
250
- const db = createMockDb({
251
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
252
- agent: { id: 'a1', model: 'claude', status: 'running', prompt: 'Test' },
253
- tools
254
- })
255
- await showCurrent(db)
256
-
257
- // Should show the last 5 tools (Tool5-Tool9)
258
- expect(consoleOutput.some(line => line.includes('Tool9'))).toBe(true)
259
- expect(consoleOutput.some(line => line.includes('Tool5'))).toBe(true)
260
- })
261
-
262
- test('shows tool name and status', async () => {
263
- const db = createMockDb({
264
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
265
- agent: { id: 'a1', model: 'claude', status: 'running', prompt: 'Test' },
266
- tools: [{ tool_name: 'Grep', status: 'complete' }]
267
- })
268
- await showCurrent(db)
269
-
270
- expect(consoleOutput.some(line => line.includes('Grep') && line.includes('complete'))).toBe(true)
271
- })
272
-
273
- test('handles empty tool calls list', async () => {
274
- const db = createMockDb({
275
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
276
- agent: { id: 'a1', model: 'claude', status: 'running', prompt: 'Test' },
277
- tools: []
278
- })
279
- await showCurrent(db)
280
-
281
- expect(consoleOutput.some(line => line.includes('Recent Tool Calls'))).toBe(false)
282
- })
283
-
284
- test('only shows tools when agent exists', async () => {
285
- const db = createMockDb({
286
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
287
- agent: null,
288
- tools: [{ tool_name: 'Read', status: 'complete' }]
289
- })
290
- await showCurrent(db)
291
-
292
- expect(consoleOutput.some(line => line.includes('Recent Tool Calls'))).toBe(false)
293
- })
294
- })
295
-
296
- describe('state display', () => {
297
- test('shows all state key-value pairs', async () => {
298
- const db = createMockDb({
299
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
300
- state: { phase: 'implementation', step: 1 }
301
- })
302
- await showCurrent(db)
303
-
304
- expect(consoleOutput.some(line => line.includes('phase:'))).toBe(true)
305
- expect(consoleOutput.some(line => line.includes('step:'))).toBe(true)
306
- })
307
-
308
- test('serializes values as JSON', async () => {
309
- const db = createMockDb({
310
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
311
- state: { config: { nested: true } }
312
- })
313
- await showCurrent(db)
314
-
315
- const output = consoleOutput.join('\n')
316
- expect(output).toContain('config')
317
- })
318
-
319
- test('handles empty state', async () => {
320
- const db = createMockDb({
321
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
322
- state: {}
323
- })
324
- await showCurrent(db)
325
-
326
- // State section should still be printed
327
- expect(consoleOutput.some(line => line.includes('State:'))).toBe(true)
328
- })
329
- })
330
-
331
- describe('header formatting', () => {
332
- test('prints correct header separator', async () => {
333
- const db = createMockDb({})
334
- await showCurrent(db)
335
-
336
- expect(consoleOutput.some(line => line.includes('═══════════════════════════════════════════════════════════'))).toBe(true)
337
- })
338
-
339
- test('prints "CURRENT EXECUTION" title', async () => {
340
- const db = createMockDb({})
341
- await showCurrent(db)
342
-
343
- expect(consoleOutput.some(line => line.includes('CURRENT EXECUTION'))).toBe(true)
344
- })
345
- })
346
-
347
- describe('edge cases', () => {
348
- test('handles null execution name', async () => {
349
- const db = createMockDb({
350
- execution: { id: '1', name: undefined, status: 'running', file_path: '/test.tsx' }
351
- })
352
- await showCurrent(db)
353
-
354
- expect(consoleOutput.some(line => line.includes('Unnamed'))).toBe(true)
355
- })
356
-
357
- test('handles exactly 100 char prompt', async () => {
358
- const exactPrompt = 'B'.repeat(100)
359
- const db = createMockDb({
360
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
361
- agent: { id: 'a1', model: 'claude', status: 'running', prompt: exactPrompt }
362
- })
363
- await showCurrent(db)
364
-
365
- // Should show exactly 100 chars plus ...
366
- expect(consoleOutput.some(line => line.includes('B'.repeat(100)))).toBe(true)
367
- })
368
-
369
- test('handles empty prompt string', async () => {
370
- const db = createMockDb({
371
- execution: { id: '1', status: 'running', file_path: '/test.tsx' },
372
- agent: { id: 'a1', model: 'claude', status: 'running', prompt: '' }
373
- })
374
- await showCurrent(db)
375
-
376
- expect(consoleOutput.some(line => line.includes('Prompt:'))).toBe(true)
377
- })
378
- })
379
- })