@positronic/template-new-project 0.0.2

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.
@@ -0,0 +1,307 @@
1
+ # Brain Testing Guide
2
+
3
+ This guide explains how to test Positronic brains using the testing utilities provided by `@positronic/core`.
4
+
5
+ ## Testing Philosophy
6
+
7
+ Following the principles from [Kent C. Dodds' testing philosophy](https://kentcdodds.com/blog/write-tests):
8
+ - **Write tests. Not too many. Mostly integration.**
9
+ - Test user outcomes, not implementation details
10
+ - Focus on what your brain produces, not how it produces it
11
+
12
+ ## Overview
13
+
14
+ Testing brains is about verifying they produce the correct outputs given specific AI responses. The testing utilities make it easy to mock AI responses and assert on the final results.
15
+
16
+ ## Quick Start
17
+
18
+ ```typescript
19
+ import { createMockClient, runBrainTest } from '@positronic/core';
20
+ import yourBrain from './your-brain.js';
21
+
22
+ describe('your-brain', () => {
23
+ it('should process user data and generate a report', async () => {
24
+ // Arrange: Set up AI responses
25
+ const mockClient = createMockClient();
26
+ mockClient.mockResponses(
27
+ { analysis: 'User shows high engagement', score: 0.85 },
28
+ { report: 'Detailed engagement report...', recommendations: ['A', 'B'] }
29
+ );
30
+
31
+ // Act: Run the brain
32
+ const result = await runBrainTest(yourBrain, { client: mockClient });
33
+
34
+ // Assert: Verify the outcome
35
+ expect(result.completed).toBe(true);
36
+ expect(result.finalState.report).toContain('Detailed engagement report');
37
+ expect(result.finalState.recommendations).toHaveLength(2);
38
+ });
39
+ });
40
+ ```
41
+
42
+ ## API Reference
43
+
44
+ ### runBrainTest
45
+
46
+ ```typescript
47
+ const result = await runBrainTest(brain, {
48
+ client: mockClient, // Optional: defaults to createMockClient()
49
+ initialState: { count: 0 }, // Optional: initial state
50
+ resources: resourceLoader, // Optional: resources
51
+ brainOptions: { mode: 'test' } // Optional: brain-specific options
52
+ });
53
+ ```
54
+
55
+ **Returns:**
56
+ - `completed: boolean` - Whether the brain completed successfully
57
+ - `error: Error | null` - Any error that occurred
58
+ - `finalState: State` - The final state after all steps
59
+ - `steps: string[]` - Names of executed steps in order
60
+
61
+ ## MockObjectGenerator API
62
+
63
+ ### Creating a Mock Client
64
+
65
+ ```typescript
66
+ const mockClient = createMockClient();
67
+ ```
68
+
69
+ ### Mocking Responses
70
+
71
+ #### Single Response
72
+ ```typescript
73
+ mockClient.mockNextResponse({
74
+ answer: 'The capital of France is Paris',
75
+ confidence: 0.95
76
+ });
77
+ ```
78
+
79
+ #### Multiple Responses
80
+ ```typescript
81
+ mockClient.mockResponses(
82
+ { step1: 'completed' },
83
+ { step2: 'processed' },
84
+ { finalResult: 'success' }
85
+ );
86
+ ```
87
+
88
+ #### Error Responses
89
+ ```typescript
90
+ mockClient.mockNextError('API rate limit exceeded');
91
+ // or
92
+ mockClient.mockNextError(new Error('Connection timeout'));
93
+ ```
94
+
95
+ ### Assertions
96
+
97
+ ```typescript
98
+ // Check call count
99
+ mockClient.expectCallCount(3);
100
+
101
+ // Check call parameters
102
+ mockClient.expectCalledWith({
103
+ prompt: 'Generate a summary',
104
+ schemaName: 'summarySchema'
105
+ });
106
+
107
+ // Get call history
108
+ const calls = mockClient.getCalls();
109
+ ```
110
+
111
+ ## Testing Patterns
112
+
113
+ ### Testing Success Cases
114
+
115
+ Focus on what the brain produces for the user:
116
+
117
+ ```typescript
118
+ it('should generate personalized recommendations', async () => {
119
+ // Arrange
120
+ mockClient.mockResponses(
121
+ { preferences: ['tech', 'sports'], confidence: 0.9 },
122
+ { recommendations: ['Item A', 'Item B', 'Item C'] }
123
+ );
124
+
125
+ // Act
126
+ const result = await runBrainTest(recommendationBrain, {
127
+ client: mockClient,
128
+ initialState: { userId: '123' }
129
+ });
130
+
131
+ // Assert outcomes, not implementation
132
+ expect(result.completed).toBe(true);
133
+ expect(result.finalState.recommendations).toHaveLength(3);
134
+ expect(result.finalState.confidence).toBeGreaterThan(0.8);
135
+ });
136
+ ```
137
+
138
+ ### Testing Error Cases
139
+
140
+ ```typescript
141
+ it('should handle API failures gracefully', async () => {
142
+ // Arrange
143
+ mockClient.mockNextError('Service temporarily unavailable');
144
+
145
+ // Act
146
+ const result = await runBrainTest(processingBrain, { client: mockClient });
147
+
148
+ // Assert
149
+ expect(result.completed).toBe(false);
150
+ expect(result.error?.message).toBe('Service temporarily unavailable');
151
+ });
152
+ ```
153
+
154
+ ### Testing State Flow
155
+
156
+ Verify that data flows correctly through your brain:
157
+
158
+ ```typescript
159
+ it('should use customer data to generate personalized content', async () => {
160
+ // Arrange
161
+ const customerName = 'Alice';
162
+ mockClient.mockResponses(
163
+ { greeting: 'Hello Alice!', tone: 'friendly' },
164
+ { email: 'Personalized email content...' }
165
+ );
166
+
167
+ // Act
168
+ const result = await runBrainTest(emailBrain, {
169
+ client: mockClient,
170
+ initialState: { customerName }
171
+ });
172
+
173
+ // Assert that the AI used the customer data
174
+ const calls = mockClient.getCalls();
175
+ expect(calls[0].params.prompt).toContain(customerName);
176
+ expect(result.finalState.email).toContain('Personalized email content');
177
+ });
178
+ ```
179
+
180
+ ## Best Practices
181
+
182
+ 1. **Test Behavior, Not Implementation**
183
+ - ✅ Test what the brain produces
184
+ - ❌ Don't test internal event sequences
185
+ - ❌ Don't test step counts unless it's a user requirement
186
+
187
+ 2. **Use Descriptive Test Names**
188
+ - ✅ `it('should generate a summary from user feedback')`
189
+ - ❌ `it('should complete 4 steps and emit events')`
190
+
191
+ 3. **Keep Tests Simple**
192
+ - Arrange: Set up mock responses
193
+ - Act: Run the brain
194
+ - Assert: Check the outcome
195
+
196
+ 4. **Test Edge Cases That Matter**
197
+ - API errors
198
+ - Empty responses
199
+ - Invalid data that could affect users
200
+
201
+ ## What Not to Test
202
+
203
+ Following testing best practices, avoid testing:
204
+
205
+ 1. **Implementation Details**
206
+ - Don't check specific event types
207
+ - Don't verify internal state transformations
208
+ - Don't count patch operations
209
+
210
+ 2. **Framework Behavior**
211
+ - Trust that the brain framework works
212
+ - Don't test that steps execute in order
213
+ - Don't verify event emission
214
+
215
+ 3. **Mock Behavior**
216
+ - Don't test that mocks were called (unless verifying data flow)
217
+ - Focus on what the brain does with the responses
218
+
219
+ ## Complete Example
220
+
221
+ ```typescript
222
+ import { createMockClient, runBrainTest } from '@positronic/core';
223
+ import analysisBrain from './analysis-brain.js';
224
+
225
+ describe('analysis-brain', () => {
226
+ let mockClient;
227
+
228
+ beforeEach(() => {
229
+ mockClient = createMockClient();
230
+ });
231
+
232
+ it('should analyze customer feedback and generate insights', async () => {
233
+ // Arrange: Set up AI to return analysis
234
+ mockClient.mockNextResponse({
235
+ sentiment: 'positive',
236
+ keywords: ['innovation', 'quality', 'service'],
237
+ summary: 'Customers appreciate product quality and innovation'
238
+ });
239
+
240
+ // Act: Run analysis on customer feedback
241
+ const result = await runBrainTest(analysisBrain, {
242
+ client: mockClient,
243
+ initialState: {
244
+ feedback: 'Your product is innovative and high quality...'
245
+ }
246
+ });
247
+
248
+ // Assert: Verify we got actionable insights
249
+ expect(result.completed).toBe(true);
250
+ expect(result.finalState.insights).toEqual({
251
+ sentiment: 'positive',
252
+ topThemes: ['innovation', 'quality', 'service'],
253
+ actionable: true,
254
+ summary: 'Customers appreciate product quality and innovation'
255
+ });
256
+ });
257
+
258
+ it('should handle analysis service outages', async () => {
259
+ // Arrange: Simulate service failure
260
+ mockClient.mockNextError('Analysis service unavailable');
261
+
262
+ // Act: Attempt analysis
263
+ const result = await runBrainTest(analysisBrain, {
264
+ client: mockClient,
265
+ initialState: { feedback: 'Some feedback...' }
266
+ });
267
+
268
+ // Assert: Verify graceful failure
269
+ expect(result.completed).toBe(false);
270
+ expect(result.error?.message).toBe('Analysis service unavailable');
271
+ });
272
+ });
273
+ ```
274
+
275
+ ## Troubleshooting
276
+
277
+ ### TypeScript Issues
278
+
279
+ The test utilities are fully typed. If you get type errors:
280
+ ```typescript
281
+ // ✅ Type-safe: result.finalState is typed as your brain's state
282
+ const result = await runBrainTest(myBrain, { initialState });
283
+ expect(result.finalState.myProperty).toBe('value');
284
+ ```
285
+
286
+ ### Common Issues
287
+
288
+ 1. **Mock count mismatch**: Ensure you mock the same number of responses as AI calls in your brain
289
+ 2. **State assertions failing**: Check that your brain is actually setting the expected state
290
+ 3. **Completion is false**: Your brain might be throwing an error - check `result.error`
291
+
292
+ ### Debugging Tips
293
+
294
+ ```typescript
295
+ // See what prompts are being sent to AI
296
+ const calls = mockClient.getCalls();
297
+ console.log('AI prompts:', calls.map(c => c.params.prompt));
298
+
299
+ // Check execution order
300
+ console.log('Steps executed:', result.steps);
301
+ ```
302
+
303
+ ## Next Steps
304
+
305
+ - Review the [Brain DSL Guide](./brain-dsl-guide.md) for more information on brain structure
306
+ - Check example tests in the codebase for real-world testing patterns
307
+ - Remember: focus on testing what matters to users, not how the brain works internally
@@ -0,0 +1,236 @@
1
+ # Positronic Project Guide
2
+
3
+ This guide covers project-level patterns and best practices for Positronic applications.
4
+
5
+ ## Project Structure
6
+
7
+ A typical Positronic project has the following structure:
8
+
9
+ ```
10
+ ├── brain.ts # Project brain wrapper
11
+ ├── brains/ # Brain definitions
12
+ ├── resources/ # Files accessible to brains
13
+ ├── tests/ # Test files
14
+ ├── docs/ # Documentation
15
+ ├── runner.ts # Local runner for development
16
+ └── positronic.config.json # Project configuration
17
+ ```
18
+
19
+ ## The Project Brain Pattern
20
+
21
+ Every Positronic project includes a `brain.ts` file in the root directory. This file exports a custom `brain` function that wraps the core Positronic brain function.
22
+
23
+ ### Why Use a Project Brain?
24
+
25
+ The project brain pattern provides a single place to:
26
+ - Configure services that all brains can access
27
+ - Set up logging, monitoring, or analytics
28
+ - Add authentication or API clients
29
+ - Establish project-wide conventions
30
+
31
+ ### Basic Usage
32
+
33
+ All brains in your project should import from `../brain.js` instead of `@positronic/core`:
34
+
35
+ ```typescript
36
+ // ✅ DO THIS (from a file in the brains/ directory)
37
+ import { brain } from '../brain.js';
38
+
39
+ // ❌ NOT THIS
40
+ import { brain } from '@positronic/core';
41
+ ```
42
+
43
+ ### Configuring Services
44
+
45
+ To add project-wide services, modify the `brain.ts` file in the root directory:
46
+
47
+ ```typescript
48
+ import { brain as coreBrain, type Brain } from '@positronic/core';
49
+
50
+ // Define your services
51
+ interface ProjectServices {
52
+ logger: {
53
+ info: (message: string) => void;
54
+ error: (message: string) => void;
55
+ };
56
+ database: {
57
+ get: (key: string) => Promise<any>;
58
+ set: (key: string, value: any) => Promise<void>;
59
+ };
60
+ }
61
+
62
+ // Export the wrapped brain function
63
+ export function brain<
64
+ TOptions extends object = object,
65
+ TState extends object = object
66
+ >(
67
+ brainConfig: string | { title: string; description?: string }
68
+ ): Brain<TOptions, TState, ProjectServices> {
69
+ return coreBrain<TOptions, TState, ProjectServices>(brainConfig)
70
+ .withServices({
71
+ logger: {
72
+ info: (msg) => console.log(`[<%= '${new Date().toISOString()}' %>] INFO: <%= '${msg}' %>`),
73
+ error: (msg) => console.error(`[<%= '${new Date().toISOString()}' %>] ERROR: <%= '${msg}' %>`)
74
+ },
75
+ database: {
76
+ get: async (key) => {
77
+ // Your database implementation
78
+ return localStorage.getItem(key);
79
+ },
80
+ set: async (key, value) => {
81
+ // Your database implementation
82
+ localStorage.setItem(key, JSON.stringify(value));
83
+ }
84
+ }
85
+ });
86
+ }
87
+ ```
88
+
89
+ Now all brains automatically have access to these services:
90
+
91
+ ```typescript
92
+ import { brain } from '../brain.js';
93
+
94
+ export default brain('User Processor')
95
+ .step('Load User', async ({ logger, database }) => {
96
+ logger.info('Loading user data');
97
+ const userData = await database.get('current-user');
98
+ return { user: userData };
99
+ });
100
+ ```
101
+
102
+ ## Resource Organization
103
+
104
+ Resources are files that brains can access during execution. Organize them logically:
105
+
106
+ ```
107
+ resources/
108
+ ├── prompts/ # AI prompt templates
109
+ │ ├── customer-support.md
110
+ │ └── code-review.md
111
+ ├── data/ # Static data files
112
+ │ └── config.json
113
+ └── templates/ # Document templates
114
+ └── report.md
115
+ ```
116
+
117
+ ## Testing Strategy
118
+
119
+ Keep test files in the `tests/` directory to avoid deployment issues. Tests should:
120
+ - Focus on brain outcomes, not implementation
121
+ - Use mock clients and services
122
+ - Verify the final state and important side effects
123
+
124
+ See `/docs/brain-testing-guide.md` for detailed testing guidance.
125
+
126
+ ## Development Workflow
127
+
128
+ 1. **Start the development server**: `px server -d`
129
+ 2. **Create or modify brains**: Always import from `./brain.js`
130
+ 3. **Test locally**: `px brain run <brain-name>`
131
+ 4. **Run tests**: `npm test`
132
+ 5. **Deploy**: Backend-specific commands (e.g., `px deploy` for Cloudflare)
133
+
134
+ ## Configuration
135
+
136
+ The `positronic.config.json` file contains project metadata:
137
+
138
+ ```json
139
+ {
140
+ "projectName": "my-project",
141
+ "backend": "cloudflare"
142
+ }
143
+ ```
144
+
145
+ ## Environment Variables
146
+
147
+ Use `.env` files for configuration:
148
+
149
+ ```bash
150
+ # API Keys
151
+ ANTHROPIC_API_KEY=your-key-here
152
+ OPENAI_API_KEY=your-key-here
153
+
154
+ # Backend-specific
155
+ <% if (backend === 'cloudflare') { %>CLOUDFLARE_ACCOUNT_ID=your-account-id
156
+ CLOUDFLARE_API_TOKEN=your-api-token<% } %>
157
+ ```
158
+
159
+ ## Best Practices
160
+
161
+ 1. **Services**: Configure once in `brain.ts`, use everywhere
162
+ 2. **Resources**: Use for content that non-developers should be able to edit
163
+ 3. **Secrets**: Never commit API keys; use environment variables
164
+ 4. **Organization**: Group related brains in folders as your project grows
165
+ 5. **Testing**: Write tests for critical workflows
166
+ 6. **Documentation**: Keep project-specific docs in the `docs/` folder
167
+
168
+ ## Common Patterns
169
+
170
+ ### Logging and Monitoring
171
+
172
+ ```typescript
173
+ // In brain.ts
174
+ interface ProjectServices {
175
+ metrics: {
176
+ track: (event: string, properties?: any) => void;
177
+ time: (label: string) => () => void;
178
+ };
179
+ }
180
+
181
+ // In your brain
182
+ export default brain('Analytics Brain')
183
+ .step('Start Timer', ({ metrics }) => {
184
+ const endTimer = metrics.time('processing');
185
+ return { endTimer };
186
+ })
187
+ .step('Process', ({ state }) => {
188
+ // Do work...
189
+ return state;
190
+ })
191
+ .step('End Timer', ({ state, metrics }) => {
192
+ state.endTimer();
193
+ metrics.track('processing_complete');
194
+ return state;
195
+ });
196
+ ```
197
+
198
+ ### API Integration
199
+
200
+ ```typescript
201
+ // In brain.ts
202
+ interface ProjectServices {
203
+ api: {
204
+ get: (path: string) => Promise<any>;
205
+ post: (path: string, data: any) => Promise<any>;
206
+ };
207
+ }
208
+
209
+ // Configure with base URL and auth
210
+ const api = {
211
+ get: async (path: string) => {
212
+ const response = await fetch(`https://api.example.com<%= '${path}' %>`, {
213
+ headers: { 'Authorization': `Bearer <%= '${process.env.API_KEY}' %>` }
214
+ });
215
+ return response.json();
216
+ },
217
+ post: async (path: string, data: any) => {
218
+ const response = await fetch(`https://api.example.com<%= '${path}' %>`, {
219
+ method: 'POST',
220
+ headers: {
221
+ 'Authorization': `Bearer <%= '${process.env.API_KEY}' %>`,
222
+ 'Content-Type': 'application/json'
223
+ },
224
+ body: JSON.stringify(data)
225
+ });
226
+ return response.json();
227
+ }
228
+ };
229
+ ```
230
+
231
+ ## Getting Help
232
+
233
+ - **Documentation**: https://positronic.dev
234
+ - **CLI Help**: `px --help`
235
+ - **Brain DSL Guide**: `/docs/brain-dsl-guide.md`
236
+ - **Testing Guide**: `/docs/brain-testing-guide.md`