@positronic/template-new-project 0.0.76 → 0.0.77

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/index.js CHANGED
@@ -53,10 +53,10 @@ module.exports = {
53
53
  ],
54
54
  setup: async ctx => {
55
55
  const devRootPath = process.env.POSITRONIC_LOCAL_PATH;
56
- let coreVersion = '^0.0.76';
57
- let cloudflareVersion = '^0.0.76';
58
- let clientVercelVersion = '^0.0.76';
59
- let genUIComponentsVersion = '^0.0.76';
56
+ let coreVersion = '^0.0.77';
57
+ let cloudflareVersion = '^0.0.77';
58
+ let clientVercelVersion = '^0.0.77';
59
+ let genUIComponentsVersion = '^0.0.77';
60
60
 
61
61
  // Map backend selection to package names
62
62
  const backendPackageMap = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@positronic/template-new-project",
3
- "version": "0.0.76",
3
+ "version": "0.0.77",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -81,7 +81,7 @@ brain('AI Education Assistant')
81
81
  context: 'We are creating an educational example',
82
82
  }))
83
83
  .prompt('Generate explanation', {
84
- template: ({ topic, context }) =>
84
+ template: ({ state: { topic, context } }) =>
85
85
  `<%= '${context}' %>. Please provide a brief, beginner-friendly explanation of <%= '${topic}' %>.`,
86
86
  outputSchema: {
87
87
  schema: z.object({
@@ -104,7 +104,7 @@ brain('AI Education Assistant')
104
104
  .prompt(
105
105
  'Generate follow-up questions',
106
106
  {
107
- template: ({ formattedOutput }) =>
107
+ template: ({ state: { formattedOutput } }) =>
108
108
  `Based on this explanation about <%= '${formattedOutput.topic}' %>: "<%= '${formattedOutput.explanation}' %>"
109
109
 
110
110
  Generate 3 thoughtful follow-up questions that a student might ask.`,
@@ -147,7 +147,7 @@ const smartModel = createAnthropicClient({ model: 'claude-sonnet-4-5-20250929' }
147
147
 
148
148
  brain('Multi-Model Brain')
149
149
  .prompt('Quick summary', {
150
- template: ({ document }) => `Summarize this briefly: <%= '${document}' %>`,
150
+ template: ({ state: { document } }) => `Summarize this briefly: <%= '${document}' %>`,
151
151
  outputSchema: {
152
152
  schema: z.object({ summary: z.string() }),
153
153
  name: 'quickSummary' as const,
@@ -155,7 +155,7 @@ brain('Multi-Model Brain')
155
155
  client: fastModel, // Use a fast, cheap model for summarization
156
156
  })
157
157
  .prompt('Deep analysis', {
158
- template: ({ quickSummary }) =>
158
+ template: ({ state: { quickSummary } }) =>
159
159
  `Analyze the implications of this summary: <%= '${quickSummary.summary}' %>`,
160
160
  outputSchema: {
161
161
  schema: z.object({
@@ -419,7 +419,7 @@ Services are destructured alongside other parameters in:
419
419
  2. **Prompt Reduce Functions**:
420
420
  ```typescript
421
421
  .prompt('Generate', {
422
- template: (state) => 'Generate something',
422
+ template: ({ state }) => 'Generate something',
423
423
  outputSchema: { schema, name: 'result' as const }
424
424
  }, async ({ state, response, logger, database }) => {
425
425
  logger.info('Saving AI response');
@@ -477,7 +477,7 @@ const analysisBrain = brain('Data Analysis')
477
477
  return { ...state, data };
478
478
  })
479
479
  .prompt('Analyze Data', {
480
- template: ({ data }) => `Analyze this data: <%= '${JSON.stringify(data)}' %>`,
480
+ template: ({ state: { data } }) => `Analyze this data: <%= '${JSON.stringify(data)}' %>`,
481
481
  outputSchema: {
482
482
  schema: z.object({
483
483
  insights: z.array(z.string()),
@@ -630,7 +630,7 @@ const brainWithComponents = brain('Custom UI Brain')
630
630
  }
631
631
  })
632
632
  .ui('Dashboard', {
633
- template: (state) => `
633
+ template: ({ state }) => `
634
634
  Create a dashboard using CustomCard components to display:
635
635
  - User name: <%= '${state.userName}' %>
636
636
  - Account status: <%= '${state.status}' %>
@@ -931,7 +931,7 @@ Resources are also available in prompt templates:
931
931
 
932
932
  ```typescript
933
933
  brain('Template Example').prompt('Generate Content', {
934
- template: async (state, resources) => {
934
+ template: async ({ state, resources }) => {
935
935
  const template = await resources.prompts.customerSupport.load();
936
936
  return template.replace('{{issue}}', state.issue);
937
937
  },
@@ -1025,7 +1025,7 @@ interface FilterPromptState {
1025
1025
 
1026
1026
  // Export the prompt configuration
1027
1027
  export const aiFilterPrompt = {
1028
- template: async (state: FilterPromptState, resources: Resources) => {
1028
+ template: async ({ state, resources }: { state: FilterPromptState, resources: Resources }) => {
1029
1029
  // Load a prompt template from resources
1030
1030
  const template = await resources.prompts.hnFilter.load();
1031
1031
 
@@ -1093,7 +1093,7 @@ brain('Item Processor')
1093
1093
  ]
1094
1094
  }))
1095
1095
  .prompt('Summarize Items', {
1096
- template: (item) => `Summarize this item: <%= '${item.title}' %>`,
1096
+ template: ({ item }) => `Summarize this item: <%= '${item.title}' %>`,
1097
1097
  outputSchema: {
1098
1098
  schema: z.object({ summary: z.string() }),
1099
1099
  name: 'summaries' as const
@@ -1202,7 +1202,7 @@ brain('Dynamic Processor')
1202
1202
  ]
1203
1203
  }))
1204
1204
  .prompt('Process', {
1205
- template: (item) => `Process item <%= '${item.id}' %>`,
1205
+ template: ({ item }) => `Process item <%= '${item.id}' %>`,
1206
1206
  outputSchema: {
1207
1207
  schema: z.object({ result: z.string() }),
1208
1208
  name: 'results' as const,
@@ -1534,7 +1534,7 @@ brain('Feedback Collector')
1534
1534
  }))
1535
1535
  // Generate the form
1536
1536
  .ui('Collect Feedback', {
1537
- template: (state) => `
1537
+ template: ({ state }) => `
1538
1538
  Create a feedback form for <%= '${state.userName}' %>.
1539
1539
  Include fields for rating (1-5) and comments.
1540
1540
  `,
@@ -1580,7 +1580,7 @@ Be specific about layout and content:
1580
1580
 
1581
1581
  ```typescript
1582
1582
  .ui('Contact Form', {
1583
- template: (state) => `
1583
+ template: ({ state }) => `
1584
1584
  Create a contact form with:
1585
1585
  - Header: "Get in Touch"
1586
1586
  - Name field (required)
@@ -1604,7 +1604,7 @@ Use `{{path}}` syntax to bind props to runtime data:
1604
1604
 
1605
1605
  ```typescript
1606
1606
  .ui('Order Summary', {
1607
- template: (state) => `
1607
+ template: ({ state }) => `
1608
1608
  Create an order summary showing:
1609
1609
  - List of items from {{cart.items}}
1610
1610
  - Total: {{cart.total}}
@@ -1651,7 +1651,7 @@ brain('User Onboarding')
1651
1651
 
1652
1652
  // Step 2: Preferences
1653
1653
  .ui('Preferences', {
1654
- template: (state) => `
1654
+ template: ({ state }) => `
1655
1655
  Create preferences form for <%= '${state.userData.firstName}' %>:
1656
1656
  - Newsletter subscription checkbox
1657
1657
  - Contact preference (email/phone/sms)
@@ -1708,7 +1708,7 @@ const completeBrain = brain({
1708
1708
  return { startTime: Date.now() };
1709
1709
  })
1710
1710
  .prompt('Generate Plan', {
1711
- template: async (state, resources) => {
1711
+ template: async ({ state, resources }) => {
1712
1712
  // Load a template from resources
1713
1713
  const template = await resources.templates.projectPlan.load();
1714
1714
  return template.replace('{{context}}', 'software project');
@@ -31,11 +31,11 @@ This also applies to variable declarations and function parameters:
31
31
  ```typescript
32
32
  // ❌ DON'T DO THIS
33
33
  const names: string[] = options.notify.split(',');
34
- template: (state: any) => { ... }
34
+ template: ({ state }: any) => { ... }
35
35
 
36
36
  // ✅ DO THIS
37
37
  const names = options.notify.split(',');
38
- template: (state) => { ... }
38
+ template: ({ state }) => { ... }
39
39
  ```
40
40
 
41
41
  If you genuinely need a cast to fix a type error, prefer the narrowest cast possible and add it only after seeing the error.
@@ -199,10 +199,10 @@ The same applies to prompt templates:
199
199
 
200
200
  ```typescript
201
201
  // ❌ DON'T DO THIS
202
- template: (state) => `Hello <%= '${state.user.name}' %>, your order <%= '${state.order.id}' %> is ready.`,
202
+ template: ({ state }) => `Hello <%= '${state.user.name}' %>, your order <%= '${state.order.id}' %> is ready.`,
203
203
 
204
204
  // ✅ DO THIS
205
- template: ({ user, order }) => `Hello <%= '${user.name}' %>, your order <%= '${order.id}' %> is ready.`,
205
+ template: ({ state: { user, order } }) => `Hello <%= '${user.name}' %>, your order <%= '${order.id}' %> is ready.`,
206
206
  ```
207
207
 
208
208
  When you still need `state` (e.g. for `...state` in the return value), destructure in the function body instead:
@@ -298,6 +298,44 @@ Use `.values` for simple extraction, `.filter()` for correlated filtering, and `
298
298
 
299
299
  **Name the `outputKey` after the content.** If results contain analyses, use `outputKey: 'analyses' as const`, not `outputKey: 'processedItems' as const`.
300
300
 
301
+ ### Naming convention for filter/map parameters
302
+
303
+ `IterateResult.filter()` and `.map()` take two parameters: the input item and the AI result. **Name them after what they represent**, not generic names like `item` and `r`:
304
+
305
+ ```typescript
306
+ // ❌ DON'T DO THIS - generic parameter names
307
+ state.validations.filter((item, r) => r.matches)
308
+ state.transcripts.filter((t) => t.hasTranscript) // WRONG: single param is the item, not the result
309
+
310
+ // ✅ DO THIS - descriptive names that reflect the data
311
+ state.validations.filter((crawledResult, validation) => validation.matches)
312
+ state.transcripts.filter((match, extraction) => extraction.hasTranscript)
313
+ ```
314
+
315
+ The first parameter is always the input item (what you passed to `over`), and the second is the AI's output (what the `outputSchema` describes). A single-parameter callback only receives the item — if you need the AI result, you must use both parameters.
316
+
317
+ ### Nested brain state mapping
318
+
319
+ When a `.brain()` step runs an inner brain and maps its state into the outer brain, **destructure to select the fields you need** instead of casting:
320
+
321
+ ```typescript
322
+ // ❌ DON'T DO THIS - casting to force the type
323
+ .brain(
324
+ 'Search and validate',
325
+ searchAndValidate,
326
+ ({ brainState }) => brainState as { matches: Match[] },
327
+ )
328
+
329
+ // ✅ DO THIS - destructure to select and let inference work
330
+ .brain(
331
+ 'Search and validate',
332
+ searchAndValidate,
333
+ ({ brainState: { matches } }) => ({ matches }),
334
+ )
335
+ ```
336
+
337
+ The inner brain's state type is fully inferred from its definition. Destructuring picks the fields you want and TypeScript infers the outer state correctly — no casts needed. If the inner brain exports an interface for its output shape, import it for use in downstream steps (like prompt templates).
338
+
301
339
  ## Brain DSL Type Inference
302
340
 
303
341
  The Brain DSL has very strong type inference capabilities. **Important**: You should NOT explicitly specify types on the state object as it flows through steps. The types are automatically inferred from the previous step.
@@ -393,7 +431,7 @@ brain('feedback-collector')
393
431
  }))
394
432
  // Generate the form
395
433
  .ui('Collect Feedback', {
396
- template: (state) => <%= '\`' %>
434
+ template: ({ state }) => <%= '\`' %>
397
435
  Create a feedback form for <%= '${state.userName}' %>:
398
436
  - Rating (1-5)
399
437
  - Comments textarea
@@ -739,7 +777,7 @@ export default feedbackBrain;
739
777
  // Step 3: Run and check logs, see it doesn't analyze yet
740
778
  // Step 4: Add sentiment analysis step
741
779
  .prompt('Analyze sentiment', {
742
- template: ({ feedback }) =>
780
+ template: ({ state: { feedback } }) =>
743
781
  <%= '\`Analyze the sentiment of this feedback: "${feedback}"\`' %>,
744
782
  outputSchema: {
745
783
  schema: z.object({
@@ -753,7 +791,7 @@ export default feedbackBrain;
753
791
  // Step 5: Run again, check logs, test still fails (no response)
754
792
  // Step 6: Add response generation
755
793
  .prompt('Generate response', {
756
- template: ({ sentimentAnalysis, feedback }) =>
794
+ template: ({ state: { sentimentAnalysis, feedback } }) =>
757
795
  <%= '\`Generate a brief response to this ${sentimentAnalysis.sentiment} feedback: "${feedback}"\`' %>,
758
796
  outputSchema: {
759
797
  schema: z.object({