@markbrutx/promptbook-core 0.2.0 → 0.4.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuD,UAAU,EAAQ,MAAM,YAAY,CAAC;AAExG,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,wGAAwG;IACxG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA+FD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAMhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,oBAAyB,GAAG,MAAM,CAY1F;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK1D"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuD,UAAU,EAAQ,MAAM,YAAY,CAAC;AAExG,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,wGAAwG;IACxG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAuGD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAMhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,oBAAyB,GAAG,MAAM,CAY1F;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK1D"}
package/dist/bundle.js CHANGED
@@ -71,10 +71,18 @@ function canonicalCodePrompt(codePrompt) {
71
71
  };
72
72
  return compact(ordered);
73
73
  }
74
- /** Emit `new Map([...])` with one `[key, value]` entry per line (entries pre-sorted). */
74
+ /**
75
+ * Emit `new Map([...])` with one `[key, value]` entry per line (entries pre-sorted).
76
+ *
77
+ * An empty map serializes to `new Map()` (not `new Map([])`): without entries to
78
+ * infer from, `new Map([])` widens to `Map<unknown, unknown>`, which is not
79
+ * assignable to `Map<string, T>` in a plain (annotation-free) module. `new Map()`
80
+ * infers `Map<any, any>`, which stays assignable, so plain bundles type-check
81
+ * against `PromptBook` even when a map is empty.
82
+ */
75
83
  function serializeMap(entries, canonical) {
76
84
  if (entries.length === 0) {
77
- return "new Map([])";
85
+ return "new Map()";
78
86
  }
79
87
  const lines = entries.map(([key, value]) => ` [${JSON.stringify(key)}, ${JSON.stringify(canonical(value))}],`);
80
88
  return `new Map([\n${lines.join("\n")}\n ])`;
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,103 @@ function interpolate(body, context, onMissing) {
19
19
  });
20
20
  }
21
21
 
22
+ // dist/lint/rules/banned-tokens.js
23
+ var EM_DASH = "\u2014";
24
+ var DEFAULT_BANNED = [EM_DASH];
25
+ function bannedTokens(options = {}) {
26
+ const tokens = options.tokens ?? DEFAULT_BANNED;
27
+ const severity = options.severity ?? "error";
28
+ return {
29
+ id: "banned-tokens",
30
+ description: "Resolved prompt must not contain banned substrings or patterns.",
31
+ scope: "resolved",
32
+ check(input) {
33
+ const result = input.result;
34
+ if (result === void 0) {
35
+ return [];
36
+ }
37
+ const text = result.text;
38
+ const findings = [];
39
+ for (const token of tokens) {
40
+ const matched = typeof token === "string" ? text.includes(token) : token.test(text);
41
+ if (matched) {
42
+ const label = typeof token === "string" ? JSON.stringify(token) : String(token);
43
+ findings.push({
44
+ ruleId: "banned-tokens",
45
+ severity,
46
+ message: `text contains banned token ${label}`
47
+ });
48
+ }
49
+ }
50
+ return findings;
51
+ }
52
+ };
53
+ }
54
+
55
+ // dist/lint/references.js
56
+ function* iterateReferences(book) {
57
+ for (const composition of book.compositions.values()) {
58
+ const composed = composition.name;
59
+ for (const id of composition.base) {
60
+ yield { id, composition: composed, role: "base" };
61
+ }
62
+ if (composition.order) {
63
+ for (const id of composition.order) {
64
+ yield { id, composition: composed, role: "order" };
65
+ }
66
+ }
67
+ for (const rule of composition.rules) {
68
+ const ruleIndex = rule.index;
69
+ for (const id of rule.add ?? []) {
70
+ yield { id, composition: composed, role: "add", ruleIndex };
71
+ }
72
+ if (rule.after !== void 0) {
73
+ yield { id: rule.after, composition: composed, role: "after", ruleIndex };
74
+ }
75
+ for (const [from, to] of Object.entries(rule.replace ?? {})) {
76
+ yield { id: from, composition: composed, role: "replace-from", ruleIndex };
77
+ yield { id: to, composition: composed, role: "replace-to", ruleIndex };
78
+ }
79
+ for (const id of rule.forbid ?? []) {
80
+ yield { id, composition: composed, role: "forbid", ruleIndex };
81
+ }
82
+ for (const id of rule.order ?? []) {
83
+ yield { id, composition: composed, role: "rule-order", ruleIndex };
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ // dist/lint/rules/dangling-reference.js
90
+ function danglingReference(options = {}) {
91
+ const severity = options.severity ?? "error";
92
+ return {
93
+ id: "dangling-reference",
94
+ description: "Every referenced fragment id must exist.",
95
+ scope: "book",
96
+ check(input) {
97
+ const findings = [];
98
+ for (const reference of iterateReferences(input.book)) {
99
+ if (input.book.fragments.has(reference.id)) {
100
+ continue;
101
+ }
102
+ const where = reference.ruleIndex !== void 0 ? `rule #${reference.ruleIndex} (${reference.role})` : reference.role;
103
+ const finding = {
104
+ ruleId: "dangling-reference",
105
+ severity,
106
+ message: `composition "${reference.composition}" references unknown fragment "${reference.id}" via ${where}`,
107
+ fragmentId: reference.id
108
+ };
109
+ if (reference.ruleIndex !== void 0) {
110
+ finding.ruleIndex = reference.ruleIndex;
111
+ }
112
+ findings.push(finding);
113
+ }
114
+ return findings;
115
+ }
116
+ };
117
+ }
118
+
22
119
  // dist/resolve-book.js
23
120
  function resolveBook(book, prompt, context) {
24
121
  const composition = book.compositions.get(prompt);
@@ -188,7 +285,299 @@ function computeUnmatchedAxes(composition, context) {
188
285
  }
189
286
  return result;
190
287
  }
288
+
289
+ // dist/lint/rules/dead-rule.js
290
+ function deadRule(options = {}) {
291
+ const severity = options.severity ?? "warning";
292
+ return {
293
+ id: "dead-rule",
294
+ description: "Rules should affect fragments that can be present (static check).",
295
+ scope: "book",
296
+ check(input) {
297
+ const findings = [];
298
+ for (const composition of input.book.compositions.values()) {
299
+ const everPresent = everPresentSet(composition);
300
+ composition.rules.forEach((rule, position) => {
301
+ const present = presentSetFor(composition.base, composition.rules, position, rule.when);
302
+ switch (rule.action) {
303
+ case "add":
304
+ for (const id of rule.add ?? []) {
305
+ if (present.has(id)) {
306
+ findings.push({
307
+ ruleId: "dead-rule",
308
+ severity,
309
+ message: `rule #${rule.index} in "${composition.name}" adds "${id}", which is already present`,
310
+ fragmentId: id,
311
+ ruleIndex: rule.index
312
+ });
313
+ }
314
+ }
315
+ break;
316
+ case "replace":
317
+ for (const from of Object.keys(rule.replace ?? {})) {
318
+ if (!present.has(from)) {
319
+ findings.push({
320
+ ruleId: "dead-rule",
321
+ severity,
322
+ message: `rule #${rule.index} in "${composition.name}" replaces "${from}", which is never present`,
323
+ fragmentId: from,
324
+ ruleIndex: rule.index
325
+ });
326
+ }
327
+ }
328
+ break;
329
+ case "order":
330
+ for (const id of rule.order ?? []) {
331
+ if (!everPresent.has(id)) {
332
+ findings.push({
333
+ ruleId: "dead-rule",
334
+ severity,
335
+ message: `rule #${rule.index} in "${composition.name}" orders unknown id "${id}"`,
336
+ fragmentId: id,
337
+ ruleIndex: rule.index
338
+ });
339
+ }
340
+ }
341
+ break;
342
+ case "forbid":
343
+ break;
344
+ }
345
+ });
346
+ }
347
+ return findings;
348
+ }
349
+ };
350
+ }
351
+ function presentSetFor(base, rules, position, currentWhen) {
352
+ const present = new Set(base);
353
+ for (let i = 0; i < position; i++) {
354
+ const prior = rules[i];
355
+ if (!prior || !firesWhenever(prior.when, currentWhen)) {
356
+ continue;
357
+ }
358
+ if (prior.action === "add") {
359
+ for (const id of prior.add ?? []) {
360
+ present.add(id);
361
+ }
362
+ } else if (prior.action === "replace") {
363
+ for (const [from, to] of Object.entries(prior.replace ?? {})) {
364
+ if (present.has(from)) {
365
+ present.delete(from);
366
+ present.add(to);
367
+ }
368
+ }
369
+ }
370
+ }
371
+ return present;
372
+ }
373
+ function everPresentSet(composition) {
374
+ const present = new Set(composition.base);
375
+ for (const rule of composition.rules) {
376
+ if (rule.action === "add") {
377
+ for (const id of rule.add ?? []) {
378
+ present.add(id);
379
+ }
380
+ } else if (rule.action === "replace") {
381
+ for (const to of Object.values(rule.replace ?? {})) {
382
+ present.add(to);
383
+ }
384
+ }
385
+ }
386
+ return present;
387
+ }
388
+ function firesWhenever(prior, current) {
389
+ for (const [key, value] of Object.entries(prior)) {
390
+ const actual = current[key];
391
+ if (actual === void 0 || !valuesEqual(actual, value)) {
392
+ return false;
393
+ }
394
+ }
395
+ return true;
396
+ }
397
+
398
+ // dist/lint/rules/example-balance.js
399
+ function exampleBalance(options = {}) {
400
+ const kind = options.kind ?? "example";
401
+ const groupTagPrefix = options.groupTagPrefix ?? "case:";
402
+ const maxSkew = options.maxSkew ?? 1;
403
+ const severity = options.severity ?? "warning";
404
+ return {
405
+ id: "example-balance",
406
+ description: `Example groups (kind "${kind}", tag "${groupTagPrefix}*") should differ by at most ${maxSkew}.`,
407
+ scope: "resolved",
408
+ check(input) {
409
+ const result = input.result;
410
+ if (result === void 0) {
411
+ return [];
412
+ }
413
+ const counts = /* @__PURE__ */ new Map();
414
+ for (const id of result.trace.finalOrder) {
415
+ const fragment = input.book.fragments.get(id);
416
+ if (fragment?.kind !== kind) {
417
+ continue;
418
+ }
419
+ for (const tag of fragment.tags ?? []) {
420
+ if (tag.startsWith(groupTagPrefix)) {
421
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
422
+ }
423
+ }
424
+ }
425
+ if (counts.size < 2) {
426
+ return [];
427
+ }
428
+ const values = [...counts.values()];
429
+ const max = Math.max(...values);
430
+ const min = Math.min(...values);
431
+ if (max - min <= maxSkew) {
432
+ return [];
433
+ }
434
+ const summary = [...counts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([group, count]) => `${group}=${count}`).join(", ");
435
+ return [
436
+ {
437
+ ruleId: "example-balance",
438
+ severity,
439
+ message: `example groups are imbalanced (${summary}); skew ${max - min} exceeds ${maxSkew}`
440
+ }
441
+ ];
442
+ }
443
+ };
444
+ }
445
+
446
+ // dist/lint/rules/language-directive-position.js
447
+ function languageDirectivePosition(options = {}) {
448
+ const kind = options.kind ?? "language-directive";
449
+ const edgeWindow = options.edgeWindow ?? 2;
450
+ const severity = options.severity ?? "warning";
451
+ return {
452
+ id: "language-directive-position",
453
+ description: `Fragments of kind "${kind}" should sit within the first/last ${edgeWindow} positions.`,
454
+ scope: "resolved",
455
+ check(input) {
456
+ const result = input.result;
457
+ if (result === void 0) {
458
+ return [];
459
+ }
460
+ const order = result.trace.finalOrder;
461
+ const total = order.length;
462
+ const findings = [];
463
+ order.forEach((id, position) => {
464
+ if (input.book.fragments.get(id)?.kind !== kind) {
465
+ return;
466
+ }
467
+ const atStart = position < edgeWindow;
468
+ const atEnd = position >= total - edgeWindow;
469
+ if (!atStart && !atEnd) {
470
+ findings.push({
471
+ ruleId: "language-directive-position",
472
+ severity,
473
+ message: `fragment "${id}" (kind "${kind}") is at position ${position + 1} of ${total}, not within the first/last ${edgeWindow}`,
474
+ fragmentId: id
475
+ });
476
+ }
477
+ });
478
+ return findings;
479
+ }
480
+ };
481
+ }
482
+
483
+ // dist/lint/rules/token-budget.js
484
+ function estimateTokensByChars(text) {
485
+ return Math.ceil(text.length / 4);
486
+ }
487
+ var DEFAULT_MAX_TOKENS = 8e3;
488
+ function tokenBudget(options = {}) {
489
+ const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
490
+ const estimate = options.estimateTokens ?? estimateTokensByChars;
491
+ const severity = options.severity ?? "warning";
492
+ return {
493
+ id: "token-budget",
494
+ description: `Resolved prompt should fit within ~${maxTokens} estimated tokens.`,
495
+ scope: "resolved",
496
+ check(input) {
497
+ const result = input.result;
498
+ if (result === void 0) {
499
+ return [];
500
+ }
501
+ const tokens = estimate(result.text);
502
+ if (tokens <= maxTokens) {
503
+ return [];
504
+ }
505
+ return [
506
+ {
507
+ ruleId: "token-budget",
508
+ severity,
509
+ message: `estimated ${tokens} tokens exceeds budget of ${maxTokens}`
510
+ }
511
+ ];
512
+ }
513
+ };
514
+ }
515
+
516
+ // dist/lint/rules/unused-fragment.js
517
+ function unusedFragment(options = {}) {
518
+ const severity = options.severity ?? "warning";
519
+ return {
520
+ id: "unused-fragment",
521
+ description: "Every fragment should be referenced by at least one composition or rule.",
522
+ scope: "book",
523
+ check(input) {
524
+ const referenced = /* @__PURE__ */ new Set();
525
+ for (const reference of iterateReferences(input.book)) {
526
+ referenced.add(reference.id);
527
+ }
528
+ const findings = [];
529
+ for (const id of [...input.book.fragments.keys()].sort()) {
530
+ if (!referenced.has(id)) {
531
+ findings.push({
532
+ ruleId: "unused-fragment",
533
+ severity,
534
+ message: `fragment "${id}" is not referenced by any composition or rule`,
535
+ fragmentId: id
536
+ });
537
+ }
538
+ }
539
+ return findings;
540
+ }
541
+ };
542
+ }
543
+
544
+ // dist/lint/rules/index.js
545
+ function defaultRules(options = {}) {
546
+ return [
547
+ tokenBudget(options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {}),
548
+ languageDirectivePosition(),
549
+ exampleBalance(),
550
+ bannedTokens(options.bannedTokens !== void 0 ? { tokens: options.bannedTokens } : {}),
551
+ unusedFragment(),
552
+ danglingReference(),
553
+ deadRule()
554
+ ];
555
+ }
556
+
557
+ // dist/lint/lint.js
558
+ function lint(input, rules = defaultRules()) {
559
+ const findings = [];
560
+ for (const rule of rules) {
561
+ if (rule.scope === "resolved" && input.result === void 0) {
562
+ continue;
563
+ }
564
+ findings.push(...rule.check(input));
565
+ }
566
+ let errorCount = 0;
567
+ let warningCount = 0;
568
+ for (const finding of findings) {
569
+ if (finding.severity === "error") {
570
+ errorCount += 1;
571
+ } else if (finding.severity === "warning") {
572
+ warningCount += 1;
573
+ }
574
+ }
575
+ return { findings, errorCount, warningCount };
576
+ }
191
577
  export {
578
+ estimateTokensByChars,
192
579
  interpolate,
580
+ iterateReferences,
581
+ lint,
193
582
  resolveBook
194
583
  };
package/dist/edge.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  /**
2
- * Self-contained entrypoint for edge / Deno runtimes that only need to resolve
3
- * an already-bundled book at request time (no folder loading). Its module graph
4
- * is just {@link resolveBook} + {@link interpolate} — zero filesystem, YAML, or
5
- * Node builtins — so `scripts/build-edge.mjs` bundles it to one portable ESM
6
- * file with no external imports. Consumers (e.g. a Supabase edge function) vendor
7
- * that file and pair it with a `promptbook bundle` book.
2
+ * Self-contained entrypoint for edge / Deno / browser runtimes that only need
3
+ * to resolve and lint an already-bundled book at request time (no folder
4
+ * loading). Its module graph touches no filesystem, YAML, or Node builtins, so
5
+ * `scripts/build-edge.mjs` bundles it to one portable ESM file with no
6
+ * external imports. Consumers (e.g. a Supabase edge function or a static
7
+ * Next.js demo) vendor that file and pair it with a `promptbook bundle` book.
8
8
  */
9
9
  export { interpolate } from "./interpolate.js";
10
+ export { lint } from "./lint/lint.js";
11
+ export type { FragmentReference } from "./lint/references.js";
12
+ export { iterateReferences } from "./lint/references.js";
13
+ export { estimateTokensByChars } from "./lint/rules/token-budget.js";
14
+ export type { LintFinding, LintInput, LintReport, LintRule, LintScope, Severity } from "./lint/types.js";
10
15
  export { resolveBook } from "./resolve-book.js";
11
- export type { Context, PromptBook, ResolveResult, Trace } from "./types.js";
16
+ export type { Composition, Context, ContextValue, Fragment, PromptBook, ResolveResult, Rule, RuleAction, Trace, When, } from "./types.js";
12
17
  //# sourceMappingURL=edge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"edge.d.ts","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"edge.d.ts","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACzG,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EACV,WAAW,EACX,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,aAAa,EACb,IAAI,EACJ,UAAU,EACV,KAAK,EACL,IAAI,GACL,MAAM,YAAY,CAAC"}
package/dist/edge.js CHANGED
@@ -1,11 +1,14 @@
1
1
  /**
2
- * Self-contained entrypoint for edge / Deno runtimes that only need to resolve
3
- * an already-bundled book at request time (no folder loading). Its module graph
4
- * is just {@link resolveBook} + {@link interpolate} — zero filesystem, YAML, or
5
- * Node builtins — so `scripts/build-edge.mjs` bundles it to one portable ESM
6
- * file with no external imports. Consumers (e.g. a Supabase edge function) vendor
7
- * that file and pair it with a `promptbook bundle` book.
2
+ * Self-contained entrypoint for edge / Deno / browser runtimes that only need
3
+ * to resolve and lint an already-bundled book at request time (no folder
4
+ * loading). Its module graph touches no filesystem, YAML, or Node builtins, so
5
+ * `scripts/build-edge.mjs` bundles it to one portable ESM file with no
6
+ * external imports. Consumers (e.g. a Supabase edge function or a static
7
+ * Next.js demo) vendor that file and pair it with a `promptbook bundle` book.
8
8
  */
9
9
  export { interpolate } from "./interpolate.js";
10
+ export { lint } from "./lint/lint.js";
11
+ export { iterateReferences } from "./lint/references.js";
12
+ export { estimateTokensByChars } from "./lint/rules/token-budget.js";
10
13
  export { resolveBook } from "./resolve-book.js";
11
14
  //# sourceMappingURL=edge.js.map
package/dist/edge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"edge.js","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"edge.js","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markbrutx/promptbook-core",
3
- "version": "0.2.0",
3
+ "version": "0.4.1",
4
4
  "description": "Agnostic, deterministic core for composing prompts from reusable fragments via declarative rules.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/bundle.ts CHANGED
@@ -95,10 +95,18 @@ function canonicalCodePrompt(codePrompt: CodePrompt): Record<string, unknown> {
95
95
  return compact(ordered);
96
96
  }
97
97
 
98
- /** Emit `new Map([...])` with one `[key, value]` entry per line (entries pre-sorted). */
98
+ /**
99
+ * Emit `new Map([...])` with one `[key, value]` entry per line (entries pre-sorted).
100
+ *
101
+ * An empty map serializes to `new Map()` (not `new Map([])`): without entries to
102
+ * infer from, `new Map([])` widens to `Map<unknown, unknown>`, which is not
103
+ * assignable to `Map<string, T>` in a plain (annotation-free) module. `new Map()`
104
+ * infers `Map<any, any>`, which stays assignable, so plain bundles type-check
105
+ * against `PromptBook` even when a map is empty.
106
+ */
99
107
  function serializeMap<T>(entries: [string, T][], canonical: (value: T) => unknown): string {
100
108
  if (entries.length === 0) {
101
- return "new Map([])";
109
+ return "new Map()";
102
110
  }
103
111
  const lines = entries.map(
104
112
  ([key, value]) => ` [${JSON.stringify(key)}, ${JSON.stringify(canonical(value))}],`,
package/src/edge.ts CHANGED
@@ -1,11 +1,27 @@
1
1
  /**
2
- * Self-contained entrypoint for edge / Deno runtimes that only need to resolve
3
- * an already-bundled book at request time (no folder loading). Its module graph
4
- * is just {@link resolveBook} + {@link interpolate} — zero filesystem, YAML, or
5
- * Node builtins — so `scripts/build-edge.mjs` bundles it to one portable ESM
6
- * file with no external imports. Consumers (e.g. a Supabase edge function) vendor
7
- * that file and pair it with a `promptbook bundle` book.
2
+ * Self-contained entrypoint for edge / Deno / browser runtimes that only need
3
+ * to resolve and lint an already-bundled book at request time (no folder
4
+ * loading). Its module graph touches no filesystem, YAML, or Node builtins, so
5
+ * `scripts/build-edge.mjs` bundles it to one portable ESM file with no
6
+ * external imports. Consumers (e.g. a Supabase edge function or a static
7
+ * Next.js demo) vendor that file and pair it with a `promptbook bundle` book.
8
8
  */
9
9
  export { interpolate } from "./interpolate.js";
10
+ export { lint } from "./lint/lint.js";
11
+ export type { FragmentReference } from "./lint/references.js";
12
+ export { iterateReferences } from "./lint/references.js";
13
+ export { estimateTokensByChars } from "./lint/rules/token-budget.js";
14
+ export type { LintFinding, LintInput, LintReport, LintRule, LintScope, Severity } from "./lint/types.js";
10
15
  export { resolveBook } from "./resolve-book.js";
11
- export type { Context, PromptBook, ResolveResult, Trace } from "./types.js";
16
+ export type {
17
+ Composition,
18
+ Context,
19
+ ContextValue,
20
+ Fragment,
21
+ PromptBook,
22
+ ResolveResult,
23
+ Rule,
24
+ RuleAction,
25
+ Trace,
26
+ When,
27
+ } from "./types.js";