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.
- package/README.md +8 -3
- package/dist/{chunk-LUSVIOAN.js → chunk-CB26Z47Y.js} +53 -87
- package/dist/examples.d.ts +4 -2
- package/dist/examples.js +4 -2
- package/dist/flow.d.ts +8 -2
- package/dist/flow.js +15 -12
- package/dist/github.d.ts +1 -1
- package/dist/github.js +1 -1
- package/dist/index.js +1 -1
- package/dist/launch.d.ts +2 -2
- package/dist/launch.js +1 -1
- package/dist/main.js +1 -1
- package/dist/node-version.d.ts +3 -2
- package/dist/node-version.js +4 -3
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/registry.d.ts +19 -10
- package/dist/registry.js +20 -5
- package/dist/scaffold.d.ts +19 -15
- package/dist/scaffold.js +63 -113
- package/package.json +1 -1
- package/skills/nola/SKILL.md +48 -16
- package/skills/nola/references/config.md +5 -5
- package/skills/nola/references/patterns.md +67 -31
- package/skills/nola/references/pitfalls.md +200 -52
- package/skills/nola/references/syntax.md +221 -122
- package/templates/empty/package.json +5 -5
- package/templates/empty/src/main.tsi +8 -0
- package/templates/empty/src/main.ts +0 -5
- package/templates/feature-extraction/README.md +0 -20
- package/templates/feature-extraction/nola.config.ts +0 -14
- package/templates/feature-extraction/nola.replay.jsonl +0 -2
- package/templates/feature-extraction/package.json +0 -22
- package/templates/feature-extraction/src/main.tsi +0 -22
- package/templates/feature-extraction/tsconfig.json +0 -12
- package/templates/function-calling/README.md +0 -21
- package/templates/function-calling/nola.config.ts +0 -14
- package/templates/function-calling/nola.replay.jsonl +0 -1
- package/templates/function-calling/package.json +0 -22
- package/templates/function-calling/src/main.tsi +0 -12
- package/templates/function-calling/src/tickets.ts +0 -17
- package/templates/function-calling/tsconfig.json +0 -12
- package/templates/typescript-interop/README.md +0 -15
- package/templates/typescript-interop/nola.config.ts +0 -14
- package/templates/typescript-interop/nola.replay.jsonl +0 -1
- package/templates/typescript-interop/package.json +0 -22
- package/templates/typescript-interop/src/main.ts +0 -8
- package/templates/typescript-interop/src/person.tsi +0 -11
- 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
|
|
7
|
-
|
|
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
|
|
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
|
|
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:
|
|
34
|
-
|
|
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(
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
162
|
-
const urgent = ask `does the message need urgent attention
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
243
|
-
|
|
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
|
|
249
|
-
resolve together in a
|
|
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`
|
|
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
|
|
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
|
|
300
|
-
const sentiment = ask `the overall sentiment of the message
|
|
301
|
-
const urgent = ask `does the message need urgent attention
|
|
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
|
|
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
|
|
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
|
|
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
|
|
19
|
-
const b = ask with provider.fast `a rough summary
|
|
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
|
|
22
|
+
const c = ask with fast `a rough summary`: string;
|
|
23
23
|
|
|
24
24
|
// RIGHT — dynamic choice
|
|
25
|
-
const d = ask (..`a rough summary
|
|
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
|
|
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
|
-
##
|
|
63
|
+
## NOLA1019 — the marker slot is reserved
|
|
64
64
|
|
|
65
|
-
>
|
|
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 —
|
|
68
|
+
// WRONG — the instruction in the reserved slot
|
|
69
69
|
export infer function f`be terse`(.t: string) {
|
|
70
|
-
`
|
|
71
|
-
return ask `the kind`<string>;
|
|
70
|
+
return ask `the kind`: string;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
// RIGHT —
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
118
|
-
|
|
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 =
|
|
125
|
-
const right = ask createTicket(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
327
|
+
## NOLA2004 — a call-intent slot with no type
|
|
203
328
|
|
|
204
|
-
> an extractor used as a call-intent argument must have an explicit
|
|
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(
|
|
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 `
|
|
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
|
|
429
|
+
const v = ask `the value`: string;
|
|
301
430
|
return v;
|
|
302
431
|
}
|
|
303
432
|
```
|
|
304
433
|
|
|
305
|
-
##
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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.
|