@markbrutx/promptbook-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/annotations.d.ts +56 -0
  4. package/dist/annotations.d.ts.map +1 -0
  5. package/dist/annotations.js +50 -0
  6. package/dist/annotations.js.map +1 -0
  7. package/dist/bundle.d.ts +44 -0
  8. package/dist/bundle.d.ts.map +1 -0
  9. package/dist/bundle.js +135 -0
  10. package/dist/bundle.js.map +1 -0
  11. package/dist/edge/index.js +192 -0
  12. package/dist/edge.d.ts +12 -0
  13. package/dist/edge.d.ts.map +1 -0
  14. package/dist/edge.js +11 -0
  15. package/dist/edge.js.map +1 -0
  16. package/dist/eval/assertions.d.ts +15 -0
  17. package/dist/eval/assertions.d.ts.map +1 -0
  18. package/dist/eval/assertions.js +131 -0
  19. package/dist/eval/assertions.js.map +1 -0
  20. package/dist/eval/evaluate.d.ts +15 -0
  21. package/dist/eval/evaluate.d.ts.map +1 -0
  22. package/dist/eval/evaluate.js +65 -0
  23. package/dist/eval/evaluate.js.map +1 -0
  24. package/dist/eval/load-fixtures.d.ts +12 -0
  25. package/dist/eval/load-fixtures.d.ts.map +1 -0
  26. package/dist/eval/load-fixtures.js +87 -0
  27. package/dist/eval/load-fixtures.js.map +1 -0
  28. package/dist/eval/types.d.ts +123 -0
  29. package/dist/eval/types.d.ts.map +1 -0
  30. package/dist/eval/types.js +2 -0
  31. package/dist/eval/types.js.map +1 -0
  32. package/dist/frontmatter.d.ts +12 -0
  33. package/dist/frontmatter.d.ts.map +1 -0
  34. package/dist/frontmatter.js +22 -0
  35. package/dist/frontmatter.js.map +1 -0
  36. package/dist/fs.d.ts +11 -0
  37. package/dist/fs.d.ts.map +1 -0
  38. package/dist/fs.js +20 -0
  39. package/dist/fs.js.map +1 -0
  40. package/dist/guards.d.ts +6 -0
  41. package/dist/guards.d.ts.map +1 -0
  42. package/dist/guards.js +9 -0
  43. package/dist/guards.js.map +1 -0
  44. package/dist/index.d.ts +22 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +15 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/interpolate.d.ts +11 -0
  49. package/dist/interpolate.d.ts.map +1 -0
  50. package/dist/interpolate.js +25 -0
  51. package/dist/interpolate.js.map +1 -0
  52. package/dist/lint/lint.d.ts +11 -0
  53. package/dist/lint/lint.d.ts.map +1 -0
  54. package/dist/lint/lint.js +30 -0
  55. package/dist/lint/lint.js.map +1 -0
  56. package/dist/lint/references.d.ts +18 -0
  57. package/dist/lint/references.d.ts.map +1 -0
  58. package/dist/lint/references.js +39 -0
  59. package/dist/lint/references.js.map +1 -0
  60. package/dist/lint/rules/banned-tokens.d.ts +13 -0
  61. package/dist/lint/rules/banned-tokens.d.ts.map +1 -0
  62. package/dist/lint/rules/banned-tokens.js +38 -0
  63. package/dist/lint/rules/banned-tokens.js.map +1 -0
  64. package/dist/lint/rules/dangling-reference.d.ts +11 -0
  65. package/dist/lint/rules/dangling-reference.d.ts.map +1 -0
  66. package/dist/lint/rules/dangling-reference.js +37 -0
  67. package/dist/lint/rules/dangling-reference.js.map +1 -0
  68. package/dist/lint/rules/dead-rule.d.ts +21 -0
  69. package/dist/lint/rules/dead-rule.d.ts.map +1 -0
  70. package/dist/lint/rules/dead-rule.js +135 -0
  71. package/dist/lint/rules/dead-rule.js.map +1 -0
  72. package/dist/lint/rules/example-balance.d.ts +19 -0
  73. package/dist/lint/rules/example-balance.d.ts.map +1 -0
  74. package/dist/lint/rules/example-balance.js +57 -0
  75. package/dist/lint/rules/example-balance.js.map +1 -0
  76. package/dist/lint/rules/index.d.ts +28 -0
  77. package/dist/lint/rules/index.d.ts.map +1 -0
  78. package/dist/lint/rules/index.js +30 -0
  79. package/dist/lint/rules/index.js.map +1 -0
  80. package/dist/lint/rules/language-directive-position.d.ts +16 -0
  81. package/dist/lint/rules/language-directive-position.d.ts.map +1 -0
  82. package/dist/lint/rules/language-directive-position.js +42 -0
  83. package/dist/lint/rules/language-directive-position.js.map +1 -0
  84. package/dist/lint/rules/token-budget.d.ts +18 -0
  85. package/dist/lint/rules/token-budget.d.ts.map +1 -0
  86. package/dist/lint/rules/token-budget.js +39 -0
  87. package/dist/lint/rules/token-budget.js.map +1 -0
  88. package/dist/lint/rules/unused-fragment.d.ts +11 -0
  89. package/dist/lint/rules/unused-fragment.d.ts.map +1 -0
  90. package/dist/lint/rules/unused-fragment.js +33 -0
  91. package/dist/lint/rules/unused-fragment.js.map +1 -0
  92. package/dist/lint/types.d.ts +50 -0
  93. package/dist/lint/types.d.ts.map +1 -0
  94. package/dist/lint/types.js +2 -0
  95. package/dist/lint/types.js.map +1 -0
  96. package/dist/load.d.ts +12 -0
  97. package/dist/load.d.ts.map +1 -0
  98. package/dist/load.js +238 -0
  99. package/dist/load.js.map +1 -0
  100. package/dist/paths.d.ts +12 -0
  101. package/dist/paths.d.ts.map +1 -0
  102. package/dist/paths.js +25 -0
  103. package/dist/paths.js.map +1 -0
  104. package/dist/resolve-book.d.ts +15 -0
  105. package/dist/resolve-book.d.ts.map +1 -0
  106. package/dist/resolve-book.js +195 -0
  107. package/dist/resolve-book.js.map +1 -0
  108. package/dist/resolve.d.ts +13 -0
  109. package/dist/resolve.d.ts.map +1 -0
  110. package/dist/resolve.js +17 -0
  111. package/dist/resolve.js.map +1 -0
  112. package/dist/types.d.ts +173 -0
  113. package/dist/types.d.ts.map +1 -0
  114. package/dist/types.js +9 -0
  115. package/dist/types.js.map +1 -0
  116. package/package.json +48 -0
  117. package/src/annotations.ts +100 -0
  118. package/src/bundle.ts +163 -0
  119. package/src/edge.ts +11 -0
  120. package/src/eval/assertions.ts +174 -0
  121. package/src/eval/evaluate.ts +84 -0
  122. package/src/eval/load-fixtures.ts +91 -0
  123. package/src/eval/types.ts +134 -0
  124. package/src/frontmatter.ts +28 -0
  125. package/src/fs.ts +21 -0
  126. package/src/guards.ts +11 -0
  127. package/src/index.ts +84 -0
  128. package/src/interpolate.ts +27 -0
  129. package/src/lint/lint.ts +32 -0
  130. package/src/lint/references.ts +50 -0
  131. package/src/lint/rules/banned-tokens.ts +46 -0
  132. package/src/lint/rules/dangling-reference.ts +43 -0
  133. package/src/lint/rules/dead-rule.ts +147 -0
  134. package/src/lint/rules/example-balance.ts +68 -0
  135. package/src/lint/rules/index.ts +47 -0
  136. package/src/lint/rules/language-directive-position.ts +51 -0
  137. package/src/lint/rules/token-budget.ts +50 -0
  138. package/src/lint/rules/unused-fragment.ts +38 -0
  139. package/src/lint/types.ts +55 -0
  140. package/src/load.ts +282 -0
  141. package/src/paths.ts +27 -0
  142. package/src/resolve-book.ts +237 -0
  143. package/src/resolve.ts +18 -0
  144. package/src/types.ts +191 -0
@@ -0,0 +1,195 @@
1
+ import { interpolate } from "./interpolate.js";
2
+ /**
3
+ * Pure resolver over an already-loaded {@link PromptBook}. No filesystem, no
4
+ * YAML, no Node builtins — the entire dependency footprint is {@link interpolate}
5
+ * and type-only imports. This is the determinism boundary: given the same book
6
+ * and context, the returned `text` is byte-for-byte stable, and the module
7
+ * bundles to a self-contained artifact for edge runtimes (see `edge.ts`).
8
+ *
9
+ * Conflict strategy: rules apply in declaration order, later wins (cascade).
10
+ * `forbid` is the one exception — it is applied as a final filter and always
11
+ * wins, so a forbidden id never reaches the output even if added earlier.
12
+ */
13
+ export function resolveBook(book, prompt, context) {
14
+ const composition = book.compositions.get(prompt);
15
+ if (!composition) {
16
+ const available = [...book.compositions.keys()].sort().join(", ") || "(none)";
17
+ throw new Error(`Unknown prompt "${prompt}". Available compositions: ${available}.`);
18
+ }
19
+ const warnings = [...book.warnings];
20
+ const ruleTraces = [];
21
+ const replaced = [];
22
+ const added = [];
23
+ const forbidden = [];
24
+ const forbiddenIds = new Set();
25
+ const working = [...composition.base];
26
+ let orderOverride = composition.order ? [...composition.order] : undefined;
27
+ for (const rule of composition.rules) {
28
+ const match = matchWhen(rule.when, context);
29
+ const trace = {
30
+ index: rule.index,
31
+ action: rule.action,
32
+ when: rule.when,
33
+ fired: match.fired,
34
+ };
35
+ if (!match.fired) {
36
+ trace.reason = match.reason;
37
+ ruleTraces.push(trace);
38
+ continue;
39
+ }
40
+ switch (rule.action) {
41
+ case "replace": {
42
+ const effects = [];
43
+ for (const [from, to] of Object.entries(rule.replace ?? {})) {
44
+ const at = working.indexOf(from);
45
+ if (at === -1) {
46
+ warnings.push(`Rule #${rule.index} cannot replace "${from}": it is not present in "${prompt}".`);
47
+ continue;
48
+ }
49
+ working[at] = to;
50
+ replaced.push({ from, to, ruleIndex: rule.index });
51
+ effects.push(`${from} -> ${to}`);
52
+ }
53
+ trace.effect = `replace ${effects.join(", ")}`;
54
+ break;
55
+ }
56
+ case "add": {
57
+ const ids = rule.add ?? [];
58
+ const after = rule.after;
59
+ const at = insertionPoint(working, after, rule.index, prompt, warnings);
60
+ working.splice(at, 0, ...ids);
61
+ for (const id of ids) {
62
+ added.push(after !== undefined ? { id, after, ruleIndex: rule.index } : { id, ruleIndex: rule.index });
63
+ }
64
+ trace.effect = `add ${ids.join(", ")}${after !== undefined ? ` after ${after}` : ""}`;
65
+ break;
66
+ }
67
+ case "forbid": {
68
+ const ids = rule.forbid ?? [];
69
+ for (const id of ids) {
70
+ forbiddenIds.add(id);
71
+ forbidden.push({ id, ruleIndex: rule.index });
72
+ }
73
+ trace.effect = `forbid ${ids.join(", ")}`;
74
+ break;
75
+ }
76
+ case "order": {
77
+ orderOverride = [...(rule.order ?? [])];
78
+ trace.effect = `order ${orderOverride.join(", ")}`;
79
+ break;
80
+ }
81
+ }
82
+ ruleTraces.push(trace);
83
+ }
84
+ const ordered = applyOrder(working, orderOverride);
85
+ const selected = ordered.filter((id) => !forbiddenIds.has(id));
86
+ const parts = [];
87
+ const finalOrder = [];
88
+ for (const id of selected) {
89
+ const fragment = book.fragments.get(id);
90
+ if (!fragment) {
91
+ warnings.push(`Fragment "${id}" referenced by "${prompt}" was not found.`);
92
+ continue;
93
+ }
94
+ parts.push(interpolate(fragment.body, context, (key) => {
95
+ warnings.push(`Missing variable "${key}" while rendering fragment "${id}".`);
96
+ }));
97
+ finalOrder.push(id);
98
+ }
99
+ const trace = {
100
+ prompt,
101
+ context,
102
+ rules: ruleTraces,
103
+ finalOrder,
104
+ replaced,
105
+ added,
106
+ forbidden,
107
+ unmatchedAxes: computeUnmatchedAxes(composition, context),
108
+ warnings,
109
+ };
110
+ return { text: parts.join("\n\n"), trace };
111
+ }
112
+ function matchWhen(when, context) {
113
+ for (const [key, expected] of Object.entries(when)) {
114
+ const actual = context[key];
115
+ if (actual === undefined) {
116
+ return { fired: false, reason: `context.${key} is unset, rule expects "${expected}"` };
117
+ }
118
+ if (!valuesEqual(expected, actual)) {
119
+ return { fired: false, reason: `context.${key}="${actual}" does not equal expected "${expected}"` };
120
+ }
121
+ }
122
+ return { fired: true };
123
+ }
124
+ export function valuesEqual(a, b) {
125
+ return String(a) === String(b);
126
+ }
127
+ /**
128
+ * Decide where an `add` inserts. With `after`, just past that id; without it,
129
+ * before the last fragment (so trailing format/footer fragments stay last).
130
+ */
131
+ function insertionPoint(working, after, ruleIndex, prompt, warnings) {
132
+ if (after === undefined) {
133
+ return Math.max(working.length - 1, 0);
134
+ }
135
+ const at = working.indexOf(after);
136
+ if (at === -1) {
137
+ warnings.push(`Rule #${ruleIndex} add anchor "${after}" not found in "${prompt}"; appending at end.`);
138
+ return working.length;
139
+ }
140
+ return at + 1;
141
+ }
142
+ /**
143
+ * Produce the final id sequence. With an `order` override, listed ids come
144
+ * first (those that are present), then any remaining working ids in their
145
+ * existing order. Without an override, the working order is used as-is.
146
+ * Duplicates are collapsed to their first occurrence.
147
+ */
148
+ function applyOrder(working, orderOverride) {
149
+ const seen = new Set();
150
+ const result = [];
151
+ const push = (id) => {
152
+ if (!seen.has(id)) {
153
+ seen.add(id);
154
+ result.push(id);
155
+ }
156
+ };
157
+ if (orderOverride) {
158
+ const present = new Set(working);
159
+ for (const id of orderOverride) {
160
+ if (present.has(id)) {
161
+ push(id);
162
+ }
163
+ }
164
+ }
165
+ for (const id of working) {
166
+ push(id);
167
+ }
168
+ return result;
169
+ }
170
+ /**
171
+ * Find context axes that some rule referenced but matched none. This surfaces
172
+ * holes like `industry=zoo` where industry rules exist but none cover "zoo".
173
+ */
174
+ function computeUnmatchedAxes(composition, context) {
175
+ const result = [];
176
+ for (const key of Object.keys(context)) {
177
+ let referenced = false;
178
+ let matched = false;
179
+ for (const rule of composition.rules) {
180
+ if (Object.hasOwn(rule.when, key)) {
181
+ referenced = true;
182
+ const expected = rule.when[key];
183
+ if (expected !== undefined && valuesEqual(expected, context[key])) {
184
+ matched = true;
185
+ break;
186
+ }
187
+ }
188
+ }
189
+ if (referenced && !matched) {
190
+ result.push({ key, value: context[key] });
191
+ }
192
+ }
193
+ return result;
194
+ }
195
+ //# sourceMappingURL=resolve-book.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-book.js","sourceRoot":"","sources":["../src/resolve-book.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ResolveInput, ResolveResult } from "./types.js";
2
+ /**
3
+ * Assemble a prompt for a context and return the text plus an explain trace.
4
+ *
5
+ * Loads the prompts folder (the one IO step), then delegates to the pure
6
+ * {@link resolveBook}. Given the same folder contents and input, the returned
7
+ * `text` is byte-for-byte stable.
8
+ *
9
+ * Each call re-reads the folder. For many resolves against the same folder,
10
+ * call {@link loadPrompts} once and reuse {@link resolveBook}.
11
+ */
12
+ export declare function resolve(input: ResolveInput): Promise<ResolveResult>;
13
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE9D;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAGzE"}
@@ -0,0 +1,17 @@
1
+ import { loadPrompts } from "./load.js";
2
+ import { resolveBook } from "./resolve-book.js";
3
+ /**
4
+ * Assemble a prompt for a context and return the text plus an explain trace.
5
+ *
6
+ * Loads the prompts folder (the one IO step), then delegates to the pure
7
+ * {@link resolveBook}. Given the same folder contents and input, the returned
8
+ * `text` is byte-for-byte stable.
9
+ *
10
+ * Each call re-reads the folder. For many resolves against the same folder,
11
+ * call {@link loadPrompts} once and reuse {@link resolveBook}.
12
+ */
13
+ export async function resolve(input) {
14
+ const book = await loadPrompts(input.promptsDir, input.fs);
15
+ return resolveBook(book, input.prompt, input.context ?? {});
16
+ }
17
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAmB;IAC/C,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Public data model for the promptbook core.
3
+ *
4
+ * The core is domain-agnostic: it only selects, orders and interpolates
5
+ * fragments. Any computed value (digests, scores, sorted lists) must be
6
+ * supplied by the caller through {@link Context}.
7
+ */
8
+ /** A scalar context value. The core never stores domain objects. */
9
+ export type ContextValue = string | number | boolean;
10
+ /** A flat bag of facts the caller knows at resolve time. */
11
+ export type Context = Record<string, ContextValue>;
12
+ /** A reusable micro-prompt (the WHAT). */
13
+ export interface Fragment {
14
+ /** Unique id within a prompts folder. */
15
+ id: string;
16
+ /** Optional classification, e.g. "directive" or "format". */
17
+ kind?: string;
18
+ /** Optional free-form tags. */
19
+ tags?: string[];
20
+ /** Body text; may contain `${path}` placeholders. */
21
+ body: string;
22
+ /** Absolute/relative path the fragment was loaded from. */
23
+ sourceFile: string;
24
+ }
25
+ /** Equality conditions on context. An empty object always matches. */
26
+ export type When = Record<string, ContextValue>;
27
+ /** The single action a rule performs. */
28
+ export type RuleAction = "add" | "replace" | "forbid" | "order";
29
+ /**
30
+ * A declarative selection rule (the WHEN). Exactly one action field is set,
31
+ * matching {@link RuleAction}.
32
+ */
33
+ export interface Rule {
34
+ /** Position in the composition's `rules` list; drives the cascade. */
35
+ index: number;
36
+ /** Match conditions. Empty = always fires. */
37
+ when: When;
38
+ /** Which action this rule performs. */
39
+ action: RuleAction;
40
+ /** ids to insert (action "add"). */
41
+ add?: string[];
42
+ /** Optional anchor: insert added ids after this id. */
43
+ after?: string;
44
+ /** Map of old id to new id (action "replace"). */
45
+ replace?: Record<string, string>;
46
+ /** ids to remove from the result (action "forbid"). */
47
+ forbid?: string[];
48
+ /** Explicit final order of ids (action "order"). */
49
+ order?: string[];
50
+ }
51
+ /** A full system prompt assembled from fragments under one context. */
52
+ export interface Composition {
53
+ /** Lookup name, e.g. "assistant". */
54
+ name: string;
55
+ /** Ordered base list of fragment ids. */
56
+ base: string[];
57
+ /** Optional explicit order override declared on the composition. */
58
+ order?: string[];
59
+ /** Ordered selection rules. */
60
+ rules: Rule[];
61
+ /** File the composition was loaded from. */
62
+ sourceFile: string;
63
+ }
64
+ /**
65
+ * A captured snapshot of a builder-backed prompt's output. The core never
66
+ * runs the builder; it only stores the frozen text so the registry can show it.
67
+ */
68
+ export interface CodePromptSample {
69
+ /** Label identifying the sample, e.g. a scenario name. */
70
+ label: string;
71
+ /** Optional context the sample was captured under. */
72
+ context?: Context;
73
+ /** The captured builder output, stored verbatim. */
74
+ output: string;
75
+ }
76
+ /**
77
+ * A builder-backed prompt registered in the book (the HOW lives in code, not in
78
+ * rules). The core holds metadata plus captured {@link CodePromptSample}s and
79
+ * **never executes the builder** — it stays dumb and deterministic. This lets a
80
+ * book index every prompt of a domain, computed ones included, as one menu.
81
+ */
82
+ export interface CodePrompt {
83
+ /** Lookup name, e.g. "digest-table". */
84
+ name: string;
85
+ /** Optional human description. */
86
+ description?: string;
87
+ /** Captured output samples (snapshots, not live executions). */
88
+ samples: CodePromptSample[];
89
+ /** Manifest file the code-prompt was loaded from. */
90
+ sourceFile: string;
91
+ }
92
+ /** A loaded prompts folder: fragments + compositions + code-prompts + load-time warnings. */
93
+ export interface PromptBook {
94
+ fragments: Map<string, Fragment>;
95
+ compositions: Map<string, Composition>;
96
+ /** Builder-backed prompts registered with snapshot output (never executed). */
97
+ codePrompts: Map<string, CodePrompt>;
98
+ warnings: string[];
99
+ }
100
+ /** Per-rule entry in the explain trace. */
101
+ export interface RuleTrace {
102
+ index: number;
103
+ action: RuleAction;
104
+ when: When;
105
+ /** Whether the rule's `when` matched the context. */
106
+ fired: boolean;
107
+ /** When `fired` is false, why (which key failed). */
108
+ reason?: string;
109
+ /** When `fired` is true, a human-readable summary of the effect. */
110
+ effect?: string;
111
+ }
112
+ export interface ReplaceTrace {
113
+ from: string;
114
+ to: string;
115
+ ruleIndex: number;
116
+ }
117
+ export interface AddTrace {
118
+ id: string;
119
+ after?: string;
120
+ ruleIndex: number;
121
+ }
122
+ export interface ForbidTrace {
123
+ id: string;
124
+ ruleIndex: number;
125
+ }
126
+ /** A context axis that rules referenced but none matched its value. */
127
+ export interface UnmatchedAxis {
128
+ key: string;
129
+ value: ContextValue;
130
+ }
131
+ /** The explain trace: everything needed to see why the prompt looks as it does. */
132
+ export interface Trace {
133
+ /** Composition name that was resolved. */
134
+ prompt: string;
135
+ /** Context the resolution ran against. */
136
+ context: Context;
137
+ /** Every rule, in declaration order, with fired/why. */
138
+ rules: RuleTrace[];
139
+ /** Final fragment ids in join order (only fragments that rendered). */
140
+ finalOrder: string[];
141
+ /** Replacements applied. */
142
+ replaced: ReplaceTrace[];
143
+ /** Additions applied. */
144
+ added: AddTrace[];
145
+ /** Forbids applied. */
146
+ forbidden: ForbidTrace[];
147
+ /** Axes present in context that no rule matched (e.g. unknown industry). */
148
+ unmatchedAxes: UnmatchedAxis[];
149
+ /** Missing variables, missing fragment refs, and other non-fatal issues. */
150
+ warnings: string[];
151
+ }
152
+ /** The result of {@link resolve}. */
153
+ export interface ResolveResult {
154
+ text: string;
155
+ trace: Trace;
156
+ }
157
+ /** Injectable filesystem so the core stays runtime-agnostic (Node/Deno/Bun). */
158
+ export interface FsAdapter {
159
+ readFile(path: string): Promise<string>;
160
+ readDir(path: string): Promise<string[]>;
161
+ }
162
+ /** Input to {@link resolve}. */
163
+ export interface ResolveInput {
164
+ /** Folder containing `fragments/` and `rules/`. */
165
+ promptsDir: string;
166
+ /** Composition name to assemble. */
167
+ prompt: string;
168
+ /** Facts to match rules against and interpolate into bodies. */
169
+ context?: Context;
170
+ /** Optional filesystem adapter; defaults to a lazy Node adapter. */
171
+ fs?: FsAdapter;
172
+ }
173
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Public data model for the promptbook core.
3
+ *
4
+ * The core is domain-agnostic: it only selects, orders and interpolates
5
+ * fragments. Any computed value (digests, scores, sorted lists) must be
6
+ * supplied by the caller through {@link Context}.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@markbrutx/promptbook-core",
3
+ "version": "0.1.0",
4
+ "description": "Agnostic, deterministic core for composing prompts from reusable fragments via declarative rules.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./edge": {
13
+ "types": "./dist/edge.d.ts",
14
+ "import": "./dist/edge/index.js"
15
+ }
16
+ },
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "sideEffects": false,
27
+ "scripts": {
28
+ "build": "tsgo -p tsconfig.build.json && npm run build:edge",
29
+ "build:edge": "node scripts/build-edge.mjs",
30
+ "typecheck": "tsgo --noEmit -p tsconfig.json",
31
+ "test": "vitest --run",
32
+ "check": "biome check ."
33
+ },
34
+ "dependencies": {
35
+ "yaml": "^2.5.0"
36
+ },
37
+ "devDependencies": {
38
+ "@biomejs/biome": "latest",
39
+ "@types/node": "^20.14.0",
40
+ "esbuild": "^0.21.5",
41
+ "@typescript/native-preview": "latest",
42
+ "typescript": "^5.6.0",
43
+ "vitest": "^2.1.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=20.6"
47
+ }
48
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The viewer→agent feedback queue: the annotation data shape plus the JSONL
3
+ * (de)serialization both ends share. The on-disk file is the cross-process
4
+ * contract — the viewer writes it, an agent's CLI reads and clears it — so the
5
+ * schema lives in one place. These helpers are pure and deterministic; the
6
+ * non-deterministic parts (id/timestamp generation, disk IO) live in the
7
+ * writer, not here.
8
+ */
9
+ import type { Context } from "./types.js";
10
+
11
+ /** Queue directory, relative to the prompts folder — the cross-process contract. */
12
+ export const ANNOTATION_QUEUE_DIR = ".annotations";
13
+ /** Queue file inside {@link ANNOTATION_QUEUE_DIR}, one annotation per line. */
14
+ export const ANNOTATION_QUEUE_FILE = "inbox.jsonl";
15
+
16
+ /** An annotation is open until an agent resolves it. */
17
+ export type AnnotationStatus = "open" | "resolved";
18
+
19
+ /** What an annotation points at: a composition variant or a standalone fragment. */
20
+ export interface AnnotationTarget {
21
+ /** Composition the annotated variant was assembled from. */
22
+ prompt?: string;
23
+ /** Context the variant was resolved under (present alongside `prompt`). */
24
+ context?: Context;
25
+ /** A fragment id, when annotating a fragment directly rather than a variant. */
26
+ fragmentId?: string;
27
+ /** Source file of the fragment/composition, to help the agent locate it. */
28
+ sourceFile?: string;
29
+ }
30
+
31
+ /** Where, inside the source text, the comment is anchored. */
32
+ export interface AnnotationAnchor {
33
+ /** Fragment whose body carries the selected text. */
34
+ fragmentId: string;
35
+ /** The exact text the user selected. */
36
+ anchorText: string;
37
+ /** Optional character offset within the fragment body, for disambiguation. */
38
+ offset?: number;
39
+ }
40
+
41
+ /** One queued piece of human feedback, waiting for an agent to act on it. */
42
+ export interface Annotation {
43
+ id: string;
44
+ /** ISO-8601 creation time. */
45
+ createdAt: string;
46
+ target: AnnotationTarget;
47
+ anchor: AnnotationAnchor;
48
+ comment: string;
49
+ status: AnnotationStatus;
50
+ }
51
+
52
+ /** True when `value` has the minimal required shape of an {@link Annotation}. */
53
+ function isAnnotation(value: unknown): value is Annotation {
54
+ if (typeof value !== "object" || value === null) {
55
+ return false;
56
+ }
57
+ const record = value as Record<string, unknown>;
58
+ const anchor = record.anchor as Record<string, unknown> | undefined;
59
+ return (
60
+ typeof record.id === "string" &&
61
+ typeof record.comment === "string" &&
62
+ typeof anchor === "object" &&
63
+ anchor !== null &&
64
+ typeof anchor.fragmentId === "string"
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Parse the inbox JSONL: one annotation per non-empty line. Malformed lines are
70
+ * skipped rather than thrown — the queue must never crash on data, so a single
71
+ * bad line cannot block the rest.
72
+ */
73
+ export function parseInbox(text: string): Annotation[] {
74
+ const annotations: Annotation[] = [];
75
+ for (const line of text.split(/\r?\n/)) {
76
+ const trimmed = line.trim();
77
+ if (trimmed === "") {
78
+ continue;
79
+ }
80
+ try {
81
+ const parsed: unknown = JSON.parse(trimmed);
82
+ if (isAnnotation(parsed)) {
83
+ annotations.push(parsed);
84
+ }
85
+ } catch {
86
+ // skip a malformed line; the rest of the queue stays usable
87
+ }
88
+ }
89
+ return annotations;
90
+ }
91
+
92
+ /** Serialize one annotation as a single append-safe JSONL line (with newline). */
93
+ export function serializeAnnotationLine(annotation: Annotation): string {
94
+ return `${JSON.stringify(annotation)}\n`;
95
+ }
96
+
97
+ /** Serialize a whole queue back to JSONL, used when removing/rewriting entries. */
98
+ export function serializeInbox(annotations: Annotation[]): string {
99
+ return annotations.map(serializeAnnotationLine).join("");
100
+ }