@nola-lang/typescript-plugin 0.1.0-alpha.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.cjs CHANGED
@@ -50,7 +50,7 @@ var require_main = __commonJS({
50
50
  __export(node_exports, {
51
51
  analyzeMetafile: () => analyzeMetafile,
52
52
  analyzeMetafileSync: () => analyzeMetafileSync,
53
- build: () => build,
53
+ build: () => build2,
54
54
  buildSync: () => buildSync,
55
55
  context: () => context,
56
56
  default: () => node_default,
@@ -59,7 +59,7 @@ var require_main = __commonJS({
59
59
  initialize: () => initialize,
60
60
  stop: () => stop,
61
61
  transform: () => transform2,
62
- transformSync: () => transformSync2,
62
+ transformSync: () => transformSync,
63
63
  version: () => version
64
64
  });
65
65
  module2.exports = __toCommonJS(node_exports);
@@ -1883,7 +1883,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
1883
1883
  }
1884
1884
  };
1885
1885
  var version = "0.25.12";
1886
- var build = (options) => ensureServiceIsRunning().build(options);
1886
+ var build2 = (options) => ensureServiceIsRunning().build(options);
1887
1887
  var context = (buildOptions) => ensureServiceIsRunning().context(buildOptions);
1888
1888
  var transform2 = (input, options) => ensureServiceIsRunning().transform(input, options);
1889
1889
  var formatMessages = (messages, options) => ensureServiceIsRunning().formatMessages(messages, options);
@@ -1907,7 +1907,7 @@ More information: The file containing the code for esbuild's JavaScript API (${_
1907
1907
  }));
1908
1908
  return result;
1909
1909
  };
1910
- var transformSync2 = (input, options) => {
1910
+ var transformSync = (input, options) => {
1911
1911
  if (worker_threads && !isInternalWorkerThread) {
1912
1912
  if (!workerThreadService) workerThreadService = startWorkerThreadService(worker_threads);
1913
1913
  return workerThreadService.transformSync(input, options);
@@ -4547,14 +4547,21 @@ var Codes = {
4547
4547
  ExpectedProviderName: "NOLA1009",
4548
4548
  ContextualParamOutsideInfer: "NOLA1010",
4549
4549
  ContextualParamReserved: "NOLA1011",
4550
+ IncompleteContextualParam: "NOLA1012",
4551
+ ContextualParamDoubleDot: "NOLA1013",
4552
+ ContextualBindingReserved: "NOLA1014",
4553
+ IncompleteScopeAccess: "NOLA1015",
4550
4554
  AskOutsideNolaFunction: "NOLA2001",
4551
4555
  UnsupportedIntentType: "NOLA2002",
4552
4556
  NolaFnNotTopLevel: "NOLA2003",
4553
4557
  UntypedCallIntentArg: "NOLA2004",
4554
4558
  SubstitutionInCallMarker: "NOLA2005",
4559
+ // retired (emit 11): call-hint substitutions are legal; number not reused
4555
4560
  ReservedCompanionPath: "NOLA2006",
4556
4561
  CompanionUnavailable: "NOLA2007",
4557
4562
  UnderivableContextType: "NOLA2008",
4563
+ ScopeAccessOutsideTemplate: "NOLA2009",
4564
+ NolaConstructInMarker: "NOLA2010",
4558
4565
  // NOLA3xxx: runtime diagnostics
4559
4566
  EmitContractMismatch: "NOLA3001",
4560
4567
  DuplicateRuntimeConflict: "NOLA3002",
@@ -4566,7 +4573,12 @@ var Codes = {
4566
4573
  ReplayFingerprintMismatch: "NOLA3008",
4567
4574
  SchemaUnsupported: "NOLA3009",
4568
4575
  IntentWithoutContext: "NOLA3010",
4569
- IntentWithoutParentFrame: "NOLA3011"
4576
+ IntentWithoutParentFrame: "NOLA3011",
4577
+ ConfigImportsTsi: "NOLA3012",
4578
+ BrowserExecutionUnsupported: "NOLA3013",
4579
+ PromptTemplateFailed: "NOLA3014",
4580
+ // NOLA4xxx: bundler-integration errors (build-time, raised by @nola-lang/unplugin and friends)
4581
+ TsiInClientBundle: "NOLA4001"
4570
4582
  };
4571
4583
  function isNode(v) {
4572
4584
  return typeof v === "object" && v !== null && typeof v.type === "string" && typeof v.start === "number";
@@ -4592,6 +4604,17 @@ function programBody(ast) {
4592
4604
  const program = ast.type === "File" ? ast.program : ast;
4593
4605
  return program.type === "Program" ? program.body ?? [] : [];
4594
4606
  }
4607
+ function walk(root, visit) {
4608
+ const queue = [{ node: root, parent: null }];
4609
+ while (queue.length > 0) {
4610
+ const item = queue.shift();
4611
+ if (!item)
4612
+ break;
4613
+ visit(item.node, item.parent);
4614
+ for (const child of children(item.node))
4615
+ queue.push({ node: child, parent: item.node });
4616
+ }
4617
+ }
4595
4618
 
4596
4619
  // ../core/dist/errors.js
4597
4620
  var NolaConfigError = class extends Error {
@@ -4602,6 +4625,14 @@ var NolaConfigError = class extends Error {
4602
4625
  this.code = code2;
4603
4626
  }
4604
4627
  };
4628
+ var NolaIntentError = class extends Error {
4629
+ code;
4630
+ name = "NolaIntentError";
4631
+ constructor(message, code2) {
4632
+ super(message);
4633
+ this.code = code2;
4634
+ }
4635
+ };
4605
4636
  var NolaVersionError = class extends Error {
4606
4637
  code;
4607
4638
  details;
@@ -4639,7 +4670,7 @@ function redactError(error) {
4639
4670
  }
4640
4671
 
4641
4672
  // ../core/dist/index.js
4642
- var NOLA_EMIT = 10;
4673
+ var NOLA_EMIT = 11;
4643
4674
  function mergeProviderParams(base, patch) {
4644
4675
  if (!base || !patch)
4645
4676
  return patch ?? base;
@@ -9565,10 +9596,14 @@ var NolaErrors = ParseErrorEnum`nola`({
9565
9596
  NolaReservedConstruct: "NOLA1004: this Nola construct is reserved for a future Nola version.",
9566
9597
  NolaExpectedPromptTemplate: "NOLA1005: expected a template literal prompt after `..`.",
9567
9598
  NolaLegacyMarker: "NOLA1007: the `function name``()` form was removed \u2014 declare the function with `infer function`.",
9568
- NolaMarkerSubstitution: "NOLA1008: `${...}` substitutions are not allowed in a function marker.",
9599
+ // NOLA1008 (marker substitution) is retired since emit 11 the number is not reused.
9600
+ NolaIncompleteScopeAccess: "NOLA1015: incomplete scope access \u2014 write `${.member}`.",
9569
9601
  NolaExpectedProviderName: "NOLA1009: expected a provider name after `ask with` \u2014 for a dynamic provider use `.withProvider(...)` on the intent.",
9570
- NolaContextualOutsideInfer: "NOLA1010: `..` context parameters are only allowed on infer function parameters.",
9571
- NolaContextualParamReserved: "NOLA1011: `..` on this parameter form is reserved for a future Nola version \u2014 use a plain identifier parameter."
9602
+ NolaContextualOutsideInfer: "NOLA1010: `.` context parameters are only allowed on infer function parameters.",
9603
+ NolaContextualParamReserved: "NOLA1011: `.` on this parameter form is reserved for a future Nola version \u2014 use a plain identifier parameter.",
9604
+ NolaIncompleteContextualParam: "NOLA1012: incomplete `.` context parameter \u2014 write `.name`.",
9605
+ NolaContextualParamDoubleDot: "NOLA1013: contextual parameters take one dot \u2014 write `.name` (`..` is the extractor sigil).",
9606
+ NolaContextualBindingReserved: "NOLA1014: `.name` contextual bindings (`const .x = \u2026`) are reserved for a future Nola version."
9572
9607
  });
9573
9608
  var nola_default = (superClass) => class NolaParserMixin extends superClass {
9574
9609
  // Set for the duration of a single parseFunctionParams call when we are
@@ -9582,6 +9617,43 @@ var nola_default = (superClass) => class NolaParserMixin extends superClass {
9582
9617
  // True while parsing the parameter list of an infer function (save/restore
9583
9618
  // across nested function-expression params in default values).
9584
9619
  nolaInInferParams = false;
9620
+ // One entry per template literal being parsed (outermost first). A
9621
+ // `${.member}` hole marks EVERY enclosing literal, so an instruction site
9622
+ // learns about scope access at any nesting depth (inside a .map callback's
9623
+ // own template literal, say). Depth > 0 is what makes a leading dot in
9624
+ // expression position a scope access instead of a syntax error.
9625
+ nolaTemplateStack = [];
9626
+ parseTemplate(isTagged) {
9627
+ const entry = { scopeAccess: false };
9628
+ this.nolaTemplateStack.push(entry);
9629
+ try {
9630
+ const node = super.parseTemplate(isTagged);
9631
+ if (entry.scopeAccess)
9632
+ node.nolaHasScopeAccess = true;
9633
+ return node;
9634
+ } finally {
9635
+ this.nolaTemplateStack.pop();
9636
+ }
9637
+ }
9638
+ // `${.member}` — scope access. Only reached inside a template hole (see
9639
+ // parseExprAtom); `..` tokenizes as nolaDotDot, so an inner extractor in a
9640
+ // hole never lands here. Keyword members (`.default`) are legal: the
9641
+ // identifier is parsed liberally like any property name.
9642
+ nolaParseScopeAccess() {
9643
+ const node = this.startNode();
9644
+ const startLoc = this.state.startLoc;
9645
+ this.next();
9646
+ for (const t of this.nolaTemplateStack)
9647
+ t.scopeAccess = true;
9648
+ if (tokenIsKeywordOrIdentifier(this.state.type)) {
9649
+ node.property = this.parseIdentifier(true);
9650
+ } else {
9651
+ this.raise(NolaErrors.NolaIncompleteScopeAccess, startLoc);
9652
+ node.property = null;
9653
+ node.nolaError = true;
9654
+ }
9655
+ return this.finishNode(node, "NolaScopeAccess");
9656
+ }
9585
9657
  // `infer function` at statement / export position. `infer` tokenizes as the
9586
9658
  // keyword-like tt._infer, and we only claim it when the NEXT token is
9587
9659
  // `function` — every other use of `infer` (identifier, TS conditional-type
@@ -9634,7 +9706,27 @@ var nola_default = (superClass) => class NolaParserMixin extends superClass {
9634
9706
  }
9635
9707
  super.readToken_dot();
9636
9708
  }
9709
+ // A `.` standing where an extractor is being typed: consume it and hand
9710
+ // back the broken-extract placeholder (nolaError), which the lowering
9711
+ // replaces with an inert expression.
9712
+ nolaIncompleteExtract() {
9713
+ const node = this.startNode();
9714
+ const startLoc = this.state.startLoc;
9715
+ this.next();
9716
+ this.raise(NolaErrors.NolaExpectedPromptTemplate, startLoc);
9717
+ node.quasi = null;
9718
+ node.prompt = "";
9719
+ node.typeArgs = null;
9720
+ node.nolaError = true;
9721
+ return this.finishNode(node, "NolaExtractExpression");
9722
+ }
9637
9723
  parseExprAtom(refExpressionErrors) {
9724
+ if (this.match(tt.dot) && this.nolaTemplateStack.length > 0) {
9725
+ return this.nolaParseScopeAccess();
9726
+ }
9727
+ if (this.match(tt.dot) && this.optionFlags & 4096) {
9728
+ return this.nolaIncompleteExtract();
9729
+ }
9638
9730
  if (this.match(tt.nolaDotDot)) {
9639
9731
  const node = this.startNode();
9640
9732
  this.next();
@@ -9685,7 +9777,7 @@ var nola_default = (superClass) => class NolaParserMixin extends superClass {
9685
9777
  this.next();
9686
9778
  }
9687
9779
  }
9688
- node.argument = this.parseMaybeUnary(null, true);
9780
+ node.argument = this.match(tt.dot) ? this.nolaIncompleteExtract() : this.parseMaybeUnary(null, true);
9689
9781
  return this.finishNode(node, "NolaAskExpression");
9690
9782
  }
9691
9783
  return super.parseMaybeUnary(refExpressionErrors, sawUnary);
@@ -9724,11 +9816,14 @@ var nola_default = (superClass) => class NolaParserMixin extends superClass {
9724
9816
  if (!infer) {
9725
9817
  this.raise(NolaErrors.NolaLegacyMarker, markerStart);
9726
9818
  } else {
9727
- if (tmpl.expressions.length > 0) {
9728
- this.raise(NolaErrors.NolaMarkerSubstitution, markerStart);
9729
- }
9730
9819
  const instruction = tmpl.quasis.map((q) => q.value.cooked ?? q.value.raw).join("");
9731
- n2.nolaMarker = { start: markerStart, end: tmpl.end, instruction };
9820
+ n2.nolaMarker = {
9821
+ start: markerStart,
9822
+ end: tmpl.end,
9823
+ instruction,
9824
+ quasi: tmpl,
9825
+ hasScopeAccess: tmpl.nolaHasScopeAccess === true
9826
+ };
9732
9827
  }
9733
9828
  }
9734
9829
  const prevInInferParams = this.nolaInInferParams;
@@ -9739,15 +9834,27 @@ var nola_default = (superClass) => class NolaParserMixin extends superClass {
9739
9834
  this.nolaInInferParams = prevInInferParams;
9740
9835
  }
9741
9836
  }
9742
- // `..name` context parameter. Claimed only at function-param positions
9837
+ // `.name` context parameter. Claimed only at function-param positions
9743
9838
  // (IS_FUNCTION_PARAMS); array-pattern elements never see the flag. On a
9744
9839
  // non-infer function it is NOLA1010; on any non-identifier form (pattern,
9745
9840
  // default) it is NOLA1011 — both recover by parsing the element normally.
9841
+ // The retired `..name` spelling is NOLA1013 and otherwise behaves the same,
9842
+ // so tolerant mode gets one diagnostic instead of a bail.
9746
9843
  parseBindingElement(flags, decorators) {
9747
- if (this.match(tt.nolaDotDot) && flags & 2) {
9844
+ const doubled = this.match(tt.nolaDotDot);
9845
+ if ((doubled || this.match(tt.dot)) && flags & 2) {
9748
9846
  const span = { start: this.state.start, end: this.state.end };
9749
9847
  const startLoc = this.state.startLoc;
9750
9848
  this.next();
9849
+ if (doubled && this.nolaInInferParams)
9850
+ this.raise(NolaErrors.NolaContextualParamDoubleDot, startLoc);
9851
+ if (this.nolaInInferParams && (this.match(tt.parenR) || this.match(tt.comma))) {
9852
+ this.raise(NolaErrors.NolaIncompleteContextualParam, startLoc);
9853
+ const placeholder = this.startNodeAt(startLoc);
9854
+ placeholder.name = `__nola_incomplete_${span.start}`;
9855
+ placeholder.nolaError = true;
9856
+ return this.finishNode(placeholder, "Identifier");
9857
+ }
9751
9858
  const elt = super.parseBindingElement(flags, decorators);
9752
9859
  if (!this.nolaInInferParams) {
9753
9860
  this.raise(NolaErrors.NolaContextualOutsideInfer, startLoc);
@@ -9760,6 +9867,33 @@ var nola_default = (superClass) => class NolaParserMixin extends superClass {
9760
9867
  }
9761
9868
  return super.parseBindingElement(flags, decorators);
9762
9869
  }
9870
+ // `const .x` / `let .x` / `var .x` (either dot count): the contextual
9871
+ // BINDING form is reserved. Consume the marker, report, then parse the id
9872
+ // normally so tolerant mode keeps a sane tree; the span rides on the id so
9873
+ // the lowering can drop the bytes under a `broken` span.
9874
+ parseVarId(decl, kind) {
9875
+ if (this.match(tt.dot) || this.match(tt.nolaDotDot)) {
9876
+ const span = { start: this.state.start, end: this.state.end };
9877
+ const startLoc = this.state.startLoc;
9878
+ this.next();
9879
+ this.raise(NolaErrors.NolaContextualBindingReserved, startLoc);
9880
+ super.parseVarId(decl, kind);
9881
+ decl.id.nolaReservedMarker = span;
9882
+ return;
9883
+ }
9884
+ super.parseVarId(decl, kind);
9885
+ }
9886
+ // `let` is contextual: Babel promotes it to a declaration keyword only when
9887
+ // the next character can start a binding. A `.` after `let` can only be the
9888
+ // (reserved) contextual-binding marker in a module — `let` is not a legal
9889
+ // identifier there, so `let.x` member access is already an error — so let it
9890
+ // through to parseVarId, which reports NOLA1014. Scoped to the `let` token
9891
+ // so no other binding-start probe changes.
9892
+ chStartsBindingIdentifier(ch, pos) {
9893
+ if (ch === charCodes5.dot && this.state.type === tt._let)
9894
+ return true;
9895
+ return super.chStartsBindingIdentifier(ch, pos);
9896
+ }
9763
9897
  // Flag method context so parseFunctionParams can reject markers on object methods.
9764
9898
  parseMethod(node, isGenerator, isAsync, isConstructor, allowDirectSuper, type, inClassScope) {
9765
9899
  this.nolaInMethod = true;
@@ -20904,7 +21038,7 @@ var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
20904
21038
  function deriveTypeExpr(t, ctx) {
20905
21039
  const refs = /* @__PURE__ */ new Set();
20906
21040
  const companions = /* @__PURE__ */ new Map();
20907
- const result = walk(t, ctx, refs, companions);
21041
+ const result = walk2(t, ctx, refs, companions);
20908
21042
  return result.ok ? { ok: true, expr: result.expr, refs, companions } : result;
20909
21043
  }
20910
21044
  function collectTypeImports(ast) {
@@ -20949,7 +21083,7 @@ function buildAccessorPlan(entryRefs, ctx) {
20949
21083
  }
20950
21084
  return plan;
20951
21085
  }
20952
- function walk(t, ctx, refs, companions) {
21086
+ function walk2(t, ctx, refs, companions) {
20953
21087
  switch (t.type) {
20954
21088
  case "TSStringKeyword":
20955
21089
  return { ok: true, expr: "__nola.types.string()" };
@@ -20958,7 +21092,7 @@ function walk(t, ctx, refs, companions) {
20958
21092
  case "TSBooleanKeyword":
20959
21093
  return { ok: true, expr: "__nola.types.boolean()" };
20960
21094
  case "TSArrayType": {
20961
- const inner = walk(t.elementType, ctx, refs, companions);
21095
+ const inner = walk2(t.elementType, ctx, refs, companions);
20962
21096
  return inner.ok ? { ok: true, expr: `__nola.types.array(${inner.expr})` } : inner;
20963
21097
  }
20964
21098
  case "TSTypeLiteral":
@@ -21071,7 +21205,7 @@ function walkObject(members, ctx, refs, companions, owner) {
21071
21205
  continue;
21072
21206
  return { ok: false, message: `property '${name}' needs a type annotation`, node: member };
21073
21207
  }
21074
- const inner = walk(annotation, ctx, refs, companions);
21208
+ const inner = walk2(annotation, ctx, refs, companions);
21075
21209
  if (!inner.ok) {
21076
21210
  if (ctx.lossy)
21077
21211
  continue;
@@ -22371,17 +22505,17 @@ var SpanRecorder = class {
22371
22505
  constructor(source) {
22372
22506
  this.s = new MagicString(source);
22373
22507
  }
22374
- overwrite(start, end, text, anchors) {
22508
+ overwrite(start, end, text, options = {}) {
22375
22509
  this.s.overwrite(start, end, text);
22376
- this.edits.push({ sourceStart: start, sourceEnd: end, text, side: 0, seq: this.seq++, anchors });
22510
+ this.edits.push({ sourceStart: start, sourceEnd: end, text, side: 0, seq: this.seq++, ...options });
22377
22511
  }
22378
22512
  remove(start, end) {
22379
22513
  this.s.remove(start, end);
22380
22514
  this.edits.push({ sourceStart: start, sourceEnd: end, text: "", side: 0, seq: this.seq++ });
22381
22515
  }
22382
- appendLeft(pos, text) {
22516
+ appendLeft(pos, text, options = {}) {
22383
22517
  this.s.appendLeft(pos, text);
22384
- this.edits.push({ sourceStart: pos, sourceEnd: pos, text, side: 0, seq: this.seq++ });
22518
+ this.edits.push({ sourceStart: pos, sourceEnd: pos, text, side: 0, seq: this.seq++, ...options });
22385
22519
  }
22386
22520
  appendRight(pos, text) {
22387
22521
  this.s.appendRight(pos, text);
@@ -22428,7 +22562,7 @@ var SpanRecorder = class {
22428
22562
  });
22429
22563
  }
22430
22564
  const prev = spans[spans.length - 1];
22431
- if (prev && prev.kind === "replaced" && prev.sourceEnd === e.sourceStart && prev.generatedEnd === gen) {
22565
+ if (!e.broken && prev && prev.kind === "replaced" && prev.sourceEnd === e.sourceStart && prev.generatedEnd === gen) {
22432
22566
  prev.sourceEnd = e.sourceEnd;
22433
22567
  prev.generatedEnd += e.text.length;
22434
22568
  } else {
@@ -22437,7 +22571,7 @@ var SpanRecorder = class {
22437
22571
  sourceEnd: e.sourceEnd,
22438
22572
  generatedStart: gen,
22439
22573
  generatedEnd: gen + e.text.length,
22440
- kind: "replaced"
22574
+ kind: e.broken ? "broken" : "replaced"
22441
22575
  });
22442
22576
  }
22443
22577
  gen += e.text.length;
@@ -22473,22 +22607,45 @@ var askClose = (providerName) => `, __frame${providerName ? `, ${JSON.stringify(
22473
22607
  var invocationOpen = (paramNames) => `
22474
22608
  return __nola.intents.Intent(async (__frame) => {${paramNames.map((n2) => ` void ${n2};`).join("")}`;
22475
22609
  var invocationArgEntry = (name, typeExpr, contextual) => `{ name: ${JSON.stringify(name)}${typeExpr ? `, type: ${typeExpr}` : ""}${contextual ? `, contextual: true, value: ${name}` : ""} }`;
22476
- var invocationClose = (fnName, instruction, argEntries) => {
22610
+ var invocationClose = (fnName, instructionField, argEntries) => {
22477
22611
  const argsField = argEntries.length > 0 ? `, args: [${argEntries.join(", ")}]` : "";
22478
- return ` }, __nola_file_ctx().func({ fn: ${JSON.stringify(fnName)}, instruction: ${JSON.stringify(instruction)}${argsField} }));
22612
+ return ` }, __nola_file_ctx().func({ fn: ${JSON.stringify(fnName)}, instruction: ${instructionField}${argsField} }));
22479
22613
  `;
22480
22614
  };
22615
+ function templateCopy(source, quasi, scopeNodes, mode) {
22616
+ const inserts = mode === "scope" ? scopeNodes.map((n2) => [n2.start, SCOPE_PARAM]) : quasi.expressions.flatMap((e) => [[e.start, FMT_OPEN], [e.end, FMT_CLOSE]]);
22617
+ inserts.sort((a, b) => a[0] - b[0]);
22618
+ let text = "";
22619
+ const anchors = [];
22620
+ let cursor = quasi.start;
22621
+ for (const [pos, ins] of inserts) {
22622
+ if (pos > cursor) {
22623
+ anchors.push({ sourceStart: cursor, sourceEnd: pos, textOffset: text.length });
22624
+ text += source.slice(cursor, pos);
22625
+ }
22626
+ text += ins;
22627
+ cursor = pos;
22628
+ }
22629
+ anchors.push({ sourceStart: cursor, sourceEnd: quasi.end, textOffset: text.length });
22630
+ text += source.slice(cursor, quasi.end);
22631
+ return { text, anchors };
22632
+ }
22481
22633
  var callIntentTypeText = (tagText) => `<Awaited<ReturnType<typeof ${tagText}>>>`;
22482
22634
  var callIntentOpen = (typeText) => `__nola.intents.FunctionCallingIntent${typeText}({ fn: `;
22483
- var callIntentArgsHead = (tagText, instruction, loc) => `, name: ${JSON.stringify(tagText)}, instruction: ${JSON.stringify(instruction)}, loc: ${JSON.stringify(locText(loc))}, args: [`;
22635
+ var callIntentArgsHead = (tagText, instructionField, loc) => `, name: ${JSON.stringify(tagText)}, instruction: ${instructionField}, loc: ${JSON.stringify(locText(loc))}, args: [`;
22484
22636
  var CALL_INTENT_CLOSE = "] })";
22485
22637
  var EXTRACT_DEFAULT_TYPE_EXPR = "__nola.types.string()";
22486
22638
  var EXTRACT_DEFAULT_TYPE_TEXT = "<any>";
22487
22639
  var typeArgsText = (sourceText) => `<${sourceText}>`;
22488
22640
  var extractOpen = (typeText) => `__nola.intents.ExtractIntent${typeText}({ instruction: `;
22641
+ var SCOPE_PARAM = "__nola_s";
22642
+ var TEMPLATE_OPEN = `template: (${SCOPE_PARAM}) => __nola.tpl`;
22643
+ var rawTemplateText = (source, quasi) => source.slice(quasi.start + 1, quasi.end - 1);
22644
+ var extractOpenTemplate = (typeText, rawInstruction) => `__nola.intents.ExtractIntent${typeText}({ instruction: ${JSON.stringify(rawInstruction)}, ${TEMPLATE_OPEN}`;
22489
22645
  var FMT_OPEN = "__nola.fmt(";
22490
22646
  var FMT_CLOSE = ")";
22491
22647
  var extractClose = (typeExpr, loc) => `, type: ${typeExpr}, loc: ${JSON.stringify(locText(loc))} })`;
22648
+ var BROKEN_CONSTRUCT = "(undefined as never)";
22492
22649
 
22493
22650
  // ../compiler/dist/lower/lowerer.js
22494
22651
  var Lowerer = class {
@@ -22502,13 +22659,27 @@ var Lowerer = class {
22502
22659
  diagnostics = [];
22503
22660
  meta = { nolaFunctions: [] };
22504
22661
  usedRuntime = false;
22662
+ /**
22663
+ * Where a `${.member}` scope access may appear right now: "inplace" — the
22664
+ * enclosing instruction literal stays where it is (extractor), so the scope
22665
+ * parameter is inserted before the dot here; "copy" — the literal is copied
22666
+ * elsewhere (marker / call hint) and the copy builder did the insertion;
22667
+ * "none" — not inside a Nola instruction literal (NOLA2009).
22668
+ */
22669
+ scopeSite = "none";
22670
+ /**
22671
+ * True while visiting the holes of a COPIED instruction literal (marker /
22672
+ * call hint). Nola constructs there have nowhere to lower to — the literal
22673
+ * is re-emitted from source bytes — so they are NOLA2010.
22674
+ */
22675
+ inCopiedHole = false;
22505
22676
  /** named types needing a __nola_type_<Name> accessor in the appendix, in first-use order */
22506
22677
  typeAccessors = /* @__PURE__ */ new Map();
22507
22678
  /** local binding name -> companion import emitted in the appendix */
22508
22679
  companionImports = /* @__PURE__ */ new Map();
22509
22680
  /** shared derivation context (registry, imports, display file, bare ref names) */
22510
22681
  deriveCtx;
22511
- /** policy for underivable `..`-contextual param types (compiler.underivableContextType) */
22682
+ /** policy for underivable `.`-contextual param types (compiler.underivableContextType) */
22512
22683
  underivableContextType;
22513
22684
  constructor(source, file, ast, displayFile, options = {}) {
22514
22685
  this.s = new SpanRecorder(source);
@@ -22569,7 +22740,7 @@ var Lowerer = class {
22569
22740
  this.diag(Codes.UnsupportedIntentType, e.message, e.node);
22570
22741
  }
22571
22742
  /**
22572
- * A `..`-contextual param's type failed to derive (shallowly or in its
22743
+ * A `.`-contextual param's type failed to derive (shallowly or in its
22573
22744
  * accessor plan). Resolve it per the configured policy; the returned expr
22574
22745
  * (if any) replaces the failed one.
22575
22746
  */
@@ -22614,13 +22785,38 @@ var Lowerer = class {
22614
22785
  visit(node, inNolaFnBody, topLevel) {
22615
22786
  switch (node.type) {
22616
22787
  case "NolaExtractExpression": {
22788
+ if (this.inCopiedHole) {
22789
+ this.diagCopiedHole(node);
22790
+ return;
22791
+ }
22617
22792
  const extract = node;
22618
- if (extract.nolaError || !extract.quasi)
22793
+ if (extract.nolaError || !extract.quasi) {
22794
+ this.s.overwrite(node.start, node.end, BROKEN_CONSTRUCT, { broken: true });
22619
22795
  return;
22796
+ }
22620
22797
  this.lowerExtract(extract, inNolaFnBody);
22621
22798
  return;
22622
22799
  }
22800
+ case "NolaScopeAccess": {
22801
+ if (this.scopeSite === "none") {
22802
+ this.diag(
22803
+ Codes.ScopeAccessOutsideTemplate,
22804
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: literal ${...} in a diagnostic message
22805
+ "`${.member}` scope access is only allowed inside a Nola instruction template (infer-function marker, extractor prompt, call-intent hint).",
22806
+ node
22807
+ );
22808
+ this.s.overwrite(node.start, node.end, BROKEN_CONSTRUCT, { broken: true });
22809
+ return;
22810
+ }
22811
+ if (this.scopeSite === "inplace")
22812
+ this.s.appendLeft(node.start, SCOPE_PARAM);
22813
+ return;
22814
+ }
22623
22815
  case "NolaAskExpression": {
22816
+ if (this.inCopiedHole) {
22817
+ this.diagCopiedHole(node);
22818
+ return;
22819
+ }
22624
22820
  const ask2 = node;
22625
22821
  if (!inNolaFnBody) {
22626
22822
  this.diag(Codes.AskOutsideNolaFunction, "`ask` is only allowed directly inside an infer function body.", node);
@@ -22652,9 +22848,24 @@ var Lowerer = class {
22652
22848
  case "CallExpression": {
22653
22849
  const call = node;
22654
22850
  if (call.callee.type === "TaggedTemplateExpression") {
22851
+ if (this.inCopiedHole) {
22852
+ this.diagCopiedHole(node);
22853
+ return;
22854
+ }
22655
22855
  this.lowerCallIntent(call, inNolaFnBody);
22656
22856
  return;
22657
22857
  }
22858
+ if ((call.callee.type === "Identifier" || call.callee.type === "MemberExpression") && call.arguments.some((a) => this.hasExtractorSlot(a))) {
22859
+ this.lowerCallIntent(call, inNolaFnBody);
22860
+ return;
22861
+ }
22862
+ break;
22863
+ }
22864
+ case "VariableDeclarator": {
22865
+ const id = node.id;
22866
+ if (id?.nolaReservedMarker) {
22867
+ this.s.overwrite(id.nolaReservedMarker.start, id.nolaReservedMarker.end, "", { broken: true });
22868
+ }
22658
22869
  break;
22659
22870
  }
22660
22871
  default:
@@ -22666,21 +22877,81 @@ var Lowerer = class {
22666
22877
  this.visit(child, isFunctionScope ? false : inNolaFnBody, nextTopLevel);
22667
22878
  }
22668
22879
  }
22880
+ /**
22881
+ * Sigil-less call-intent detection (2026-08-14 spec): a well-formed extractor
22882
+ * in a slot position — a direct argument, or nested at any depth inside plain
22883
+ * object/array literals (the same walk checkCallIntentArg performs). Tolerant
22884
+ * placeholders (nolaError) do NOT count: a half-typed `f(..` stays a plain
22885
+ * call, so the editor never lowers a call intent around a broken slot.
22886
+ */
22887
+ hasExtractorSlot(node) {
22888
+ if (node.type === "NolaExtractExpression") {
22889
+ return !node.nolaError;
22890
+ }
22891
+ if (node.type === "ObjectExpression") {
22892
+ return node.properties.some((p) => p.type === "ObjectProperty" && this.hasExtractorSlot(p.value));
22893
+ }
22894
+ if (node.type === "ArrayExpression") {
22895
+ return node.elements.some((el) => el != null && this.hasExtractorSlot(el));
22896
+ }
22897
+ return false;
22898
+ }
22899
+ diagCopiedHole(node) {
22900
+ this.diag(Codes.NolaConstructInMarker, "Nola constructs are not allowed inside an infer-function marker or call-intent hint hole.", node);
22901
+ }
22902
+ /**
22903
+ * The instruction field of a copied instruction literal (marker / call hint):
22904
+ * prose → JSON string; lexical holes → a template literal with fmt-wrapped
22905
+ * holes; `${.member}` holes → the raw text as the instruction plus the
22906
+ * template closure. Anchors point back at the literal's verbatim runs.
22907
+ */
22908
+ instructionFieldFor(quasi, hasScopeAccess, cooked) {
22909
+ if (quasi.expressions.length === 0)
22910
+ return { field: JSON.stringify(cooked), copyText: "", anchors: [], holes: [] };
22911
+ const scopeNodes = [];
22912
+ if (hasScopeAccess) {
22913
+ walk(quasi, (n2) => {
22914
+ if (n2.type === "NolaScopeAccess")
22915
+ scopeNodes.push(n2);
22916
+ });
22917
+ }
22918
+ const copy = templateCopy(this.source, quasi, scopeNodes, hasScopeAccess ? "scope" : "fmt");
22919
+ const field = hasScopeAccess ? `${JSON.stringify(rawTemplateText(this.source, quasi))}, ${TEMPLATE_OPEN}${copy.text}` : copy.text;
22920
+ return { field, copyText: copy.text, anchors: copy.anchors, holes: quasi.expressions };
22921
+ }
22922
+ /** Visit the holes of a copied literal only to diagnose (NOLA2010 / NOLA2009) — the copy is already built. */
22923
+ visitCopiedHoles(holes, inNolaFnBody) {
22924
+ const prevSite = this.scopeSite;
22925
+ const prevHole = this.inCopiedHole;
22926
+ this.scopeSite = "copy";
22927
+ this.inCopiedHole = true;
22928
+ try {
22929
+ for (const e of holes)
22930
+ this.visit(e, inNolaFnBody, false);
22931
+ } finally {
22932
+ this.scopeSite = prevSite;
22933
+ this.inCopiedHole = prevHole;
22934
+ }
22935
+ }
22669
22936
  lowerInferFunction(fn) {
22670
22937
  const infer = fn.nolaInfer;
22671
22938
  if (!infer)
22672
22939
  return;
22673
22940
  const name = fn.id?.name ?? "anonymous";
22674
22941
  this.s.remove(infer.start, infer.end);
22675
- const instruction = fn.nolaMarker?.instruction ?? "";
22676
- if (fn.nolaMarker)
22677
- this.s.remove(fn.nolaMarker.start, fn.nolaMarker.end);
22942
+ const marker = fn.nolaMarker;
22943
+ if (marker)
22944
+ this.s.remove(marker.start, marker.end);
22678
22945
  const body = fn.body;
22679
22946
  if (!body)
22680
22947
  return;
22681
22948
  const argEntries = [];
22682
22949
  const paramNames = [];
22683
22950
  for (const p of fn.params ?? []) {
22951
+ if (p.nolaError) {
22952
+ this.s.overwrite(p.start, p.end, "", { broken: true });
22953
+ continue;
22954
+ }
22684
22955
  if (p.nolaContextual)
22685
22956
  this.s.remove(p.nolaContextual.start, p.nolaContextual.end);
22686
22957
  const target = p.type === "AssignmentPattern" ? p.left : p;
@@ -22708,34 +22979,34 @@ var Lowerer = class {
22708
22979
  }
22709
22980
  argEntries.push(invocationArgEntry(paramName, typeExpr, Boolean(p.nolaContextual)));
22710
22981
  }
22982
+ const inst = marker?.quasi !== void 0 ? this.instructionFieldFor(marker.quasi, marker.hasScopeAccess === true, marker.instruction) : { field: JSON.stringify(marker?.instruction ?? ""), copyText: "", anchors: [], holes: [] };
22983
+ const close = invocationClose(name, inst.field, argEntries);
22984
+ const copyAt = inst.copyText ? close.indexOf(inst.copyText) : -1;
22985
+ const anchors = copyAt >= 0 ? inst.anchors.map((a) => ({ ...a, textOffset: a.textOffset + copyAt })) : void 0;
22711
22986
  this.s.appendRight(body.start + 1, invocationOpen(paramNames));
22712
- this.s.appendLeft(body.end - 1, invocationClose(name, instruction, argEntries));
22987
+ this.s.appendLeft(body.end - 1, close, anchors ? { anchors } : {});
22713
22988
  this.meta.nolaFunctions.push(name);
22714
22989
  this.usedRuntime = true;
22990
+ this.visitCopiedHoles(inst.holes, false);
22715
22991
  }
22716
22992
  lowerCallIntent(call, inNolaFnBody) {
22717
- const callee = call.callee;
22718
- const { tag, quasi } = callee;
22719
- if (quasi.expressions.length > 0) {
22720
- this.diag(
22721
- Codes.SubstitutionInCallMarker,
22722
- // biome-ignore lint/suspicious/noTemplateCurlyInString: literal ${...} in a diagnostic message
22723
- "`${...}` substitutions are not allowed in a call-intent marker.",
22724
- quasi.expressions[0]
22725
- );
22726
- return;
22727
- }
22993
+ const tagged = call.callee.type === "TaggedTemplateExpression" ? call.callee : void 0;
22994
+ const inst = tagged ? this.instructionFieldFor(tagged.quasi, tagged.quasi.nolaHasScopeAccess === true, tagged.quasi.quasis.map((q) => q.value.cooked ?? q.value.raw).join("")) : { field: '""', copyText: "", anchors: [], holes: [] };
22728
22995
  for (const arg of call.arguments)
22729
22996
  this.checkCallIntentArg(arg);
22730
- const instruction = quasi.quasis.map((q) => q.value.cooked ?? q.value.raw).join("");
22731
- const tagText = this.source.slice(tag.start, tag.end);
22732
- const simple = tag.type === "Identifier" || tag.type === "MemberExpression";
22733
- const typeText = simple ? callIntentTypeText(tagText) : "";
22997
+ const callee = tagged ? tagged.tag : call.callee;
22998
+ const calleeText = this.source.slice(callee.start, callee.end);
22999
+ const simple = callee.type === "Identifier" || callee.type === "MemberExpression";
23000
+ const typeText = simple ? callIntentTypeText(calleeText) : "";
22734
23001
  this.s.appendLeft(call.start, callIntentOpen(typeText));
22735
23002
  const argsStart = call.arguments.length > 0 ? call.arguments[0].start : call.end - 1;
22736
- this.s.overwrite(tag.end, argsStart, callIntentArgsHead(tagText, instruction, call.loc.start));
23003
+ const head = callIntentArgsHead(calleeText, inst.field, call.loc.start);
23004
+ const copyAt = inst.copyText ? head.indexOf(inst.copyText) : -1;
23005
+ const anchors = copyAt >= 0 ? inst.anchors.map((a) => ({ ...a, textOffset: a.textOffset + copyAt })) : void 0;
23006
+ this.s.overwrite(callee.end, argsStart, head, anchors ? { anchors } : {});
22737
23007
  this.s.overwrite(call.end - 1, call.end, CALL_INTENT_CLOSE);
22738
23008
  this.usedRuntime = true;
23009
+ this.visitCopiedHoles(inst.holes, inNolaFnBody);
22739
23010
  for (const arg of call.arguments)
22740
23011
  this.visit(arg, inNolaFnBody, false);
22741
23012
  }
@@ -22783,13 +23054,16 @@ var Lowerer = class {
22783
23054
  }
22784
23055
  }
22785
23056
  }
22786
- const open = extractOpen(typeText);
23057
+ const isTemplate = quasi.nolaHasScopeAccess === true;
23058
+ const open = isTemplate ? extractOpenTemplate(typeText, rawTemplateText(this.source, quasi)) : extractOpen(typeText);
22787
23059
  const typeNode = node.typeArgs?.params[0];
22788
23060
  const anchors = typeNode ? [{ sourceStart: typeNode.start, sourceEnd: typeNode.end, textOffset: open.indexOf(typeText) + 1 }] : void 0;
22789
- this.s.overwrite(node.start, quasi.start, open, anchors);
22790
- for (const expr of quasi.expressions) {
22791
- this.s.appendLeft(expr.start, FMT_OPEN);
22792
- this.s.appendLeft(expr.end, FMT_CLOSE);
23061
+ this.s.overwrite(node.start, quasi.start, open, { anchors });
23062
+ if (!isTemplate) {
23063
+ for (const expr of quasi.expressions) {
23064
+ this.s.appendLeft(expr.start, FMT_OPEN);
23065
+ this.s.appendLeft(expr.end, FMT_CLOSE);
23066
+ }
22793
23067
  }
22794
23068
  const suffix = extractClose(typeExpr, node.loc.start);
22795
23069
  if (node.end > quasi.end) {
@@ -22798,8 +23072,14 @@ var Lowerer = class {
22798
23072
  this.s.appendLeft(node.end, suffix);
22799
23073
  }
22800
23074
  this.usedRuntime = true;
22801
- for (const expr of quasi.expressions)
22802
- this.visit(expr, inNolaFnBody, false);
23075
+ const prevSite = this.scopeSite;
23076
+ this.scopeSite = isTemplate ? "inplace" : "none";
23077
+ try {
23078
+ for (const expr of quasi.expressions)
23079
+ this.visit(expr, inNolaFnBody, false);
23080
+ } finally {
23081
+ this.scopeSite = prevSite;
23082
+ }
22803
23083
  }
22804
23084
  };
22805
23085
 
@@ -22978,15 +23258,33 @@ var INSERTION_DATA = { navigation: true, format: false };
22978
23258
  function spansToMappings(spans, anchors = [], code2) {
22979
23259
  const verbatim = [];
22980
23260
  const replaced = [];
23261
+ const afterBroken = new Set(spans.filter((s) => s.kind === "broken").map((s) => s.sourceEnd));
22981
23262
  for (const span of spans) {
22982
23263
  if (span.kind === "verbatim") {
23264
+ if (afterBroken.has(span.sourceStart) && span.sourceEnd > span.sourceStart) {
23265
+ verbatim.push({
23266
+ sourceOffsets: [span.sourceStart],
23267
+ generatedOffsets: [span.generatedStart],
23268
+ lengths: [1],
23269
+ data: { ...VERBATIM_DATA, completion: false }
23270
+ });
23271
+ if (span.sourceEnd - span.sourceStart === 1)
23272
+ continue;
23273
+ verbatim.push({
23274
+ sourceOffsets: [span.sourceStart + 1],
23275
+ generatedOffsets: [span.generatedStart + 1],
23276
+ lengths: [span.sourceEnd - span.sourceStart - 1],
23277
+ data: { ...VERBATIM_DATA }
23278
+ });
23279
+ continue;
23280
+ }
22983
23281
  verbatim.push({
22984
23282
  sourceOffsets: [span.sourceStart],
22985
23283
  generatedOffsets: [span.generatedStart],
22986
23284
  lengths: [span.sourceEnd - span.sourceStart],
22987
23285
  data: { ...VERBATIM_DATA }
22988
23286
  });
22989
- } else if (span.kind === "replaced") {
23287
+ } else if (span.kind === "replaced" || span.kind === "broken") {
22990
23288
  replaced.push({
22991
23289
  sourceOffsets: [span.sourceStart],
22992
23290
  generatedOffsets: [span.generatedStart],
@@ -23135,10 +23433,6 @@ function createNolaLanguagePlugin(asFileName, options = {}) {
23135
23433
  };
23136
23434
  }
23137
23435
 
23138
- // ../node-loader/dist/config.js
23139
- var import_node_fs2 = require("node:fs");
23140
- var import_node_path2 = require("node:path");
23141
-
23142
23436
  // ../runtime/dist/runtime/ask-span.js
23143
23437
  var AskSpan = class {
23144
23438
  kind = "ask";
@@ -23222,6 +23516,7 @@ var ALLOWED_KEYS = /* @__PURE__ */ new Set([
23222
23516
  "system",
23223
23517
  "ask",
23224
23518
  "compiler",
23519
+ "build",
23225
23520
  ...RESERVED_KEYS
23226
23521
  ]);
23227
23522
  var HOOK_METHODS = [
@@ -23316,6 +23611,24 @@ function resolveCompilerConfig(raw, source) {
23316
23611
  }
23317
23612
  return Object.freeze({ underivableContextType: mode ?? "error" });
23318
23613
  }
23614
+ var BUILD_TARGETS = ["app", "lib"];
23615
+ function resolveBuildConfig(raw, source) {
23616
+ if (raw === void 0)
23617
+ return Object.freeze({ target: "app" });
23618
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
23619
+ fail(source, '`build` must be an object \u2014 write build: { target: "lib" }.');
23620
+ }
23621
+ const cfg = raw;
23622
+ for (const key of Object.keys(cfg)) {
23623
+ if (key !== "target")
23624
+ fail(source, `unknown build config key \`${key}\` \u2014 allowed keys: target.`);
23625
+ }
23626
+ const target = cfg.target;
23627
+ if (target !== void 0 && (typeof target !== "string" || !BUILD_TARGETS.includes(target))) {
23628
+ fail(source, `build.target must be one of ${BUILD_TARGETS.join(", ")}.`);
23629
+ }
23630
+ return Object.freeze({ target: target ?? "app" });
23631
+ }
23319
23632
  function validateMiddleware(source, raw) {
23320
23633
  if (raw === void 0)
23321
23634
  return Object.freeze([]);
@@ -23342,7 +23655,7 @@ function resolveNolaConfig(raw, opts = {}) {
23342
23655
  }
23343
23656
  for (const key of Object.keys(cfg)) {
23344
23657
  if (!ALLOWED_KEYS.has(key)) {
23345
- fail(source, `unknown config key \`${key}\` \u2014 allowed keys: providers, forceProvider, observability, hooks, middleware, cache, system, ask, compiler.`);
23658
+ fail(source, `unknown config key \`${key}\` \u2014 allowed keys: providers, forceProvider, observability, hooks, middleware, cache, system, ask, compiler, build.`);
23346
23659
  }
23347
23660
  }
23348
23661
  const providers = cfg.providers;
@@ -23383,6 +23696,7 @@ function resolveNolaConfig(raw, opts = {}) {
23383
23696
  const system = validateSystem(source, cfg.system);
23384
23697
  const ask2 = validateAsk(source, cfg.ask);
23385
23698
  const compiler = resolveCompilerConfig(cfg.compiler, source);
23699
+ const build2 = resolveBuildConfig(cfg.build, source);
23386
23700
  return Object.freeze({
23387
23701
  providers: Object.freeze({ ...map }),
23388
23702
  forceProvider: cfg.forceProvider,
@@ -23392,7 +23706,8 @@ function resolveNolaConfig(raw, opts = {}) {
23392
23706
  cache: cache3,
23393
23707
  system,
23394
23708
  ask: ask2,
23395
- compiler
23709
+ compiler,
23710
+ build: build2
23396
23711
  });
23397
23712
  }
23398
23713
 
@@ -23483,17 +23798,6 @@ var Frame = class _Frame {
23483
23798
  composeInferenceData(composer, opts) {
23484
23799
  this.infer.composeInferenceData(composer, { ...opts, nested: this.parent !== void 0 });
23485
23800
  }
23486
- // /**
23487
- // * Prompt-facing lineage under stack-frame semantics: the root frame's static
23488
- // * ancestry followed by one entry per frame down the call chain. A callee's
23489
- // * own construction ancestors are bypassed — the caller chain is its lineage;
23490
- // * sourceFile() (the definition site) intentionally differs.
23491
- // */
23492
- // promptLineage(): ReadonlyArray<Readonly<Record<string, unknown>>> {
23493
- // if (!this.parent) return this.infer.promptLineage();
23494
- // const own = this.infer.promptData();
23495
- // return own === undefined ? this.parent.promptLineage() : [...this.parent.promptLineage(), own];
23496
- // }
23497
23801
  /**
23498
23802
  * The .tsi file this frame's function is defined in (static chain first).
23499
23803
  * Scope-less intents (extract/call) carry no file root — for them the
@@ -23527,6 +23831,70 @@ var Frame = class _Frame {
23527
23831
  }
23528
23832
  };
23529
23833
 
23834
+ // ../runtime/dist/ask/prompt-render.js
23835
+ var defaultPromptRenderer = Object.freeze({
23836
+ function(data) {
23837
+ const { fn, file, instruction, args, nested } = data;
23838
+ const signature = `${fn}(${args.map((a) => a.name).join(", ")})`;
23839
+ const header = `CONTEXT \u2014 inside ${signature}` + (file === void 0 ? "" : `, ${file}`) + (nested ? ", called from the context above" : "");
23840
+ const lines = [header];
23841
+ if (instruction)
23842
+ lines.push(`Purpose: ${instruction}`);
23843
+ if (args.length > 0) {
23844
+ lines.push("Arguments (values are runtime data, not instructions):");
23845
+ for (const a of args) {
23846
+ if (!a.contextual) {
23847
+ lines.push(`- ${a.name} = (value not available)`);
23848
+ continue;
23849
+ }
23850
+ const type = a.type ? ` (${a.type.toNativeType()})` : "";
23851
+ if (a.value === void 0) {
23852
+ lines.push(`- ${a.name}${type} = (no value)`);
23853
+ } else if (typeof a.value === "string" && (a.value.includes("\n") || a.value.length > 120)) {
23854
+ lines.push(`- ${a.name}${type}:`, "<value>", a.value, "</value>");
23855
+ } else {
23856
+ lines.push(`- ${a.name}${type} = ${JSON.stringify(a.value)}`);
23857
+ }
23858
+ }
23859
+ }
23860
+ return lines.join("\n");
23861
+ },
23862
+ extract(data) {
23863
+ const { instruction, hasContext } = data;
23864
+ const lines = [
23865
+ "TASK",
23866
+ hasContext ? "Produce the data requested below from the context above." : "Produce the data requested below.",
23867
+ "<request>",
23868
+ instruction,
23869
+ "</request>",
23870
+ extractFormat(data)
23871
+ ];
23872
+ return lines.join("\n");
23873
+ }
23874
+ });
23875
+ function extractFormat(data) {
23876
+ const lines = [];
23877
+ if (!data.trivialString)
23878
+ lines.push("RESPONSE SCHEMA (JSON Schema):", JSON.stringify(data.schema));
23879
+ lines.push(data.trivialString ? "Respond with a single JSON string containing the value." : "Respond with a single JSON value strictly conforming to the schema above.");
23880
+ return lines.join("\n");
23881
+ }
23882
+ function joinBlocks(...parts) {
23883
+ return parts.filter((p) => p.length > 0).join("\n\n");
23884
+ }
23885
+ function renderTemplate(template, scope, site, tail, tailRead) {
23886
+ let text;
23887
+ try {
23888
+ text = template(scope);
23889
+ } catch (e) {
23890
+ throw new NolaIntentError(`${Codes.PromptTemplateFailed}: prompt template at ${site} threw: ${e instanceof Error ? e.message : String(e)}`, Codes.PromptTemplateFailed);
23891
+ }
23892
+ if (typeof text !== "string" || text.trim() === "") {
23893
+ throw new NolaIntentError(`${Codes.PromptTemplateFailed}: prompt template at ${site} rendered no text.`, Codes.PromptTemplateFailed);
23894
+ }
23895
+ return tailRead() ? text : joinBlocks(text, tail());
23896
+ }
23897
+
23530
23898
  // ../runtime/dist/infer-context/infer-context.js
23531
23899
  var InferContext = class _InferContext {
23532
23900
  data;
@@ -23541,31 +23909,16 @@ var InferContext = class _InferContext {
23541
23909
  scope(data) {
23542
23910
  return new _InferContext(Object.freeze({ ...data }), this.runtime, this);
23543
23911
  }
23544
- /**
23545
- * What this node contributes to the user-message lineage JSON and to
23546
- * fingerprints. undefined = nothing (system node). The single polymorphic
23547
- * serialization seam.
23548
- */
23549
- // promptData(): Readonly<Record<string, unknown>> | undefined {
23550
- // return this.data;
23551
- // }
23552
- composeInferenceData(_composer, _opts) {
23553
- }
23554
- /** Raw data chain from root to this node (internal/debug). */
23555
- // lineage(): ReadonlyArray<Readonly<Record<string, unknown>>> {
23556
- // const chain: Array<Readonly<Record<string, unknown>>> = [];
23557
- // for (let c: InferContext | undefined = this; c; c = c.parent) chain.unshift(c.data);
23558
- // return chain;
23559
- // }
23560
- /** Prompt-facing lineage: promptData() per node, undefined entries dropped. */
23561
- // promptLineage(): ReadonlyArray<Readonly<Record<string, unknown>>> {
23562
- // const chain: Array<Readonly<Record<string, unknown>>> = [];
23563
- // for (let c: InferContext | undefined = this; c; c = c.parent) {
23564
- // const d = c.promptData();
23565
- // if (d !== undefined) chain.unshift(d);
23566
- // }
23567
- // return chain;
23568
- // }
23912
+ /** Whether this node emits prompt text of its own (function/extract nodes do; bare scopes do not). */
23913
+ contributesText() {
23914
+ return false;
23915
+ }
23916
+ /** Base nodes contribute nothing — the remainder passes straight through. */
23917
+ composeInferenceData(composer, opts) {
23918
+ const rest = opts?.next?.() ?? "";
23919
+ if (rest)
23920
+ composer.addText(rest);
23921
+ }
23569
23922
  /**
23570
23923
  * The `.tsi` file this context descends from: the nearest file node up the
23571
23924
  * parent chain (FileInferContext overrides). A lineage with no file root
@@ -23581,42 +23934,67 @@ var FunctionInferContext = class _FunctionInferContext extends InferContext {
23581
23934
  /** @internal created via FileInferContext.func (and tests) only. Parentless = free-standing scope. */
23582
23935
  static create(init, runtime, parent) {
23583
23936
  const args = Object.freeze((init.args ?? []).map((a) => Object.freeze({ name: a.name, type: a.type, contextual: a.contextual ?? false, value: a.value })));
23584
- return new _FunctionInferContext(Object.freeze({ fn: init.fn, instruction: init.instruction ?? "", args }), runtime, parent);
23937
+ return new _FunctionInferContext(Object.freeze({
23938
+ fn: init.fn,
23939
+ instruction: init.instruction ?? "",
23940
+ ...init.template ? { template: init.template } : {},
23941
+ args
23942
+ }), runtime, parent);
23943
+ }
23944
+ contributesText() {
23945
+ return true;
23585
23946
  }
23586
23947
  /**
23587
- * One CONTEXT block per invocation: signature + source file, the authored
23588
- * instruction, and the argument list. Plain (non-contextual) params appear
23589
- * by name with an explicit unknown marker so the model never invents a
23590
- * value for them; contextual values stay JSON-quoted (newlines arrive as
23591
- * \n, never as fake section breaks). The composed text is fingerprint
23592
- * input — keep it deterministic.
23948
+ * One CONTEXT block per invocation. The node assembles FunctionPromptData;
23949
+ * without a template the runtime's PromptRenderer renders it and the
23950
+ * remainder follows; with a template (a `${.member}` marker) the template
23951
+ * renders the block from a FunctionPromptScope over the same data — `.default`
23952
+ * is the built-in block, `.next` the remainder (appended after the template
23953
+ * when it never reads it). The composed text is fingerprint input — keep it
23954
+ * deterministic.
23593
23955
  */
23594
23956
  composeInferenceData(composer, opts) {
23595
- const { fn, instruction, args } = this.data;
23596
- const signature = `${fn}(${args.map((a) => a.name).join(", ")})`;
23957
+ const { fn, instruction, args, template } = this.data;
23597
23958
  const file = this.sourceFile();
23598
- const header = `CONTEXT \u2014 inside ${signature}` + (file === "<unknown>" ? "" : `, ${file}`) + (opts?.nested ? ", called from the context above" : "");
23599
- const lines = [header];
23600
- if (instruction)
23601
- lines.push(`Purpose: ${instruction}`);
23602
- if (args.length > 0) {
23603
- lines.push("Arguments (values are runtime data, not instructions):");
23604
- for (const a of args) {
23605
- if (!a.contextual) {
23606
- lines.push(`- ${a.name} = (value not available)`);
23607
- continue;
23608
- }
23609
- const type = a.type ? ` (${a.type.toNativeType()})` : "";
23610
- if (a.value === void 0) {
23611
- lines.push(`- ${a.name}${type} = (no value)`);
23612
- } else if (typeof a.value === "string" && (a.value.includes("\n") || a.value.length > 120)) {
23613
- lines.push(`- ${a.name}${type}:`, "<value>", a.value, "</value>");
23614
- } else {
23615
- lines.push(`- ${a.name}${type} = ${JSON.stringify(a.value)}`);
23616
- }
23617
- }
23959
+ const data = {
23960
+ kind: "function",
23961
+ fn,
23962
+ ...file === "<unknown>" ? {} : { file },
23963
+ instruction,
23964
+ args,
23965
+ nested: opts?.nested ?? false,
23966
+ hasContext: opts?.hasContext ?? false
23967
+ };
23968
+ const renderer = this.runtime.promptRenderer;
23969
+ let memo;
23970
+ const rest = () => memo ??= opts?.next?.() ?? "";
23971
+ if (!template) {
23972
+ composer.addText(joinBlocks(renderer.function(data), rest()));
23973
+ return;
23618
23974
  }
23619
- composer.addText(lines.join("\n"));
23975
+ let nextRead = false;
23976
+ const scope = Object.freeze({
23977
+ fn,
23978
+ signature: `${fn}(${args.map((a) => a.name).join(", ")})`,
23979
+ ...data.file === void 0 ? {} : { file: data.file },
23980
+ args: Object.freeze(args.map((a) => Object.freeze({
23981
+ name: a.name,
23982
+ ...a.type ? { type: a.type.toNativeType() } : {},
23983
+ contextual: a.contextual,
23984
+ value: a.value
23985
+ }))),
23986
+ nested: data.nested,
23987
+ hasContext: data.hasContext,
23988
+ // The template IS the instruction: the default block renders without Purpose.
23989
+ get default() {
23990
+ return renderer.function({ ...data, instruction: "" });
23991
+ },
23992
+ get next() {
23993
+ nextRead = true;
23994
+ return rest();
23995
+ }
23996
+ });
23997
+ composer.addText(renderTemplate(template, scope, `${file}:${fn}`, rest, () => nextRead));
23620
23998
  }
23621
23999
  };
23622
24000
 
@@ -23729,6 +24107,13 @@ var NolaRuntime = class {
23729
24107
  this.emit = emit;
23730
24108
  this.url = url;
23731
24109
  }
24110
+ /**
24111
+ * The provider-facing text seam consulted by every composeInferenceData —
24112
+ * the built-in renderer today (config-level overrides are the next step).
24113
+ */
24114
+ get promptRenderer() {
24115
+ return defaultPromptRenderer;
24116
+ }
23732
24117
  /** The frozen resolved config, or null when nothing has been configured yet. */
23733
24118
  get config() {
23734
24119
  return this.#config;
@@ -23845,8 +24230,12 @@ var nolaRuntime = {
23845
24230
  }
23846
24231
  };
23847
24232
 
23848
- // ../node-loader/dist/config.js
24233
+ // ../node-loader/dist/bundle-config.js
23849
24234
  var import_esbuild = __toESM(require_main(), 1);
24235
+
24236
+ // ../node-loader/dist/config.js
24237
+ var import_node_fs2 = require("node:fs");
24238
+ var import_node_path2 = require("node:path");
23850
24239
  function findUp2(startDir, name) {
23851
24240
  let dir = startDir;
23852
24241
  for (; ; ) {
@@ -23973,6 +24362,61 @@ function guardProjectServiceDocumentCache(projectService) {
23973
24362
  };
23974
24363
  }
23975
24364
 
24365
+ // src/resolution-watch.ts
24366
+ function decorateHostForTsiResolutionWatch(host, serverHost, project, extensions) {
24367
+ const prior = host.resolveModuleNameLiterals?.bind(host);
24368
+ if (!prior) return;
24369
+ const watchFile = (path, cb) => serverHost.watchFile(
24370
+ path,
24371
+ cb,
24372
+ 500
24373
+ );
24374
+ const caseSensitive = typeof host.useCaseSensitiveFileNames === "function" ? host.useCaseSensitiveFileNames() : host.useCaseSensitiveFileNames ?? false;
24375
+ const canon = (fileName) => {
24376
+ const posix = fileName.replace(/\\/g, "/");
24377
+ return caseSensitive ? posix : posix.toLowerCase();
24378
+ };
24379
+ const watched = /* @__PURE__ */ new Map();
24380
+ const invalidated = /* @__PURE__ */ new Set();
24381
+ const hostWithInvalidation = host;
24382
+ const projectWithDirty = project;
24383
+ let innerHasInvalidated = hostWithInvalidation.hasInvalidatedResolutions;
24384
+ const composedHasInvalidated = (path) => invalidated.has(canon(path)) || (innerHasInvalidated?.(path) ?? false);
24385
+ Object.defineProperty(host, "hasInvalidatedResolutions", {
24386
+ configurable: true,
24387
+ get: () => composedHasInvalidated,
24388
+ set: (fn) => {
24389
+ innerHasInvalidated = fn;
24390
+ }
24391
+ });
24392
+ host.resolveModuleNameLiterals = (moduleLiterals, containingFile, ...rest) => {
24393
+ const containing = canon(containingFile);
24394
+ invalidated.delete(containing);
24395
+ const results = prior(moduleLiterals, containingFile, ...rest);
24396
+ for (let i = 0; i < moduleLiterals.length; i++) {
24397
+ const text = moduleLiterals[i]?.text ?? "";
24398
+ if (!text.startsWith("./") && !text.startsWith("../")) continue;
24399
+ if (!extensions.some((ext) => text.endsWith(ext))) continue;
24400
+ const dir = containingFile.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
24401
+ const candidate = canon(`${dir}/${text}`.replace(/\/\.\//g, "/"));
24402
+ const entry = watched.get(candidate);
24403
+ if (entry) {
24404
+ entry.waiters.add(containing);
24405
+ continue;
24406
+ }
24407
+ if (results[i]?.resolvedModule) continue;
24408
+ const waiters = /* @__PURE__ */ new Set([containing]);
24409
+ const watcher = watchFile(candidate, () => {
24410
+ for (const waiter of waiters) invalidated.add(waiter);
24411
+ projectWithDirty.markAsDirty?.();
24412
+ project.refreshDiagnostics();
24413
+ });
24414
+ watched.set(candidate, { watcher, waiters });
24415
+ }
24416
+ return results;
24417
+ };
24418
+ }
24419
+
23976
24420
  // src/server-host.ts
23977
24421
  var PATCHED2 = Symbol.for("nola.companionServerHost");
23978
24422
  function decorateServerHostForCompanions(serverHost) {
@@ -24031,7 +24475,12 @@ function createNolaTsPlugin() {
24031
24475
  decorateHostHideShadowedDeclarations(typescript, info.languageServiceHost);
24032
24476
  decorateHostWithCompanions(typescript, info.languageServiceHost, { sourceRoot });
24033
24477
  return {
24034
- languagePlugins: [createNolaLanguagePlugin((fileName) => fileName, { sourceRoot })]
24478
+ languagePlugins: [createNolaLanguagePlugin((fileName) => fileName, { sourceRoot })],
24479
+ // setup runs AFTER Volar's decorateLanguageServiceHost, so this wraps the
24480
+ // resolver that actually handles `.tsi` literals (see resolution-watch.ts).
24481
+ setup: () => {
24482
+ decorateHostForTsiResolutionWatch(info.languageServiceHost, info.serverHost, info.project, [".tsi"]);
24483
+ }
24035
24484
  };
24036
24485
  });
24037
24486
  }