@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 +4 -4
- package/package.json +1 -1
- package/template/docs/brain-dsl-guide.md +16 -16
- package/template/docs/tips-for-agents.md +45 -7
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.
|
|
57
|
-
let cloudflareVersion = '^0.0.
|
|
58
|
-
let clientVercelVersion = '^0.0.
|
|
59
|
-
let genUIComponentsVersion = '^0.0.
|
|
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
|
@@ -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({
|