create-nola-lang 0.1.13 → 0.1.14

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.
Files changed (49) hide show
  1. package/README.md +8 -3
  2. package/dist/{chunk-LUSVIOAN.js → chunk-CB26Z47Y.js} +53 -87
  3. package/dist/examples.d.ts +4 -2
  4. package/dist/examples.js +4 -2
  5. package/dist/flow.d.ts +8 -2
  6. package/dist/flow.js +15 -12
  7. package/dist/github.d.ts +1 -1
  8. package/dist/github.js +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/launch.d.ts +2 -2
  11. package/dist/launch.js +1 -1
  12. package/dist/main.js +1 -1
  13. package/dist/node-version.d.ts +3 -2
  14. package/dist/node-version.js +4 -3
  15. package/dist/providers.d.ts +1 -1
  16. package/dist/providers.js +1 -1
  17. package/dist/registry.d.ts +19 -10
  18. package/dist/registry.js +20 -5
  19. package/dist/scaffold.d.ts +19 -15
  20. package/dist/scaffold.js +63 -113
  21. package/package.json +1 -1
  22. package/skills/nola/SKILL.md +48 -16
  23. package/skills/nola/references/config.md +5 -5
  24. package/skills/nola/references/patterns.md +67 -31
  25. package/skills/nola/references/pitfalls.md +200 -52
  26. package/skills/nola/references/syntax.md +221 -122
  27. package/templates/empty/package.json +5 -5
  28. package/templates/empty/src/main.tsi +8 -0
  29. package/templates/empty/src/main.ts +0 -5
  30. package/templates/feature-extraction/README.md +0 -20
  31. package/templates/feature-extraction/nola.config.ts +0 -14
  32. package/templates/feature-extraction/nola.replay.jsonl +0 -2
  33. package/templates/feature-extraction/package.json +0 -22
  34. package/templates/feature-extraction/src/main.tsi +0 -22
  35. package/templates/feature-extraction/tsconfig.json +0 -12
  36. package/templates/function-calling/README.md +0 -21
  37. package/templates/function-calling/nola.config.ts +0 -14
  38. package/templates/function-calling/nola.replay.jsonl +0 -1
  39. package/templates/function-calling/package.json +0 -22
  40. package/templates/function-calling/src/main.tsi +0 -12
  41. package/templates/function-calling/src/tickets.ts +0 -17
  42. package/templates/function-calling/tsconfig.json +0 -12
  43. package/templates/typescript-interop/README.md +0 -15
  44. package/templates/typescript-interop/nola.config.ts +0 -14
  45. package/templates/typescript-interop/nola.replay.jsonl +0 -1
  46. package/templates/typescript-interop/package.json +0 -22
  47. package/templates/typescript-interop/src/main.ts +0 -8
  48. package/templates/typescript-interop/src/person.tsi +0 -11
  49. package/templates/typescript-interop/tsconfig.json +0 -12
@@ -3,8 +3,8 @@
3
3
  ## The feature-extraction project: one .tsi file is the program
4
4
 
5
5
  The smallest Nola program is a single `.tsi` file run directly — `ask` is
6
- legal at the top level, a bare template literal as the FIRST statement is
7
- the instruction for the whole file, and `const .x` bindings are context the
6
+ legal at the top level, a bare template literal on its own line is a context
7
+ statement every ask below it sees, and `const .x` bindings are context the
8
8
  model sees at every ask that follows them (`npm create nola` scaffolds this
9
9
  shape as `feature-extraction`):
10
10
 
@@ -21,17 +21,17 @@ interface Person {
21
21
 
22
22
  const .message = "Alice Smith, 32, is a staff engineer at Acme Corp working on distributed systems.";
23
23
 
24
- const person = ask `the person described in the text`<Person>;
24
+ const person = ask `the person described in the text`: Person;
25
25
 
26
26
  // declared after the first ask, so only the second ask sees it — one answer feeds the next
27
27
  const .role = person.job;
28
- const seniority = ask `the seniority level the role implies`<"junior" | "mid" | "senior" | "staff">;
28
+ const seniority = ask `the seniority level the role implies`: "junior" | "mid" | "senior" | "staff";
29
29
 
30
30
  console.log(JSON.stringify({ ...person, seniority }));
31
31
  ```
32
32
 
33
- Rules that matter here: the instruction literal must be the very first
34
- statement (a comment before it is fine, an `import` or a type is not); a
33
+ Rules that matter here: a context statement applies to the asks written
34
+ after it, wherever it stands (an `import` or a type before it is fine); a
35
35
  `.` binding is visible to the asks declared after it in the same or an
36
36
  enclosing block, never to its own initializer; `ask` at the top level is
37
37
  legal in the module body and top-level blocks/loops, not inside a plain
@@ -63,16 +63,19 @@ import { createTicket } from "./tickets.js";
63
63
 
64
64
  const .message = "Hi, I can't log in since this morning and I have a customer demo in an hour — please help!";
65
65
 
66
- const ticket = ask createTicket(..`a short ticket title`<string>, ..`priority 1-5, where 1 is most urgent`<number>);
66
+ const ticket = ask createTicket(
67
+ `a short ticket title`: string,
68
+ `priority 1-5, where 1 is most urgent`: number
69
+ );
67
70
 
68
71
  console.log(JSON.stringify(ticket));
69
72
  ```
70
73
 
71
74
  `ask` yields the callee's SETTLED value — a `Ticket`, not a `Promise<Ticket>`.
72
75
  The import uses the NodeNext `./tickets.js` specifier for the on-disk
73
- `tickets.ts`. Note there is no first-line instruction here: an `import` is a
74
- statement, so a template literal placed after it is not the file's first
75
- statement and would be a no-op.
76
+ `tickets.ts`. Note there is no context statement here: a template literal on
77
+ its own line after the `import` would be one, and every ask below it would
78
+ see it.
76
79
 
77
80
  ## The typescript-interop project, end to end
78
81
 
@@ -102,7 +105,7 @@ export interface Person {
102
105
  }
103
106
 
104
107
  export infer function extractPerson(.message: string) {
105
- const person = ask `the person described in the text`<Person>;
108
+ const person = ask `the person described in the text`: Person;
106
109
  return person;
107
110
  }
108
111
  ```
@@ -129,8 +132,8 @@ nola build # dist/ — plain JS + source maps + .d.ts
129
132
 
130
133
  ## Composing several asks in one invocation
131
134
 
132
- Every `ask` in one invocation shares that invocation's context — the `..`
133
- contextual parameters and the function's instruction marker. That is what lets
135
+ Every `ask` in one invocation shares that invocation's context — the `.`
136
+ contextual parameters and the function's context statements. That is what lets
134
137
  you split one big prompt into several small, individually-typed asks instead of
135
138
  demanding everything at once.
136
139
 
@@ -145,7 +148,7 @@ export infer function solve(.problem: string) {
145
148
  // other's answers automatically — the reasoning is handed to the second ask
146
149
  // explicitly through `${}`.
147
150
  const reasoning = ask `think step by step about the problem before answering`;
148
- const answer = ask `the final numeric answer, given this reasoning: ${reasoning}`<number>;
151
+ const answer = ask `the final numeric answer, given this reasoning: ${reasoning}`: number;
149
152
  return { reasoning, answer };
150
153
  }
151
154
  ```
@@ -158,8 +161,8 @@ later prompt:
158
161
  export type Category = "billing" | "refund" | "fraud" | "other";
159
162
 
160
163
  export infer function classifyMessage(.message: string) {
161
- const category = ask `the category of the customer message`<Category>;
162
- const urgent = ask `does the message need urgent attention`<"yes" | "no">;
164
+ const category = ask `the category of the customer message`: Category;
165
+ const urgent = ask `does the message need urgent attention`: "yes" | "no";
163
166
 
164
167
  // plain TS from here on
165
168
  if (category === "fraud") return { category, urgent: true, escalate: true };
@@ -178,11 +181,11 @@ export interface Conclusion {
178
181
  }
179
182
 
180
183
  export infer function nextQuery(.question: string, .notes: string[]) {
181
- return ask `the single best search query to advance the research; keywords only`<string>;
184
+ return ask `the single best search query to advance the research; keywords only`: string;
182
185
  }
183
186
 
184
187
  export infer function conclude(.question: string, .notes: string[]) {
185
- return ask `answer the research question using only the collected notes`<Conclusion>;
188
+ return ask `answer the research question using only the collected notes`: Conclusion;
186
189
  }
187
190
  ```
188
191
 
@@ -228,7 +231,7 @@ import { createTicket } from "./tickets.js";
228
231
  export infer function fileTicket(.request: string) {
229
232
  // Sigil-less: the extractor argument makes this call an intent. `2` is a
230
233
  // plain argument and is passed through untouched.
231
- const id = ask createTicket(..`a short ticket title for the request`<string>, 2);
234
+ const id = ask createTicket(`a short ticket title for the request`: string, 2);
232
235
  return id;
233
236
  }
234
237
  ```
@@ -239,14 +242,15 @@ spelling that carries a hint:
239
242
  ```tsi
240
243
  export infer function fileTicketCarefully(.request: string) {
241
244
  return ask createTicket`file the ticket exactly as the customer described it`(
242
- ..`a short ticket title`<string>,
243
- ..`priority 1-5, where 1 is most urgent`<number>,
245
+ `a short ticket title`: string,
246
+ `priority 1-5, where 1 is most urgent`: number,
244
247
  );
245
248
  }
246
249
  ```
247
250
 
248
- Every extractor slot needs an explicit `<T>`, and all slots of one call
249
- resolve together in a single provider round trip.
251
+ Every extractor slot needs an explicit type (an untyped template in the
252
+ arguments is a plain string), and all slots of one call resolve together in a
253
+ single provider round trip.
250
254
 
251
255
  `createTicket` is async, but `id` is a `string`, not a `Promise<string>` — a
252
256
  call intent awaits a promise-returning callee itself (`ask` ≈ `await`), so
@@ -254,9 +258,41 @@ call intent awaits a promise-returning callee itself (`ask` ≈ `await`), so
254
258
  ask, `.withRetry(n)` on a call intent re-invokes it on failure — only use it
255
259
  when the target is idempotent.
256
260
 
261
+ ## An agent loop with a live context statement
262
+
263
+ A context statement is read at EACH ask that sees it, so one placed before a
264
+ loop shows the model the current state on every pass — no re-prompting code:
265
+
266
+ ```tsi
267
+ type Problem = { brief: string; solved: boolean };
268
+
269
+ export infer function solveAll(.query: string) {
270
+ `Solve every problem in the query, one at a time.`
271
+ const solved: Problem[] = [];
272
+ `Already solved:` solved.map((p) => p.brief);
273
+ while (solved.length < 5) {
274
+ const next = ask `the next unsolved problem, or a problem with solved: true when none is left`: Problem;
275
+ if (next.solved) break;
276
+ solved.push(next);
277
+ }
278
+ return solved;
279
+ }
280
+ ```
281
+
282
+ Every `ask` inside the loop sees `Already solved: ["…", "…"]` with the list as
283
+ it is at that moment. The value is your words (JSON), not an `<input>` block;
284
+ use `const .solved` instead if the model should treat it as data.
285
+
286
+ The statement is rendered once per ask, not once per pass: five passes do not
287
+ put five copies in front of the model, and the model never sees a history of
288
+ earlier renderings. A statement written INSIDE the loop body is scoped to
289
+ those braces like a `const` — only the asks inside them see it, and an ask
290
+ after the loop does not. What changes from pass to pass is the value a
291
+ statement reads, never the set of statements.
292
+
257
293
  ## Typing the answers
258
294
 
259
- Prefer a NAMED, exported `type` or `interface` for `<T>` over an inline object
295
+ Prefer a NAMED, exported `type` or `interface` as the extractor's type over an inline object
260
296
  literal: it documents the contract, it is reusable from plain TS, and JSDoc
261
297
  comments on its members become descriptions in the schema the LLM sees.
262
298
 
@@ -279,7 +315,7 @@ export interface Invoice {
279
315
  }
280
316
 
281
317
  export infer function extractInvoice(.document: string) {
282
- return ask `the invoice data from the document`<Invoice>;
318
+ return ask `the invoice data from the document`: Invoice;
283
319
  }
284
320
  ```
285
321
 
@@ -296,9 +332,9 @@ export enum Sentiment {
296
332
  }
297
333
 
298
334
  export infer function triage(.message: string) {
299
- const category = ask `the category of the customer message`<Category>;
300
- const sentiment = ask `the overall sentiment of the message`<Sentiment>;
301
- const urgent = ask `does the message need urgent attention`<"yes" | "no">;
335
+ const category = ask `the category of the customer message`: Category;
336
+ const sentiment = ask `the overall sentiment of the message`: Sentiment;
337
+ const urgent = ask `does the message need urgent attention`: "yes" | "no";
302
338
  return { category, sentiment, urgent: urgent === "yes" };
303
339
  }
304
340
  ```
@@ -323,7 +359,7 @@ export interface Person {
323
359
  import type { Person } from "./models.js";
324
360
 
325
361
  export infer function extractPerson(.text: string) {
326
- return ask `the person described in the text`<Person>;
362
+ return ask `the person described in the text`: Person;
327
363
  }
328
364
  ```
329
365
 
@@ -347,7 +383,7 @@ export type TreeNode = {
347
383
  };
348
384
 
349
385
  export infer function parseTree(.input: string) {
350
- return ask `the tree structure described in the input`<TreeNode>;
386
+ return ask `the tree structure described in the input`: TreeNode;
351
387
  }
352
388
  ```
353
389
 
@@ -357,7 +393,7 @@ export infer function parseTree(.input: string) {
357
393
  export type CalendarEvent = { title: string; at: Date };
358
394
 
359
395
  export infer function nextEvent(.calendar: string) {
360
- const event = ask `the next event on the calendar`<CalendarEvent>;
396
+ const event = ask `the next event on the calendar`: CalendarEvent;
361
397
  const when: Date = event.at; // a Date, not a string
362
398
  return when;
363
399
  }
@@ -15,14 +15,14 @@ a parenthesized expression will not parse.
15
15
  ```tsi
16
16
  export infer function summarize(.text: string, useFast: boolean) {
17
17
  // WRONG
18
- const a = ask with "fast" `a rough summary`<string>;
19
- const b = ask with provider.fast `a rough summary`<string>;
18
+ const a = ask with "fast" `a rough summary`: string;
19
+ const b = ask with provider.fast `a rough summary`: string;
20
20
 
21
21
  // RIGHT — name it in nola.config.ts, then use that name
22
- const c = ask with fast `a rough summary`<string>;
22
+ const c = ask with fast `a rough summary`: string;
23
23
 
24
24
  // RIGHT — dynamic choice
25
- const d = ask (..`a rough summary`<string>).withModel(useFast ? "fast" : "careful");
25
+ const d = ask (..`a rough summary`: string).withModel(useFast ? "fast" : "careful");
26
26
 
27
27
  return { a, b, c, d };
28
28
  }
@@ -53,28 +53,27 @@ function summarize(.text: string) {
53
53
 
54
54
  // RIGHT
55
55
  export infer function summarize(.text: string) {
56
- return ask `a one-sentence summary`<string>;
56
+ return ask `a one-sentence summary`: string;
57
57
  }
58
58
  ```
59
59
 
60
60
  If the function is genuinely plain TypeScript, drop the `.`; the parameter is
61
61
  an ordinary argument.
62
62
 
63
- ## NOLA2013 — marker and body instruction together
63
+ ## NOLA1019 — the marker slot is reserved
64
64
 
65
- > this infer function already has an instruction marker — write the instruction in one place.
65
+ > the marker after an infer function's name is reserved for a future Nola version — write the instruction as a context statement in the body: `…`.
66
66
 
67
67
  ```tsi
68
- // WRONG — two spellings of the same instruction
68
+ // WRONG — the instruction in the reserved slot
69
69
  export infer function f`be terse`(.t: string) {
70
- `be terse`
71
- return ask `the kind`<string>;
70
+ return ask `the kind`: string;
72
71
  }
73
72
 
74
- // RIGHT — one or the other
73
+ // RIGHT — a context statement in the body
75
74
  export infer function f(.t: string) {
76
75
  `be terse`
77
- return ask `the kind`<string>;
76
+ return ask `the kind`: string;
78
77
  }
79
78
  ```
80
79
 
@@ -89,16 +88,16 @@ infer function — nor in a class field initializer or `static` block.
89
88
 
90
89
  ```tsi
91
90
  // WRONG
92
- const g = async () => ask `the kind`<string>; // plain closure
91
+ const g = async () => ask `the kind`: string; // plain closure
93
92
 
94
93
  export infer function f(.t: string) {
95
- const h = () => ask `the kind`<string>; // nested closure
94
+ const h = () => ask `the kind`: string; // nested closure
96
95
  return h();
97
96
  }
98
97
 
99
98
  // RIGHT — ask directly in the body, or at the top level of the module
100
99
  export infer function f(.t: string) {
101
- return ask `the kind`<string>;
100
+ return ask `the kind`: string;
102
101
  }
103
102
  export const kind = ask f("…");
104
103
  ```
@@ -107,32 +106,158 @@ Constructing an extractor outside a body is fine — only resolving it is
107
106
  restricted:
108
107
 
109
108
  ```tsi
110
- export const nameIntent = ..`the user's full name`<string>; // legal, inert
109
+ export const nameIntent = ..`the user's full name`: string; // legal, inert
111
110
  ```
112
111
 
113
- ## NOLA2014 — a typed template outside `ask` needs the dots
112
+ ## NOLA2014 — a typed template outside `ask` and a call's slots needs the dots
114
113
 
115
- > a typed template literal is an extractor only directly after `ask`; write ..`…`<T> here.
114
+ > a typed template literal is an extractor only directly after `ask` or in a call's argument list; write ..`…`<T> here.
116
115
 
117
- The `..` is implied only directly after `ask`. Anywhere else a bare template
118
- is an ordinary string, so an extractor there needs its sigil:
116
+ The `..` is implied directly after `ask` and in a call's slots (an argument,
117
+ or a value nested in plain object/array literals there). Anywhere else a bare
118
+ template is an ordinary string, so an extractor there needs its sigil. The
119
+ code is raised for the angle-bracket spelling; a colon-typed template in such
120
+ a place (`` const stored = `the user's full name`: string ``) is a plain
121
+ syntax error instead:
119
122
 
120
123
  ```tsi
121
124
  declare function createTicket(title: string): Promise<string>;
122
125
 
123
126
  infer function file(.request: string) {
124
- const wrong = ask createTicket(`a short title`<string>); // NOLA2014
125
- const right = ask createTicket(..`a short title`<string>);
127
+ const wrong = `a short title`<string>; // NOLA2014 — stored: dots required
128
+ const right = ask createTicket(`a short title`: string); // a slot: the dots are implied
126
129
  return right;
127
130
  }
128
- export const stored = ..`the user's full name`<string>; // stored: dots required
131
+ export const stored = ..`the user's full name`: string; // stored: dots required
132
+ ```
133
+
134
+ ## NOLA1018 — a colon type with nothing on its line
135
+
136
+ > expected a type after the extractor's `:` on the same line — write `…`: T.
137
+
138
+ The colon spelling `` ask `the ticket id`: string `` needs the colon glued to
139
+ the backtick (a colon after a space is a ternary's, never the extractor's) and
140
+ the type on the colon's line. Two more traps come with it, both from the type
141
+ having no closing delimiter:
142
+
143
+ ```tsi
144
+ interface User { name: string }
145
+
146
+ infer function f(.doc: string) {
147
+ const a = ask `the user`: User.withRetry(2); // the TYPE `User.withRetry`, called with 2 — TS error
148
+ const b = ask `the user`<User>.withRetry(2); // RIGHT
149
+ const c = ask `the tags`: string[].withRetry(2); // RIGHT — a delimited type ends the type
150
+ return { b, c };
151
+ }
152
+ ```
153
+
154
+ A bare type name followed by `.` continues as a qualified type name, and `<`
155
+ after a type name opens type arguments (`` `n`: User < 5 `` is not a
156
+ comparison). Chain intent methods after a parenthesized extractor
157
+ (`` ask (..`the user`: User).withRetry(2) ``), after a delimited type
158
+ (`string[]`, `Array<User>`, `{…}`, `(User)`), or after `<T>`.
159
+
160
+ ## NOLA1020 — a value in a context statement must be simple
161
+
162
+ > a value in a context statement is a name, a call or a bracketed expression,
163
+ > followed by text or the end of the statement — wrap anything else in
164
+ > parentheses, or assign it to a local first (`await`, `ask` and `this` cannot
165
+ > be values).
166
+
167
+ ```tsi
168
+ export infer function total(.items: string[], a: number, b: number) {
169
+ // WRONG — an operator after a value
170
+ `total` a + b `items`;
171
+ // RIGHT
172
+ `total` (a + b) `items`;
173
+ return ask `a summary`: string;
174
+ }
175
+ ```
176
+
177
+ `this` is the same kind of mistake: an item is a hoisted function of its own,
178
+ so its `this` is not the infer function's. A bare `this` after text is
179
+ NOLA1020; in parentheses or in a `${}` hole it is TS2683 in `nola check` and
180
+ the editor (and `undefined` at run time). Assign it to a local first:
181
+
182
+ ```tsi
183
+ export infer function greet(this: { user: string }) {
184
+ // WRONG — NOLA1020 (bare `this`); `(this.user)` and `${this.user}` are TS2683
185
+ `Greet` this.user `warmly.`;
186
+ // RIGHT — a local, then its name
187
+ const user = this.user;
188
+ `Greet` user `warmly.`;
189
+ return ask `a one-line greeting`: string;
190
+ }
129
191
  ```
130
192
 
193
+ Also:
194
+
195
+ - A value never ends with a backtick: `` `x` foo<string> `more` `` and
196
+ `` `x` new Foo `more` `` are NOLA1020 too (the text would be read as a
197
+ tagged template) — write `(foo<string>)`, `(new Foo)` or `new Foo()`.
198
+ - `typeof`, `void`, `await`, `ask`, `this`, `..`, `function` and `class`
199
+ right after text are NOLA1020. Parenthesize `typeof`, `void`, functions and
200
+ classes. `await`, `ask` and `this` cannot be values even in parentheses (an
201
+ item is read at each ask, outside the async body, in a function of its own:
202
+ `(await x)` is TS1308, `(this.x)` is TS2683, `(ask …)` is NOLA2010) —
203
+ compute the value into a `const` first and write its name.
204
+ - A NAME at the start of a line begins a new statement (JavaScript's own
205
+ rule), so `` `analyze` `` on one line and `foo(x)` on the next are a context
206
+ statement and a call. Keep values on the text's line, or start the line with
207
+ `(` or `[`.
208
+ - The mirror image: a CODE line that starts with `[` or `(` right after a
209
+ context statement continues it and becomes its value — no error, but
210
+ whatever the code returns (`undefined` for a `forEach`) lands in the
211
+ prompt, and the code runs at every ask. End the context statement with
212
+ `;`.
213
+
214
+ ## An operator after the text is not a context statement
215
+
216
+ No diagnostic: `` `Page ` + oncall `` is ordinary JavaScript — a string
217
+ concatenation whose result is dropped — so the model never sees it.
218
+
219
+ ```tsi
220
+ export infer function page(.ticket: string, oncall: string) {
221
+ // WRONG — plain concatenation, nothing reaches the model
222
+ `Page ` + oncall + ` when the ticket is an outage.`;
223
+ // RIGHT — juxtapose the parts, or use a hole
224
+ `Page` oncall `when the ticket is an outage.`;
225
+ `Page ${oncall} when the ticket is an outage.`;
226
+ return ask `the severity`: string;
227
+ }
228
+ ```
229
+
230
+ ## NOLA2017 — a context statement outside a scope body
231
+
232
+ > a context statement is only legal in a scope body — directly in an infer
233
+ > function or at module level.
234
+
235
+ > a context statement cannot be the unbraced body of an `if`, a loop or a
236
+ > label — wrap it in braces.
237
+
238
+ ```tsi
239
+ export infer function go(.items: string[], urgent: boolean) {
240
+ // WRONG — inside a callback, no ask can carry it
241
+ items.forEach((it) => {
242
+ `note` it;
243
+ });
244
+ // WRONG — an unbraced body
245
+ if (urgent) `Answer quickly.`;
246
+ // RIGHT — directly in the body, before the asks that should see it
247
+ `items:` items;
248
+ return ask `a summary`: string;
249
+ }
250
+ ```
251
+
252
+ A class `static` block and a namespace body are outside a scope body too.
253
+ Inside braces (`if (urgent) { … }`) a context statement is legal and applies
254
+ to the asks inside them.
255
+
131
256
  ## NOLA2002 — a type the compiler cannot turn into a schema
132
257
 
133
258
  > unsupported type for intent schema: …
134
259
 
135
- An extractor's `<T>` must RESOLVE to a JSON-shaped value — the TypeScript
260
+ An extractor's type must RESOLVE to a JSON-shaped value — the TypeScript
136
261
  checker decides, so unions (`Refund | Chargeback`, `string | null`),
137
262
  `Partial<T>` / `Pick` / `Omit`, `interface … extends`, intersections,
138
263
  `Record<string, T>`, tuples, generics applied with arguments and types from
@@ -143,16 +268,16 @@ functions and a generic declaration used without arguments (`Box<T>` — write
143
268
  ```tsi
144
269
  export infer function tally(.doc: string) {
145
270
  // WRONG
146
- const wrong = ask `counts per label`<Map<string, number>>;
271
+ const wrong = ask `counts per label`: Map<string, number>;
147
272
 
148
273
  // RIGHT — a JSON-shaped type; convert afterwards in plain TS
149
- const counts = ask `counts per label`<{ label: string; count: number }[]>;
274
+ const counts = ask `counts per label`: { label: string; count: number }[];
150
275
  const asMap = new Map(counts.map((c) => [c.label, c.count]));
151
276
  return asMap;
152
277
  }
153
278
  ```
154
279
 
155
- Always give an extractor a concrete `<T>` in an expression position. An
280
+ Always give an extractor a concrete type in an expression position. An
156
281
  extractor is a value-producing expression; it is not a statement, a type, or a
157
282
  declaration.
158
283
 
@@ -169,18 +294,18 @@ values are serialized into the prompt.
169
294
  ```tsi
170
295
  // WRONG
171
296
  export infer function topLabel(.index: Map<string, number>) {
172
- return ask `the label with the highest count`<string>;
297
+ return ask `the label with the highest count`: string;
173
298
  }
174
299
 
175
300
  // RIGHT — pass a JSON-shaped view as the contextual parameter
176
301
  export infer function topLabel(.index: { label: string; count: number }[]) {
177
- return ask `the label with the highest count`<string>;
302
+ return ask `the label with the highest count`: string;
178
303
  }
179
304
 
180
305
  // RIGHT — keep the exotic value, but as a PLAIN parameter (the LLM never
181
306
  // sees its value, so nothing needs deriving)
182
307
  export infer function topLabel(.summary: string, index: Map<string, number>) {
183
- const label = ask `the label with the highest count`<string>;
308
+ const label = ask `the label with the highest count`: string;
184
309
  return { label, count: index.get(label) ?? 0 };
185
310
  }
186
311
  ```
@@ -199,9 +324,9 @@ export default defineConfig({
199
324
  Keep that value a literal — the editor reads it statically and never executes
200
325
  your config.
201
326
 
202
- ## NOLA2004 — a call-intent slot with no `<T>`
327
+ ## NOLA2004 — a call-intent slot with no type
203
328
 
204
- > an extractor used as a call-intent argument must have an explicit `<T>`.
329
+ > an extractor used as a call-intent argument must have an explicit type — write ..`…`: T here.
205
330
 
206
331
  ```tsi
207
332
  declare function createTicket(title: string, priority: number): Promise<string>;
@@ -210,12 +335,16 @@ export infer function fileTicket(.request: string) {
210
335
  // WRONG — the slot has no type
211
336
  const wrong = ask createTicket(..`a short ticket title`, 2);
212
337
 
213
- // RIGHT
214
- const id = ask createTicket(..`a short ticket title`<string>, 2);
338
+ // RIGHT (the slot's `..` is implied; `..`a short ticket title`: string` works too)
339
+ const id = ask createTicket(`a short ticket title`: string, 2);
215
340
  return id;
216
341
  }
217
342
  ```
218
343
 
344
+ A bare template with no type, `` createTicket(`a short ticket title`, 2) ``,
345
+ is not a slot at all: it is a plain string argument and the call stays an
346
+ ordinary call — no error, and no model request.
347
+
219
348
  ## NOLA3010 — bare `await` on a raw extract or call intent
220
349
 
221
350
  > extract/call intents carry no construction scope — only `ask` supplies their
@@ -285,7 +414,7 @@ import { Person } from "./models.js"; // WRONG — kept at run time; mod
285
414
 
286
415
  `__nola` and any identifier starting with `__nola` are reserved in `.tsi`.
287
416
  `__nola.ask(...)`, `__nola.intents.ExtractIntent(...)`, `__nola.types.string()`
288
- and `__nola_file_ctx()` are what the compiler EMITS — they are not an API you
417
+ and `__nola_module_ctx()` are what the compiler EMITS — they are not an API you
289
418
  call, and writing them by hand is an error.
290
419
 
291
420
  ```tsi
@@ -297,28 +426,44 @@ export infer function read(.doc: string) {
297
426
  );
298
427
 
299
428
  // RIGHT
300
- const v = ask `the value`<string>;
429
+ const v = ask `the value`: string;
301
430
  return v;
302
431
  }
303
432
  ```
304
433
 
305
- ## NOLA2009 — `${.member}` outside a Nola instruction
306
-
307
- `${.x}` is prompt-scope access and only means something inside an
308
- infer-function marker, an extractor's backticks, or a call-intent hint. In a
309
- plain template literal it is an error — use a lexical value there.
310
-
311
- ## NOLA2010 — a Nola construct inside a marker / call-hint hole
434
+ ## NOLA2010 — a Nola construct inside a context statement or a hole
435
+
436
+ > Nola constructs are not allowed inside a context statement or an instruction
437
+ > literal's hole; a hinted call intent used as a value must be parenthesized.
438
+
439
+ A context statement is read at each ask, outside the async body, and call
440
+ hints are re-emitted from source, so `ask` and `..` cannot appear in a context
441
+ statement's parenthesized value, bracketed value or `${}` hole, nor in a call
442
+ hint's hole (written bare right after the text, they are NOLA1020). A call
443
+ intent is a legal value, but a HINTED one, `` fn`hint`(…) ``, must be
444
+ parenthesized or written in a `${}` hole (an empty marker too): bare, the
445
+ backtick after `fn` is read as the next text part and the arguments as a
446
+ value of their own, so it is no call intent — a `` ..`x`: T `` in the
447
+ arguments is left as a bare extractor, and that is this error (a `` `x`: T ``
448
+ there is a syntax error at its colon, since a parenthesized group has no
449
+ slots); plain arguments may raise no error at all and render the wrong text.
450
+ A sigil-less call intent (`` fn(`x`: T) ``) needs no parentheses. Otherwise
451
+ compute the value first and interpolate the result, or move the ask into the
452
+ function body.
312
453
 
313
- Marker and call-hint literals are re-emitted from source, so `..`, call
314
- intents and `ask` cannot appear in their holes. Compute the value first and
315
- interpolate the result, or move the ask into the function body.
316
-
317
- ## NOLA3014 — a prompt template rendered nothing (or threw)
318
-
319
- A `${.member}` template must produce text. An empty result usually means the
320
- template only read members that were undefined; a throw is a bug in the
321
- template's own JS. Fixed at the template — there is no retry.
454
+ ```tsi
455
+ declare function escalate(team: string): void;
456
+
457
+ export infer function route(.ticket: string) {
458
+ // WRONG — the hint's backtick starts the next text part: NOLA2010 at the extractor
459
+ `If the ticket is an outage,` escalate`page the on-call engineer`(..`the team to page`: string);
460
+ // RIGHT — parenthesized (or written in a `${}` hole)
461
+ `If the ticket is an outage,` (escalate`page the on-call engineer`(`the team to page`: string));
462
+ // RIGHT — no hint, no parentheses
463
+ `If the ticket is an outage,` escalate(`the team to page`: string);
464
+ return ask `the label for the ticket`: string;
465
+ }
466
+ ```
322
467
 
323
468
  ## NOLA3015 — running `.tsi` under Bun or Deno
324
469
 
@@ -336,3 +481,6 @@ the `nola` bin is a Node script), but `bun --bun`, `bun src/main.ts` and
336
481
  - `infer` on a method, arrow function or function expression — top-level
337
482
  function declarations only.
338
483
  - `fn(..)` — the bare derive-all call form is reserved (NOLA1004).
484
+ - `` ask `p` : T `` — a colon after a space is not the extractor's type; only a
485
+ colon glued to the closing backtick is (`` `p`: T ``). In a ternary the
486
+ spaced colon is the separator.