smithers-orchestrator 0.1.16 → 0.1.17

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smithers-orchestrator",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Build AI agents with Solid.js - Declarative JSX for Claude orchestration",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -51,7 +51,6 @@
51
51
  "@anthropic-ai/sdk": "^0.71.2",
52
52
  "@babel/core": "^7.28.6",
53
53
  "@babel/preset-typescript": "^7.28.5",
54
- "@dschz/bun-plugin-solid": "^1.0.4",
55
54
  "@electric-sql/pglite": "^0.3.15",
56
55
  "babel-preset-solid": "^1.9.10",
57
56
  "commander": "^12.0.0",
package/preload.ts CHANGED
@@ -1,10 +1,69 @@
1
- import { SolidPlugin } from "@dschz/bun-plugin-solid";
2
-
3
- await Bun.plugin(
4
- SolidPlugin({
5
- generate: "universal",
6
- moduleName: "smithers-orchestrator/solid",
7
- hydratable: false,
8
- debug: false,
9
- }),
10
- );
1
+ /**
2
+ * Preload script for Smithers orchestration files.
3
+ *
4
+ * This sets up the Solid JSX transform using babel directly,
5
+ * pointing to smithers-orchestrator/solid as the render module.
6
+ */
7
+ import tsPreset from "@babel/preset-typescript";
8
+ import solidPreset from "babel-preset-solid";
9
+
10
+ const logPrefix = "\x1b[36m[smithers-solid-jsx]\x1b[0m";
11
+
12
+ await Bun.plugin({
13
+ name: "smithers-solid-jsx",
14
+ setup: (build) => {
15
+ let babel: typeof import("@babel/core") | null = null;
16
+ let babelTransformPresets: any[] | null = null;
17
+
18
+ // Only match tsx/jsx files outside of node_modules
19
+ // Use negative lookahead to exclude node_modules paths
20
+ build.onLoad({ filter: /^(?!.*node_modules).*\.[tj]sx$/ }, async ({ path }) => {
21
+
22
+ if (!babel) {
23
+ babel = await import("@babel/core");
24
+ }
25
+
26
+ if (!babelTransformPresets) {
27
+ babelTransformPresets = [
28
+ [tsPreset, {}],
29
+ [solidPreset, {
30
+ // Use universal mode (non-DOM) for the custom renderer
31
+ generate: "universal",
32
+ // Point to smithers-orchestrator's solid module for JSX runtime
33
+ moduleName: "smithers-orchestrator/solid",
34
+ // No hydration needed for non-DOM rendering
35
+ hydratable: false,
36
+ }],
37
+ ];
38
+ }
39
+
40
+ console.log(`${logPrefix} Transforming: ${path}`);
41
+ const start = performance.now();
42
+
43
+ try {
44
+ const result = await babel.transformFileAsync(path, {
45
+ presets: babelTransformPresets,
46
+ filename: path,
47
+ sourceMaps: "inline",
48
+ });
49
+
50
+ const end = performance.now();
51
+ console.log(`${logPrefix} Transformed: ${path} in ${Math.round(end - start)}ms`);
52
+
53
+ if (!result || !result.code) {
54
+ console.warn(`${logPrefix} No code for: ${path}`);
55
+ // Return undefined to let bun handle the file normally
56
+ return undefined;
57
+ }
58
+
59
+ return {
60
+ loader: "js",
61
+ contents: result.code,
62
+ };
63
+ } catch (error) {
64
+ console.error(`${logPrefix} Error transforming ${path}:`, error);
65
+ throw error;
66
+ }
67
+ });
68
+ },
69
+ });
@@ -1,11 +1,30 @@
1
1
  import { spawn } from 'child_process'
2
2
  import * as fs from 'fs'
3
3
  import * as path from 'path'
4
+ import { fileURLToPath } from 'url'
4
5
  import { OutputParser } from '../monitor/output-parser.jsx'
5
6
  import { StreamFormatter } from '../monitor/stream-formatter.jsx'
6
7
  import { LogWriter } from '../monitor/log-writer.jsx'
7
8
  import { summarizeWithHaiku } from '../monitor/haiku-summarizer.jsx'
8
9
 
10
+ /**
11
+ * Find the preload.ts file from the smithers-orchestrator package
12
+ */
13
+ function findPreloadPath(): string {
14
+ const __filename = fileURLToPath(import.meta.url)
15
+ const __dirname = path.dirname(__filename)
16
+ // Navigate up from src/orchestrator/commands to package root
17
+ let dir = __dirname
18
+ while (dir !== path.dirname(dir)) {
19
+ const preloadPath = path.join(dir, 'preload.ts')
20
+ if (fs.existsSync(preloadPath)) {
21
+ return preloadPath
22
+ }
23
+ dir = path.dirname(dir)
24
+ }
25
+ throw new Error('Could not find preload.ts - smithers-orchestrator may be incorrectly installed')
26
+ }
27
+
9
28
  interface MonitorOptions {
10
29
  file?: string
11
30
  summary?: boolean
@@ -42,7 +61,8 @@ export async function monitor(fileArg?: string, options: MonitorOptions = {}) {
42
61
 
43
62
  // Start execution
44
63
  const startTime = Date.now()
45
- const child = spawn('bun', ['--install=fallback', filePath], {
64
+ const preloadPath = findPreloadPath()
65
+ const child = spawn('bun', ['--preload', preloadPath, '--install=fallback', filePath], {
46
66
  stdio: ['inherit', 'pipe', 'pipe'],
47
67
  shell: true,
48
68
  })
@@ -1,6 +1,25 @@
1
1
  import { spawn } from 'child_process'
2
2
  import * as fs from 'fs'
3
3
  import * as path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ /**
7
+ * Find the preload.ts file from the smithers-orchestrator package
8
+ */
9
+ function findPreloadPath(): string {
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = path.dirname(__filename)
12
+ // Navigate up from src/orchestrator/commands to package root
13
+ let dir = __dirname
14
+ while (dir !== path.dirname(dir)) {
15
+ const preloadPath = path.join(dir, 'preload.ts')
16
+ if (fs.existsSync(preloadPath)) {
17
+ return preloadPath
18
+ }
19
+ dir = path.dirname(dir)
20
+ }
21
+ throw new Error('Could not find preload.ts - smithers-orchestrator may be incorrectly installed')
22
+ }
4
23
 
5
24
  interface RunOptions {
6
25
  file?: string
@@ -35,7 +54,8 @@ export async function run(fileArg?: string, options: RunOptions = {}) {
35
54
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
36
55
  console.log('')
37
56
 
38
- const child = spawn('bun', ['--install=fallback', filePath], {
57
+ const preloadPath = findPreloadPath()
58
+ const child = spawn('bun', ['--preload', preloadPath, '--install=fallback', filePath], {
39
59
  stdio: 'inherit',
40
60
  shell: true,
41
61
  })
@@ -172,9 +172,14 @@ export function SmithersProvider(props: SmithersProviderProps): JSX.Element {
172
172
  isRebaseRequested: rebaseRequested,
173
173
  }
174
174
 
175
+ // Use a function to render children lazily after context is established
176
+ // This is important for Solid.js universal renderer where children may
177
+ // be evaluated eagerly before the context provider is set up
178
+ const renderChildren = () => props.children
179
+
175
180
  return (
176
181
  <SmithersContext.Provider value={value}>
177
- {props.children}
182
+ {renderChildren()}
178
183
  </SmithersContext.Provider>
179
184
  )
180
185
  }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Unit tests for ReportTool.ts
3
+ */
4
+ import { describe, test, expect, mock } from 'bun:test'
5
+ import { createReportTool, getReportToolDescription } from './ReportTool'
6
+
7
+ describe('getReportToolDescription', () => {
8
+ test('returns a non-empty string', () => {
9
+ const description = getReportToolDescription()
10
+ expect(typeof description).toBe('string')
11
+ expect(description.length).toBeGreaterThan(0)
12
+ })
13
+
14
+ test('contains Report Tool header', () => {
15
+ const description = getReportToolDescription()
16
+ expect(description).toContain('## Report Tool')
17
+ })
18
+
19
+ test('describes report types', () => {
20
+ const description = getReportToolDescription()
21
+ expect(description).toContain('progress')
22
+ expect(description).toContain('findings')
23
+ expect(description).toContain('warnings')
24
+ expect(description).toContain('errors')
25
+ expect(description).toContain('metrics')
26
+ expect(description).toContain('decisions')
27
+ })
28
+
29
+ test('includes example usage', () => {
30
+ const description = getReportToolDescription()
31
+ expect(description).toContain('Example usage')
32
+ expect(description).toContain('"type": "finding"')
33
+ expect(description).toContain('"title"')
34
+ expect(description).toContain('"content"')
35
+ })
36
+
37
+ test('mentions severity levels', () => {
38
+ const description = getReportToolDescription()
39
+ expect(description).toContain('severity')
40
+ expect(description).toContain('critical')
41
+ })
42
+ })
43
+
44
+ describe('createReportTool', () => {
45
+ // Create a mock context
46
+ const mockAddReport = mock(async () => 'report-123')
47
+ const mockContext = {
48
+ db: {
49
+ vcs: {
50
+ addReport: mockAddReport,
51
+ },
52
+ },
53
+ agentId: 'test-agent',
54
+ }
55
+
56
+ test('returns a tool with correct name', () => {
57
+ const tool = createReportTool(mockContext as any)
58
+ expect(tool.name).toBe('Report')
59
+ })
60
+
61
+ test('has description', () => {
62
+ const tool = createReportTool(mockContext as any)
63
+ expect(tool.description).toBeDefined()
64
+ expect(tool.description.length).toBeGreaterThan(0)
65
+ })
66
+
67
+ test('has input schema', () => {
68
+ const tool = createReportTool(mockContext as any)
69
+ expect(tool.inputSchema).toBeDefined()
70
+ expect(tool.inputSchema.type).toBe('object')
71
+ expect(tool.inputSchema.properties).toBeDefined()
72
+ })
73
+
74
+ test('input schema has required fields', () => {
75
+ const tool = createReportTool(mockContext as any)
76
+ expect(tool.inputSchema.required).toContain('type')
77
+ expect(tool.inputSchema.required).toContain('title')
78
+ expect(tool.inputSchema.required).toContain('content')
79
+ })
80
+
81
+ test('input schema has type enum', () => {
82
+ const tool = createReportTool(mockContext as any)
83
+ const typeProperty = tool.inputSchema.properties?.type as any
84
+ expect(typeProperty.enum).toContain('progress')
85
+ expect(typeProperty.enum).toContain('finding')
86
+ expect(typeProperty.enum).toContain('warning')
87
+ expect(typeProperty.enum).toContain('error')
88
+ expect(typeProperty.enum).toContain('metric')
89
+ expect(typeProperty.enum).toContain('decision')
90
+ })
91
+
92
+ test('input schema has severity enum', () => {
93
+ const tool = createReportTool(mockContext as any)
94
+ const severityProperty = tool.inputSchema.properties?.severity as any
95
+ expect(severityProperty.enum).toContain('info')
96
+ expect(severityProperty.enum).toContain('warning')
97
+ expect(severityProperty.enum).toContain('critical')
98
+ })
99
+
100
+ test('has execute function', () => {
101
+ const tool = createReportTool(mockContext as any)
102
+ expect(typeof tool.execute).toBe('function')
103
+ })
104
+
105
+ test('execute calls addReport with correct data', async () => {
106
+ const addReport = mock(async () => 'report-456')
107
+ const context = {
108
+ db: { vcs: { addReport } },
109
+ agentId: 'agent-123',
110
+ }
111
+
112
+ const tool = createReportTool(context as any)
113
+ await tool.execute({
114
+ type: 'progress',
115
+ title: 'Test Progress',
116
+ content: 'Progress update content',
117
+ })
118
+
119
+ expect(addReport).toHaveBeenCalledWith({
120
+ type: 'progress',
121
+ title: 'Test Progress',
122
+ content: 'Progress update content',
123
+ data: undefined,
124
+ severity: 'info',
125
+ agent_id: 'agent-123',
126
+ })
127
+ })
128
+
129
+ test('execute returns success with reportId', async () => {
130
+ const addReport = mock(async () => 'report-789')
131
+ const context = {
132
+ db: { vcs: { addReport } },
133
+ agentId: 'agent-123',
134
+ }
135
+
136
+ const tool = createReportTool(context as any)
137
+ const result = await tool.execute({
138
+ type: 'finding',
139
+ title: 'Test Finding',
140
+ content: 'Finding content',
141
+ })
142
+
143
+ expect(result.success).toBe(true)
144
+ expect(result.reportId).toBe('report-789')
145
+ expect(result.message).toContain('Test Finding')
146
+ })
147
+
148
+ test('defaults severity to critical for error type', async () => {
149
+ const addReport = mock(async () => 'report-err')
150
+ const context = {
151
+ db: { vcs: { addReport } },
152
+ agentId: 'agent-123',
153
+ }
154
+
155
+ const tool = createReportTool(context as any)
156
+ await tool.execute({
157
+ type: 'error',
158
+ title: 'Test Error',
159
+ content: 'Error content',
160
+ })
161
+
162
+ expect(addReport).toHaveBeenCalledWith(
163
+ expect.objectContaining({ severity: 'critical' })
164
+ )
165
+ })
166
+
167
+ test('defaults severity to warning for warning type', async () => {
168
+ const addReport = mock(async () => 'report-warn')
169
+ const context = {
170
+ db: { vcs: { addReport } },
171
+ agentId: 'agent-123',
172
+ }
173
+
174
+ const tool = createReportTool(context as any)
175
+ await tool.execute({
176
+ type: 'warning',
177
+ title: 'Test Warning',
178
+ content: 'Warning content',
179
+ })
180
+
181
+ expect(addReport).toHaveBeenCalledWith(
182
+ expect.objectContaining({ severity: 'warning' })
183
+ )
184
+ })
185
+
186
+ test('uses provided severity over default', async () => {
187
+ const addReport = mock(async () => 'report-custom')
188
+ const context = {
189
+ db: { vcs: { addReport } },
190
+ agentId: 'agent-123',
191
+ }
192
+
193
+ const tool = createReportTool(context as any)
194
+ await tool.execute({
195
+ type: 'error',
196
+ title: 'Test Error',
197
+ content: 'Error content',
198
+ severity: 'info', // Override critical default
199
+ })
200
+
201
+ expect(addReport).toHaveBeenCalledWith(
202
+ expect.objectContaining({ severity: 'info' })
203
+ )
204
+ })
205
+
206
+ test('includes data in report', async () => {
207
+ const addReport = mock(async () => 'report-data')
208
+ const context = {
209
+ db: { vcs: { addReport } },
210
+ agentId: 'agent-123',
211
+ }
212
+
213
+ const tool = createReportTool(context as any)
214
+ await tool.execute({
215
+ type: 'metric',
216
+ title: 'Test Metric',
217
+ content: 'Metric content',
218
+ data: { value: 42, unit: 'ms' },
219
+ })
220
+
221
+ expect(addReport).toHaveBeenCalledWith(
222
+ expect.objectContaining({
223
+ data: { value: 42, unit: 'ms' },
224
+ })
225
+ )
226
+ })
227
+ })