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.
- package/package.json +4 -1
- package/src/components/agents/claude-cli/arg-builder.ts +4 -3
- package/src/reconciler/jsx-runtime.ts +13 -1
- package/templates/main.tsx.template +1 -0
- package/src/commands/cli-utils.test.ts +0 -178
- package/src/commands/db/current-view.test.ts +0 -379
- package/src/commands/db/executions-view.test.ts +0 -270
- package/src/commands/db/help.test.ts +0 -145
- package/src/commands/db/index.test.ts +0 -118
- package/src/commands/db/memories-view.test.ts +0 -366
- package/src/commands/db/recovery-view.test.ts +0 -279
- package/src/commands/db/state-view.test.ts +0 -192
- package/src/commands/db/stats-view.test.ts +0 -194
- package/src/commands/db/transitions-view.test.ts +0 -243
- package/src/commands/init.test.ts +0 -263
- package/src/commands/monitor.test.ts +0 -230
- package/src/commands/run.test.ts +0 -240
- package/src/components/Claude.test.tsx +0 -2008
- package/src/components/End.test.tsx +0 -421
- package/src/components/Git/Commit.test.tsx +0 -586
- package/src/components/Git/Notes.test.tsx +0 -578
- package/src/components/Hooks/OnCIFailure.test.tsx +0 -683
- package/src/components/Hooks/PostCommit.test.tsx +0 -792
- package/src/components/JJ/Commit.test.tsx +0 -206
- package/src/components/JJ/Describe.test.tsx +0 -131
- package/src/components/JJ/Rebase.test.tsx +0 -187
- package/src/components/JJ/Snapshot.test.tsx +0 -187
- package/src/components/JJ/Status.test.tsx +0 -374
- package/src/components/MCP/Sqlite.test.tsx +0 -744
- package/src/components/Parallel.test.tsx +0 -743
- package/src/components/Phase.test.tsx +0 -827
- package/src/components/PhaseRegistry.test.tsx +0 -428
- package/src/components/Ralph.test.tsx +0 -765
- package/src/components/Review.test.tsx +0 -606
- package/src/components/Smithers.test.tsx +0 -89
- package/src/components/SmithersProvider.test.ts +0 -422
- package/src/components/Step.test.tsx +0 -783
- package/src/components/Worktree.test.tsx +0 -243
- package/src/components/agents/SmithersCLI.test.ts +0 -329
- package/src/components/agents/claude-cli/arg-builder.test.ts +0 -312
- package/src/components/agents/claude-cli/executor.test.ts +0 -805
- package/src/components/agents/claude-cli/message-parser.test.ts +0 -390
- package/src/components/agents/claude-cli/output-parser.test.ts +0 -217
- package/src/components/agents/claude-cli/stop-conditions.test.ts +0 -517
- package/src/components/components.test.tsx +0 -799
- package/src/core/index.test.ts +0 -123
- package/src/db/agents.test.ts +0 -703
- package/src/db/artifacts.test.ts +0 -436
- package/src/db/build-state.test.ts +0 -101
- package/src/db/execution.test.ts +0 -803
- package/src/db/human.test.ts +0 -71
- package/src/db/index.test.ts +0 -436
- package/src/db/memories.test.ts +0 -1016
- package/src/db/phases.test.ts +0 -932
- package/src/db/query.test.ts +0 -226
- package/src/db/render-frames.test.ts +0 -420
- package/src/db/state.test.ts +0 -667
- package/src/db/steps.test.ts +0 -772
- package/src/db/tasks.test.ts +0 -600
- package/src/db/tools.test.ts +0 -442
- package/src/db/utils.test.ts +0 -191
- package/src/db/vcs-queue.test.ts +0 -312
- package/src/db/vcs.test.ts +0 -569
- package/src/debug/index.test.ts +0 -307
- package/src/hooks/index.test.ts +0 -85
- package/src/hooks/useCaptureRenderFrame.test.tsx +0 -267
- package/src/hooks/useCommitWithRetry.test.tsx +0 -161
- package/src/hooks/useHuman.test.ts +0 -138
- package/src/hooks/useHuman.test.tsx +0 -159
- package/src/hooks/useHumanInteractive.test.ts +0 -446
- package/src/hooks/useHumanInteractive.test.tsx +0 -59
- package/src/hooks/useRalphCount.test.tsx +0 -173
- package/src/jsx-runtime.test.ts +0 -74
- package/src/middleware/middleware.test.ts +0 -318
- package/src/monitor/haiku-summarizer.test.ts +0 -253
- package/src/monitor/log-writer.test.ts +0 -257
- package/src/monitor/output-parser.test.ts +0 -424
- package/src/monitor/stream-formatter.test.ts +0 -471
- package/src/rate-limits/middleware.test.ts +0 -163
- package/src/rate-limits/monitor.test.ts +0 -38
- package/src/rate-limits/providers/anthropic.test.ts +0 -36
- package/src/rate-limits/providers/openai.test.ts +0 -81
- package/src/rate-limits/store.test.ts +0 -139
- package/src/rate-limits/throttle.test.ts +0 -172
- package/src/reactive-sqlite/database.test.ts +0 -787
- package/src/reactive-sqlite/hooks/context.test.tsx +0 -418
- package/src/reactive-sqlite/hooks/useMutation.test.tsx +0 -583
- package/src/reactive-sqlite/hooks/useQuery.test.tsx +0 -609
- package/src/reactive-sqlite/hooks/useQueryOne.test.tsx +0 -419
- package/src/reactive-sqlite/hooks/useQueryValue.test.tsx +0 -539
- package/src/reactive-sqlite/parser.test.ts +0 -1330
- package/src/reactive-sqlite/row-tracking.test.ts +0 -176
- package/src/reconciler/hooks-integration.test.tsx +0 -383
- package/src/reconciler/hooks.test.tsx +0 -613
- package/src/reconciler/host-config.test.ts +0 -568
- package/src/reconciler/jsx-runtime.test.tsx +0 -449
- package/src/reconciler/methods.test.ts +0 -657
- package/src/reconciler/root.test.tsx +0 -54
- package/src/reconciler/serialize-direct.test.ts +0 -819
- package/src/reconciler/serialize.test.ts +0 -679
- package/src/streaming/claude-parser.test.ts +0 -207
- package/src/streaming/v3-compat.test.ts +0 -348
- package/src/tools/ReportTool.test.ts +0 -282
- package/src/tools/createSmithersTool.test.ts +0 -75
- package/src/tools/registry.test.ts +0 -556
- package/src/tools/smithers-mcp-server.test.ts +0 -107
- package/src/tools/tool-to-mcp.test.ts +0 -216
- package/src/transport/smithers-chat-transport.test.ts +0 -719
- package/src/tui/App.test.tsx +0 -356
- package/src/tui/components/layout/Header.test.tsx +0 -192
- package/src/tui/components/layout/StatusBar.test.tsx +0 -211
- package/src/tui/components/layout/TabBar.test.tsx +0 -251
- package/src/tui/components/shared/ScrollableList.test.tsx +0 -212
- package/src/tui/components/shared/XMLViewer.test.tsx +0 -196
- package/src/tui/components/views/ChatInterface.test.tsx +0 -33
- package/src/tui/components/views/DatabaseExplorer.test.tsx +0 -33
- package/src/tui/hooks/useClaudeChat.test.ts +0 -257
- package/src/tui/hooks/useHumanRequests.test.ts +0 -434
- package/src/tui/hooks/usePollEvents.test.ts +0 -400
- package/src/tui/hooks/usePollTableData.test.ts +0 -280
- package/src/tui/hooks/useRenderFrames.test.ts +0 -449
- package/src/tui/hooks/useReportGenerator.test.ts +0 -316
- package/src/tui/hooks/useSmithersConnection.test.ts +0 -173
- package/src/tui/index.test.ts +0 -43
- package/src/tui/services/claude-assistant.test.ts +0 -56
- package/src/tui/services/report-generator.test.ts +0 -73
- package/src/tui/utils/colors.test.ts +0 -49
- package/src/tui/utils/format.test.ts +0 -83
- package/src/utils/capture.test.ts +0 -547
- package/src/utils/extract-text.test.ts +0 -161
- package/src/utils/mcp-config.test.ts +0 -490
- package/src/utils/scope.test.ts +0 -184
- package/src/utils/structured-output/prompt-generator.test.ts +0 -356
- package/src/utils/structured-output/validator.test.ts +0 -308
- package/src/utils/structured-output/zod-converter.test.ts +0 -276
- package/src/utils/structured-output.test.ts +0 -305
- package/src/utils/vcs/git.test.ts +0 -138
- package/src/utils/vcs/git.worktree.test.ts +0 -139
- package/src/utils/vcs/jj.test.ts +0 -359
- package/src/utils/vcs/parsers.test.ts +0 -441
- 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.
|
|
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
|
-
|
|
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,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
|
-
})
|