@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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/annotations.d.ts +56 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/annotations.js +50 -0
- package/dist/annotations.js.map +1 -0
- package/dist/bundle.d.ts +44 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +135 -0
- package/dist/bundle.js.map +1 -0
- package/dist/edge/index.js +192 -0
- package/dist/edge.d.ts +12 -0
- package/dist/edge.d.ts.map +1 -0
- package/dist/edge.js +11 -0
- package/dist/edge.js.map +1 -0
- package/dist/eval/assertions.d.ts +15 -0
- package/dist/eval/assertions.d.ts.map +1 -0
- package/dist/eval/assertions.js +131 -0
- package/dist/eval/assertions.js.map +1 -0
- package/dist/eval/evaluate.d.ts +15 -0
- package/dist/eval/evaluate.d.ts.map +1 -0
- package/dist/eval/evaluate.js +65 -0
- package/dist/eval/evaluate.js.map +1 -0
- package/dist/eval/load-fixtures.d.ts +12 -0
- package/dist/eval/load-fixtures.d.ts.map +1 -0
- package/dist/eval/load-fixtures.js +87 -0
- package/dist/eval/load-fixtures.js.map +1 -0
- package/dist/eval/types.d.ts +123 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +2 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/frontmatter.d.ts +12 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +22 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/fs.d.ts +11 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +20 -0
- package/dist/fs.js.map +1 -0
- package/dist/guards.d.ts +6 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +9 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate.d.ts +11 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/interpolate.js +25 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/lint/lint.d.ts +11 -0
- package/dist/lint/lint.d.ts.map +1 -0
- package/dist/lint/lint.js +30 -0
- package/dist/lint/lint.js.map +1 -0
- package/dist/lint/references.d.ts +18 -0
- package/dist/lint/references.d.ts.map +1 -0
- package/dist/lint/references.js +39 -0
- package/dist/lint/references.js.map +1 -0
- package/dist/lint/rules/banned-tokens.d.ts +13 -0
- package/dist/lint/rules/banned-tokens.d.ts.map +1 -0
- package/dist/lint/rules/banned-tokens.js +38 -0
- package/dist/lint/rules/banned-tokens.js.map +1 -0
- package/dist/lint/rules/dangling-reference.d.ts +11 -0
- package/dist/lint/rules/dangling-reference.d.ts.map +1 -0
- package/dist/lint/rules/dangling-reference.js +37 -0
- package/dist/lint/rules/dangling-reference.js.map +1 -0
- package/dist/lint/rules/dead-rule.d.ts +21 -0
- package/dist/lint/rules/dead-rule.d.ts.map +1 -0
- package/dist/lint/rules/dead-rule.js +135 -0
- package/dist/lint/rules/dead-rule.js.map +1 -0
- package/dist/lint/rules/example-balance.d.ts +19 -0
- package/dist/lint/rules/example-balance.d.ts.map +1 -0
- package/dist/lint/rules/example-balance.js +57 -0
- package/dist/lint/rules/example-balance.js.map +1 -0
- package/dist/lint/rules/index.d.ts +28 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/index.js +30 -0
- package/dist/lint/rules/index.js.map +1 -0
- package/dist/lint/rules/language-directive-position.d.ts +16 -0
- package/dist/lint/rules/language-directive-position.d.ts.map +1 -0
- package/dist/lint/rules/language-directive-position.js +42 -0
- package/dist/lint/rules/language-directive-position.js.map +1 -0
- package/dist/lint/rules/token-budget.d.ts +18 -0
- package/dist/lint/rules/token-budget.d.ts.map +1 -0
- package/dist/lint/rules/token-budget.js +39 -0
- package/dist/lint/rules/token-budget.js.map +1 -0
- package/dist/lint/rules/unused-fragment.d.ts +11 -0
- package/dist/lint/rules/unused-fragment.d.ts.map +1 -0
- package/dist/lint/rules/unused-fragment.js +33 -0
- package/dist/lint/rules/unused-fragment.js.map +1 -0
- package/dist/lint/types.d.ts +50 -0
- package/dist/lint/types.d.ts.map +1 -0
- package/dist/lint/types.js +2 -0
- package/dist/lint/types.js.map +1 -0
- package/dist/load.d.ts +12 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +238 -0
- package/dist/load.js.map +1 -0
- package/dist/paths.d.ts +12 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +25 -0
- package/dist/paths.js.map +1 -0
- package/dist/resolve-book.d.ts +15 -0
- package/dist/resolve-book.d.ts.map +1 -0
- package/dist/resolve-book.js +195 -0
- package/dist/resolve-book.js.map +1 -0
- package/dist/resolve.d.ts +13 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +17 -0
- package/dist/resolve.js.map +1 -0
- package/dist/types.d.ts +173 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/annotations.ts +100 -0
- package/src/bundle.ts +163 -0
- package/src/edge.ts +11 -0
- package/src/eval/assertions.ts +174 -0
- package/src/eval/evaluate.ts +84 -0
- package/src/eval/load-fixtures.ts +91 -0
- package/src/eval/types.ts +134 -0
- package/src/frontmatter.ts +28 -0
- package/src/fs.ts +21 -0
- package/src/guards.ts +11 -0
- package/src/index.ts +84 -0
- package/src/interpolate.ts +27 -0
- package/src/lint/lint.ts +32 -0
- package/src/lint/references.ts +50 -0
- package/src/lint/rules/banned-tokens.ts +46 -0
- package/src/lint/rules/dangling-reference.ts +43 -0
- package/src/lint/rules/dead-rule.ts +147 -0
- package/src/lint/rules/example-balance.ts +68 -0
- package/src/lint/rules/index.ts +47 -0
- package/src/lint/rules/language-directive-position.ts +51 -0
- package/src/lint/rules/token-budget.ts +50 -0
- package/src/lint/rules/unused-fragment.ts +38 -0
- package/src/lint/types.ts +55 -0
- package/src/load.ts +282 -0
- package/src/paths.ts +27 -0
- package/src/resolve-book.ts +237 -0
- package/src/resolve.ts +18 -0
- package/src/types.ts +191 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 markbrutx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @markbrutx/promptbook-core
|
|
2
|
+
|
|
3
|
+
Agnostic, deterministic core for composing prompts from reusable fragments via
|
|
4
|
+
declarative rules. No CLI, no UI, no domain knowledge — it only **selects**,
|
|
5
|
+
**orders** and **interpolates** fragments, and explains what it did.
|
|
6
|
+
|
|
7
|
+
## Model
|
|
8
|
+
|
|
9
|
+
- **fragment** (WHAT) — a reusable micro-prompt: a Markdown file with YAML
|
|
10
|
+
frontmatter (`id`, optional `kind`/`tags`) and a body that may contain
|
|
11
|
+
`${path}` placeholders.
|
|
12
|
+
- **composition** — a full system prompt declared in `rules/<name>.yaml`:
|
|
13
|
+
an ordered `base` list of fragment ids, an optional `order`, and `rules`.
|
|
14
|
+
- **rule** (WHEN) — `when <context> → one action`. Actions: `add` (optionally
|
|
15
|
+
`after: <id>`), `replace` (old → new), `forbid`, `order`. Empty `when` always
|
|
16
|
+
fires.
|
|
17
|
+
- **context** — a flat bag of scalar facts. Any computed value (digests,
|
|
18
|
+
scores, sorted lists) is pre-computed by the caller and passed in.
|
|
19
|
+
|
|
20
|
+
## Folder layout
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
prompts/
|
|
24
|
+
├─ fragments/ *.md (text + frontmatter)
|
|
25
|
+
└─ rules/ *.yaml (composition + rules)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { resolve } from "@markbrutx/promptbook-core";
|
|
32
|
+
|
|
33
|
+
const { text, trace } = await resolve({
|
|
34
|
+
promptsDir: "./prompts",
|
|
35
|
+
prompt: "assistant",
|
|
36
|
+
context: { mode: "terse", locale: "ru", subjectName: "Ada" },
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`resolve` returns `{ text, trace }`. `text` is the fragments joined with `\n\n`,
|
|
41
|
+
in final order, with `${...}` substituted. `trace` is the explain output: every
|
|
42
|
+
rule (fired + why), the final id order, what was replaced/added/forbidden,
|
|
43
|
+
context axes no rule matched, and warnings.
|
|
44
|
+
|
|
45
|
+
## Guarantees
|
|
46
|
+
|
|
47
|
+
- **Deterministic.** Same folder contents + input → byte-identical `text`.
|
|
48
|
+
- **Never throws on data.** A missing `${var}` renders empty and is recorded in
|
|
49
|
+
`trace.warnings`; an unknown fragment reference is a warning, not a crash.
|
|
50
|
+
- **Conflict strategy.** Rules apply in order, later wins (cascade). `forbid` is
|
|
51
|
+
the one exception: it is a final filter and always wins.
|
|
52
|
+
- **Runtime-agnostic.** Pure functions; the filesystem is injectable
|
|
53
|
+
(`FsAdapter`), so it runs under Node, Deno and Bun.
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
/** Queue directory, relative to the prompts folder — the cross-process contract. */
|
|
11
|
+
export declare const ANNOTATION_QUEUE_DIR = ".annotations";
|
|
12
|
+
/** Queue file inside {@link ANNOTATION_QUEUE_DIR}, one annotation per line. */
|
|
13
|
+
export declare const ANNOTATION_QUEUE_FILE = "inbox.jsonl";
|
|
14
|
+
/** An annotation is open until an agent resolves it. */
|
|
15
|
+
export type AnnotationStatus = "open" | "resolved";
|
|
16
|
+
/** What an annotation points at: a composition variant or a standalone fragment. */
|
|
17
|
+
export interface AnnotationTarget {
|
|
18
|
+
/** Composition the annotated variant was assembled from. */
|
|
19
|
+
prompt?: string;
|
|
20
|
+
/** Context the variant was resolved under (present alongside `prompt`). */
|
|
21
|
+
context?: Context;
|
|
22
|
+
/** A fragment id, when annotating a fragment directly rather than a variant. */
|
|
23
|
+
fragmentId?: string;
|
|
24
|
+
/** Source file of the fragment/composition, to help the agent locate it. */
|
|
25
|
+
sourceFile?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Where, inside the source text, the comment is anchored. */
|
|
28
|
+
export interface AnnotationAnchor {
|
|
29
|
+
/** Fragment whose body carries the selected text. */
|
|
30
|
+
fragmentId: string;
|
|
31
|
+
/** The exact text the user selected. */
|
|
32
|
+
anchorText: string;
|
|
33
|
+
/** Optional character offset within the fragment body, for disambiguation. */
|
|
34
|
+
offset?: number;
|
|
35
|
+
}
|
|
36
|
+
/** One queued piece of human feedback, waiting for an agent to act on it. */
|
|
37
|
+
export interface Annotation {
|
|
38
|
+
id: string;
|
|
39
|
+
/** ISO-8601 creation time. */
|
|
40
|
+
createdAt: string;
|
|
41
|
+
target: AnnotationTarget;
|
|
42
|
+
anchor: AnnotationAnchor;
|
|
43
|
+
comment: string;
|
|
44
|
+
status: AnnotationStatus;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse the inbox JSONL: one annotation per non-empty line. Malformed lines are
|
|
48
|
+
* skipped rather than thrown — the queue must never crash on data, so a single
|
|
49
|
+
* bad line cannot block the rest.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseInbox(text: string): Annotation[];
|
|
52
|
+
/** Serialize one annotation as a single append-safe JSONL line (with newline). */
|
|
53
|
+
export declare function serializeAnnotationLine(annotation: Annotation): string;
|
|
54
|
+
/** Serialize a whole queue back to JSONL, used when removing/rewriting entries. */
|
|
55
|
+
export declare function serializeInbox(annotations: Annotation[]): string;
|
|
56
|
+
//# sourceMappingURL=annotations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../src/annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,iBAAiB,CAAC;AACnD,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD,wDAAwD;AACxD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,CAAC;AAEnD,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAkBD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAiBrD;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEtE;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,MAAM,CAEhE"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Queue directory, relative to the prompts folder — the cross-process contract. */
|
|
2
|
+
export const ANNOTATION_QUEUE_DIR = ".annotations";
|
|
3
|
+
/** Queue file inside {@link ANNOTATION_QUEUE_DIR}, one annotation per line. */
|
|
4
|
+
export const ANNOTATION_QUEUE_FILE = "inbox.jsonl";
|
|
5
|
+
/** True when `value` has the minimal required shape of an {@link Annotation}. */
|
|
6
|
+
function isAnnotation(value) {
|
|
7
|
+
if (typeof value !== "object" || value === null) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const record = value;
|
|
11
|
+
const anchor = record.anchor;
|
|
12
|
+
return (typeof record.id === "string" &&
|
|
13
|
+
typeof record.comment === "string" &&
|
|
14
|
+
typeof anchor === "object" &&
|
|
15
|
+
anchor !== null &&
|
|
16
|
+
typeof anchor.fragmentId === "string");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse the inbox JSONL: one annotation per non-empty line. Malformed lines are
|
|
20
|
+
* skipped rather than thrown — the queue must never crash on data, so a single
|
|
21
|
+
* bad line cannot block the rest.
|
|
22
|
+
*/
|
|
23
|
+
export function parseInbox(text) {
|
|
24
|
+
const annotations = [];
|
|
25
|
+
for (const line of text.split(/\r?\n/)) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (trimmed === "") {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(trimmed);
|
|
32
|
+
if (isAnnotation(parsed)) {
|
|
33
|
+
annotations.push(parsed);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// skip a malformed line; the rest of the queue stays usable
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return annotations;
|
|
41
|
+
}
|
|
42
|
+
/** Serialize one annotation as a single append-safe JSONL line (with newline). */
|
|
43
|
+
export function serializeAnnotationLine(annotation) {
|
|
44
|
+
return `${JSON.stringify(annotation)}\n`;
|
|
45
|
+
}
|
|
46
|
+
/** Serialize a whole queue back to JSONL, used when removing/rewriting entries. */
|
|
47
|
+
export function serializeInbox(annotations) {
|
|
48
|
+
return annotations.map(serializeAnnotationLine).join("");
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=annotations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotations.js","sourceRoot":"","sources":["../src/annotations.ts"],"names":[],"mappings":"AAUA,oFAAoF;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAG,cAAc,CAAC;AACnD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAsCnD,iFAAiF;AACjF,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,MAA6C,CAAC;IACpE,OAAO,CACL,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;QAC7B,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;QAClC,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CACtC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,WAAW,GAAiB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5C,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4DAA4D;QAC9D,CAAC;IACH,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,uBAAuB,CAAC,UAAsB;IAC5D,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,WAAyB;IACtD,OAAO,WAAW,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3D,CAAC"}
|
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { PromptBook } from "./types.js";
|
|
2
|
+
/** Options for {@link serializeBook}. */
|
|
3
|
+
export interface SerializeBookOptions {
|
|
4
|
+
/** Module specifier for the `import type { PromptBook }` line. Default `@markbrutx/promptbook-core`. */
|
|
5
|
+
importSpecifier?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Emit the `import type { PromptBook }` line and the `: PromptBook`
|
|
8
|
+
* annotation. Default `true`. Set `false` for a plain, inference-typed
|
|
9
|
+
* module — useful for runtimes that cannot resolve the type-only import
|
|
10
|
+
* (e.g. Deno consuming the raw build), where the value still resolves fine.
|
|
11
|
+
*/
|
|
12
|
+
typed?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Serialize a {@link PromptBook} to a deterministic, evaluable expression:
|
|
16
|
+
* `{ fragments: new Map([...]), compositions: new Map([...]),
|
|
17
|
+
* codePrompts: new Map([...]), warnings: [...] }`.
|
|
18
|
+
*
|
|
19
|
+
* The result is pure JavaScript (only `new Map`, arrays, object and scalar
|
|
20
|
+
* literals) so it can be embedded in a module or reconstructed directly. Map
|
|
21
|
+
* entries are sorted by key and optional fields are omitted when absent, so the
|
|
22
|
+
* same book always produces byte-identical output.
|
|
23
|
+
*/
|
|
24
|
+
export declare function serializeBookExpression(book: PromptBook): string;
|
|
25
|
+
/**
|
|
26
|
+
* Serialize a {@link PromptBook} to an importable TypeScript module exporting
|
|
27
|
+
* `book: PromptBook`. Folding a prompts folder into a single module lets a
|
|
28
|
+
* runtime (e.g. a Deno edge function) import the book instead of reading the
|
|
29
|
+
* disk. Deterministic: the same book yields the same module text.
|
|
30
|
+
*
|
|
31
|
+
* The `: PromptBook` annotation is required so literal rule actions narrow to
|
|
32
|
+
* `RuleAction` via contextual typing; pass `typed: false` (or use
|
|
33
|
+
* {@link serializeBookExpression}) for the annotation-free value.
|
|
34
|
+
*/
|
|
35
|
+
export declare function serializeBook(book: PromptBook, options?: SerializeBookOptions): string;
|
|
36
|
+
/**
|
|
37
|
+
* Serialize a {@link PromptBook} to a deterministic JSON dump: fragments,
|
|
38
|
+
* compositions and code-prompts as key-sorted arrays of their canonical objects,
|
|
39
|
+
* plus warnings.
|
|
40
|
+
* Shares the canonical key order and locale-independent sort with
|
|
41
|
+
* {@link serializeBook}, so the JSON and module outputs never drift.
|
|
42
|
+
*/
|
|
43
|
+
export declare function serializeBookJson(book: PromptBook): string;
|
|
44
|
+
//# sourceMappingURL=bundle.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Locale-independent string order so serialized output is byte-stable across machines. */
|
|
2
|
+
function compareKeys(a, b) {
|
|
3
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
4
|
+
}
|
|
5
|
+
/** Map entries sorted by key with a locale-independent comparator. */
|
|
6
|
+
function sortedEntries(map) {
|
|
7
|
+
return [...map.entries()].sort((a, b) => compareKeys(a[0], b[0]));
|
|
8
|
+
}
|
|
9
|
+
/** Drop keys whose value is `undefined`, preserving insertion (declaration) order. */
|
|
10
|
+
function compact(obj) {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
13
|
+
if (value !== undefined) {
|
|
14
|
+
out[key] = value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
// The canonical builders below list every key explicitly via a
|
|
20
|
+
// `Record<keyof Required<…>>` literal: adding a field to the data model is then
|
|
21
|
+
// a compile error here until it is serialized, so the bundler never silently
|
|
22
|
+
// drops data. Key order follows the type declaration; absent optionals are
|
|
23
|
+
// compacted away. `JSON.stringify` of the result is a deterministic literal.
|
|
24
|
+
function canonicalFragment(fragment) {
|
|
25
|
+
const ordered = {
|
|
26
|
+
id: fragment.id,
|
|
27
|
+
kind: fragment.kind,
|
|
28
|
+
tags: fragment.tags,
|
|
29
|
+
body: fragment.body,
|
|
30
|
+
sourceFile: fragment.sourceFile,
|
|
31
|
+
};
|
|
32
|
+
return compact(ordered);
|
|
33
|
+
}
|
|
34
|
+
function canonicalRule(rule) {
|
|
35
|
+
const ordered = {
|
|
36
|
+
index: rule.index,
|
|
37
|
+
when: rule.when,
|
|
38
|
+
action: rule.action,
|
|
39
|
+
add: rule.add,
|
|
40
|
+
after: rule.after,
|
|
41
|
+
replace: rule.replace,
|
|
42
|
+
forbid: rule.forbid,
|
|
43
|
+
order: rule.order,
|
|
44
|
+
};
|
|
45
|
+
return compact(ordered);
|
|
46
|
+
}
|
|
47
|
+
function canonicalComposition(composition) {
|
|
48
|
+
const ordered = {
|
|
49
|
+
name: composition.name,
|
|
50
|
+
base: composition.base,
|
|
51
|
+
order: composition.order,
|
|
52
|
+
rules: composition.rules.map(canonicalRule),
|
|
53
|
+
sourceFile: composition.sourceFile,
|
|
54
|
+
};
|
|
55
|
+
return compact(ordered);
|
|
56
|
+
}
|
|
57
|
+
function canonicalCodePromptSample(sample) {
|
|
58
|
+
const ordered = {
|
|
59
|
+
label: sample.label,
|
|
60
|
+
context: sample.context,
|
|
61
|
+
output: sample.output,
|
|
62
|
+
};
|
|
63
|
+
return compact(ordered);
|
|
64
|
+
}
|
|
65
|
+
function canonicalCodePrompt(codePrompt) {
|
|
66
|
+
const ordered = {
|
|
67
|
+
name: codePrompt.name,
|
|
68
|
+
description: codePrompt.description,
|
|
69
|
+
samples: codePrompt.samples.map(canonicalCodePromptSample),
|
|
70
|
+
sourceFile: codePrompt.sourceFile,
|
|
71
|
+
};
|
|
72
|
+
return compact(ordered);
|
|
73
|
+
}
|
|
74
|
+
/** Emit `new Map([...])` with one `[key, value]` entry per line (entries pre-sorted). */
|
|
75
|
+
function serializeMap(entries, canonical) {
|
|
76
|
+
if (entries.length === 0) {
|
|
77
|
+
return "new Map([])";
|
|
78
|
+
}
|
|
79
|
+
const lines = entries.map(([key, value]) => ` [${JSON.stringify(key)}, ${JSON.stringify(canonical(value))}],`);
|
|
80
|
+
return `new Map([\n${lines.join("\n")}\n ])`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Serialize a {@link PromptBook} to a deterministic, evaluable expression:
|
|
84
|
+
* `{ fragments: new Map([...]), compositions: new Map([...]),
|
|
85
|
+
* codePrompts: new Map([...]), warnings: [...] }`.
|
|
86
|
+
*
|
|
87
|
+
* The result is pure JavaScript (only `new Map`, arrays, object and scalar
|
|
88
|
+
* literals) so it can be embedded in a module or reconstructed directly. Map
|
|
89
|
+
* entries are sorted by key and optional fields are omitted when absent, so the
|
|
90
|
+
* same book always produces byte-identical output.
|
|
91
|
+
*/
|
|
92
|
+
export function serializeBookExpression(book) {
|
|
93
|
+
const fragments = serializeMap(sortedEntries(book.fragments), canonicalFragment);
|
|
94
|
+
const compositions = serializeMap(sortedEntries(book.compositions), canonicalComposition);
|
|
95
|
+
const codePrompts = serializeMap(sortedEntries(book.codePrompts), canonicalCodePrompt);
|
|
96
|
+
const warnings = JSON.stringify(book.warnings);
|
|
97
|
+
return `{\n fragments: ${fragments},\n compositions: ${compositions},\n codePrompts: ${codePrompts},\n warnings: ${warnings},\n}`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Serialize a {@link PromptBook} to an importable TypeScript module exporting
|
|
101
|
+
* `book: PromptBook`. Folding a prompts folder into a single module lets a
|
|
102
|
+
* runtime (e.g. a Deno edge function) import the book instead of reading the
|
|
103
|
+
* disk. Deterministic: the same book yields the same module text.
|
|
104
|
+
*
|
|
105
|
+
* The `: PromptBook` annotation is required so literal rule actions narrow to
|
|
106
|
+
* `RuleAction` via contextual typing; pass `typed: false` (or use
|
|
107
|
+
* {@link serializeBookExpression}) for the annotation-free value.
|
|
108
|
+
*/
|
|
109
|
+
export function serializeBook(book, options = {}) {
|
|
110
|
+
const typed = options.typed !== false;
|
|
111
|
+
const importSpecifier = options.importSpecifier ?? "@markbrutx/promptbook-core";
|
|
112
|
+
return [
|
|
113
|
+
"// Code generated by `promptbook bundle`; do not edit by hand.",
|
|
114
|
+
...(typed ? [`import type { PromptBook } from "${importSpecifier}";`] : []),
|
|
115
|
+
"",
|
|
116
|
+
`export const book${typed ? ": PromptBook" : ""} = ${serializeBookExpression(book)};`,
|
|
117
|
+
"",
|
|
118
|
+
"export default book;",
|
|
119
|
+
"",
|
|
120
|
+
].join("\n");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Serialize a {@link PromptBook} to a deterministic JSON dump: fragments,
|
|
124
|
+
* compositions and code-prompts as key-sorted arrays of their canonical objects,
|
|
125
|
+
* plus warnings.
|
|
126
|
+
* Shares the canonical key order and locale-independent sort with
|
|
127
|
+
* {@link serializeBook}, so the JSON and module outputs never drift.
|
|
128
|
+
*/
|
|
129
|
+
export function serializeBookJson(book) {
|
|
130
|
+
const fragments = sortedEntries(book.fragments).map(([, value]) => canonicalFragment(value));
|
|
131
|
+
const compositions = sortedEntries(book.compositions).map(([, value]) => canonicalComposition(value));
|
|
132
|
+
const codePrompts = sortedEntries(book.codePrompts).map(([, value]) => canonicalCodePrompt(value));
|
|
133
|
+
return `${JSON.stringify({ fragments, compositions, codePrompts, warnings: book.warnings }, null, 2)}\n`;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Generated by `npm run build:edge`; do not edit by hand.
|
|
2
|
+
|
|
3
|
+
// dist/interpolate.js
|
|
4
|
+
var VAR_RE = /(\\?)\$\{([^}]+)\}/g;
|
|
5
|
+
function interpolate(body, context, onMissing) {
|
|
6
|
+
return body.replace(VAR_RE, (_match, backslash, expr) => {
|
|
7
|
+
if (backslash) {
|
|
8
|
+
return `\${${expr}}`;
|
|
9
|
+
}
|
|
10
|
+
const key = expr.trim();
|
|
11
|
+
const value = context[key];
|
|
12
|
+
if (value === void 0) {
|
|
13
|
+
onMissing(key);
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
return String(value);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// dist/resolve-book.js
|
|
21
|
+
function resolveBook(book, prompt, context) {
|
|
22
|
+
const composition = book.compositions.get(prompt);
|
|
23
|
+
if (!composition) {
|
|
24
|
+
const available = [...book.compositions.keys()].sort().join(", ") || "(none)";
|
|
25
|
+
throw new Error(`Unknown prompt "${prompt}". Available compositions: ${available}.`);
|
|
26
|
+
}
|
|
27
|
+
const warnings = [...book.warnings];
|
|
28
|
+
const ruleTraces = [];
|
|
29
|
+
const replaced = [];
|
|
30
|
+
const added = [];
|
|
31
|
+
const forbidden = [];
|
|
32
|
+
const forbiddenIds = /* @__PURE__ */ new Set();
|
|
33
|
+
const working = [...composition.base];
|
|
34
|
+
let orderOverride = composition.order ? [...composition.order] : void 0;
|
|
35
|
+
for (const rule of composition.rules) {
|
|
36
|
+
const match = matchWhen(rule.when, context);
|
|
37
|
+
const trace2 = {
|
|
38
|
+
index: rule.index,
|
|
39
|
+
action: rule.action,
|
|
40
|
+
when: rule.when,
|
|
41
|
+
fired: match.fired
|
|
42
|
+
};
|
|
43
|
+
if (!match.fired) {
|
|
44
|
+
trace2.reason = match.reason;
|
|
45
|
+
ruleTraces.push(trace2);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
switch (rule.action) {
|
|
49
|
+
case "replace": {
|
|
50
|
+
const effects = [];
|
|
51
|
+
for (const [from, to] of Object.entries(rule.replace ?? {})) {
|
|
52
|
+
const at = working.indexOf(from);
|
|
53
|
+
if (at === -1) {
|
|
54
|
+
warnings.push(`Rule #${rule.index} cannot replace "${from}": it is not present in "${prompt}".`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
working[at] = to;
|
|
58
|
+
replaced.push({ from, to, ruleIndex: rule.index });
|
|
59
|
+
effects.push(`${from} -> ${to}`);
|
|
60
|
+
}
|
|
61
|
+
trace2.effect = `replace ${effects.join(", ")}`;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "add": {
|
|
65
|
+
const ids = rule.add ?? [];
|
|
66
|
+
const after = rule.after;
|
|
67
|
+
const at = insertionPoint(working, after, rule.index, prompt, warnings);
|
|
68
|
+
working.splice(at, 0, ...ids);
|
|
69
|
+
for (const id of ids) {
|
|
70
|
+
added.push(after !== void 0 ? { id, after, ruleIndex: rule.index } : { id, ruleIndex: rule.index });
|
|
71
|
+
}
|
|
72
|
+
trace2.effect = `add ${ids.join(", ")}${after !== void 0 ? ` after ${after}` : ""}`;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "forbid": {
|
|
76
|
+
const ids = rule.forbid ?? [];
|
|
77
|
+
for (const id of ids) {
|
|
78
|
+
forbiddenIds.add(id);
|
|
79
|
+
forbidden.push({ id, ruleIndex: rule.index });
|
|
80
|
+
}
|
|
81
|
+
trace2.effect = `forbid ${ids.join(", ")}`;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "order": {
|
|
85
|
+
orderOverride = [...rule.order ?? []];
|
|
86
|
+
trace2.effect = `order ${orderOverride.join(", ")}`;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
ruleTraces.push(trace2);
|
|
91
|
+
}
|
|
92
|
+
const ordered = applyOrder(working, orderOverride);
|
|
93
|
+
const selected = ordered.filter((id) => !forbiddenIds.has(id));
|
|
94
|
+
const parts = [];
|
|
95
|
+
const finalOrder = [];
|
|
96
|
+
for (const id of selected) {
|
|
97
|
+
const fragment = book.fragments.get(id);
|
|
98
|
+
if (!fragment) {
|
|
99
|
+
warnings.push(`Fragment "${id}" referenced by "${prompt}" was not found.`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
parts.push(interpolate(fragment.body, context, (key) => {
|
|
103
|
+
warnings.push(`Missing variable "${key}" while rendering fragment "${id}".`);
|
|
104
|
+
}));
|
|
105
|
+
finalOrder.push(id);
|
|
106
|
+
}
|
|
107
|
+
const trace = {
|
|
108
|
+
prompt,
|
|
109
|
+
context,
|
|
110
|
+
rules: ruleTraces,
|
|
111
|
+
finalOrder,
|
|
112
|
+
replaced,
|
|
113
|
+
added,
|
|
114
|
+
forbidden,
|
|
115
|
+
unmatchedAxes: computeUnmatchedAxes(composition, context),
|
|
116
|
+
warnings
|
|
117
|
+
};
|
|
118
|
+
return { text: parts.join("\n\n"), trace };
|
|
119
|
+
}
|
|
120
|
+
function matchWhen(when, context) {
|
|
121
|
+
for (const [key, expected] of Object.entries(when)) {
|
|
122
|
+
const actual = context[key];
|
|
123
|
+
if (actual === void 0) {
|
|
124
|
+
return { fired: false, reason: `context.${key} is unset, rule expects "${expected}"` };
|
|
125
|
+
}
|
|
126
|
+
if (!valuesEqual(expected, actual)) {
|
|
127
|
+
return { fired: false, reason: `context.${key}="${actual}" does not equal expected "${expected}"` };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { fired: true };
|
|
131
|
+
}
|
|
132
|
+
function valuesEqual(a, b) {
|
|
133
|
+
return String(a) === String(b);
|
|
134
|
+
}
|
|
135
|
+
function insertionPoint(working, after, ruleIndex, prompt, warnings) {
|
|
136
|
+
if (after === void 0) {
|
|
137
|
+
return Math.max(working.length - 1, 0);
|
|
138
|
+
}
|
|
139
|
+
const at = working.indexOf(after);
|
|
140
|
+
if (at === -1) {
|
|
141
|
+
warnings.push(`Rule #${ruleIndex} add anchor "${after}" not found in "${prompt}"; appending at end.`);
|
|
142
|
+
return working.length;
|
|
143
|
+
}
|
|
144
|
+
return at + 1;
|
|
145
|
+
}
|
|
146
|
+
function applyOrder(working, orderOverride) {
|
|
147
|
+
const seen = /* @__PURE__ */ new Set();
|
|
148
|
+
const result = [];
|
|
149
|
+
const push = (id) => {
|
|
150
|
+
if (!seen.has(id)) {
|
|
151
|
+
seen.add(id);
|
|
152
|
+
result.push(id);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
if (orderOverride) {
|
|
156
|
+
const present = new Set(working);
|
|
157
|
+
for (const id of orderOverride) {
|
|
158
|
+
if (present.has(id)) {
|
|
159
|
+
push(id);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const id of working) {
|
|
164
|
+
push(id);
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
function computeUnmatchedAxes(composition, context) {
|
|
169
|
+
const result = [];
|
|
170
|
+
for (const key of Object.keys(context)) {
|
|
171
|
+
let referenced = false;
|
|
172
|
+
let matched = false;
|
|
173
|
+
for (const rule of composition.rules) {
|
|
174
|
+
if (Object.hasOwn(rule.when, key)) {
|
|
175
|
+
referenced = true;
|
|
176
|
+
const expected = rule.when[key];
|
|
177
|
+
if (expected !== void 0 && valuesEqual(expected, context[key])) {
|
|
178
|
+
matched = true;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (referenced && !matched) {
|
|
184
|
+
result.push({ key, value: context[key] });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
export {
|
|
190
|
+
interpolate,
|
|
191
|
+
resolveBook
|
|
192
|
+
};
|
package/dist/edge.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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.
|
|
8
|
+
*/
|
|
9
|
+
export { interpolate } from "./interpolate.js";
|
|
10
|
+
export { resolveBook } from "./resolve-book.js";
|
|
11
|
+
export type { Context, PromptBook, ResolveResult, Trace } from "./types.js";
|
|
12
|
+
//# sourceMappingURL=edge.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/edge.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
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.
|
|
8
|
+
*/
|
|
9
|
+
export { interpolate } from "./interpolate.js";
|
|
10
|
+
export { resolveBook } from "./resolve-book.js";
|
|
11
|
+
//# sourceMappingURL=edge.js.map
|
package/dist/edge.js.map
ADDED
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in, pluggable assertions for the eval engine.
|
|
3
|
+
*
|
|
4
|
+
* Each checker is pure: it inspects a model output against an {@link Assertion}
|
|
5
|
+
* spec and returns an {@link AssertionResult}. Callers can pass their own
|
|
6
|
+
* registry to {@link evaluate} to add or replace checkers, exactly like lint
|
|
7
|
+
* rules. The `language` checker is a script heuristic, not an LLM judge.
|
|
8
|
+
*/
|
|
9
|
+
import type { AssertionRegistry } from "./types.js";
|
|
10
|
+
/**
|
|
11
|
+
* The built-in assertion registry. Pass a merged/replaced object to
|
|
12
|
+
* {@link evaluate} to customize: `{ ...defaultAssertions(), myType: fn }`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function defaultAssertions(): AssertionRegistry;
|
|
15
|
+
//# sourceMappingURL=assertions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../../src/eval/assertions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAA0B,iBAAiB,EAAmB,MAAM,YAAY,CAAC;AAsJ7F;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CAWrD"}
|