@markbrutx/promptbook-cli 0.3.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.
package/README.md CHANGED
@@ -10,15 +10,31 @@ npm i -D @markbrutx/promptbook-cli
10
10
  ## Commands
11
11
 
12
12
  ```
13
- promptbook resolve <name> --explain # assemble a prompt; print the explain trace
14
- promptbook ls # list fragments / compositions / code-prompts
15
- promptbook lint <name> # static checks: token budget, banned tokens, dead rules…
16
- promptbook eval --case <fixture> # run fixtures against a model adapter → pass-rate
17
- promptbook bundle -o book.generated.ts # serialize a prompts folder to a portable module
18
- promptbook view # open the viewer (needs @markbrutx/promptbook-viewer)
13
+ promptbook resolve <name> --explain # assemble a prompt; print the explain trace
14
+ promptbook ls # list fragments / compositions / code-prompts
15
+ promptbook lint <name> # static checks: token budget, banned tokens, dead rules…
16
+ promptbook eval --case <fixture> # run fixtures against a model adapter → pass-rate
17
+ promptbook bundle -o book.generated.ts # serialize a prompts folder to a portable module
18
+ promptbook bundle --all # rebundle every book in a workspace (one artifact per book)
19
+ promptbook bundle --check [--all] # CI gate: exit 1 when book.generated.ts is stale or missing
20
+ promptbook bundle --exclude-code-prompts # serialize code-prompts as an empty map (runtime-lean bundle)
21
+ promptbook watch # rebundle book.generated.ts on every fragment/rule edit
22
+ promptbook view # open the viewer (needs @markbrutx/promptbook-viewer)
19
23
  promptbook annotations list|resolve|clear
20
24
  ```
21
25
 
26
+ Dev loop:
27
+
28
+ - `promptbook watch` keeps `book.generated.ts` in sync while you edit
29
+ `fragments/`, `rules/`, `code-prompts/` or `promptbook.json`
30
+ (one rebundle per book, debounced 250 ms; multi-book workspaces rebundle in
31
+ parallel).
32
+ - `promptbook bundle --check --all` is the CI gate: it exits non-zero when any
33
+ book's checked-in `book.generated.ts` drifts from the prompts folder, so
34
+ `book.generated.ts` and the source files cannot fall out of sync silently.
35
+ - `promptbook bundle --exclude-code-prompts` ships a runtime-lean bundle while
36
+ keeping `code-prompts/` on disk as metadata for `ls` / the viewer.
37
+
22
38
  The prompts folder is resolved from `--dir`, then `promptbook.json` (the
23
39
  nearest one found by walking up from the current directory — same model as
24
40
  `git`/`biome`/`eslint`; the `promptsDir` value is taken relative to wherever
File without changes
@@ -18,8 +18,12 @@ export interface ParsedArgs {
18
18
  contextFile?: string;
19
19
  fragments: boolean;
20
20
  compositions: boolean;
21
- /** ls/resolve: operate across every book in the workspace. */
21
+ /** ls/resolve/bundle: operate across every book in the workspace. */
22
22
  all: boolean;
23
+ /** bundle: compare the generated output with the existing artifact and exit non-zero on drift. */
24
+ check: boolean;
25
+ /** bundle: serialize without code-prompts so a runtime bundle stays lean. */
26
+ excludeCodePrompts: boolean;
23
27
  /** lint: estimated token ceiling for the token-budget rule. */
24
28
  maxTokens?: number;
25
29
  /** lint: treat warnings as failures for the exit code. */
@@ -1 +1 @@
1
- {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/args.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,gEAAgE;IAChE,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,8DAA8D;IAC9D,GAAG,EAAE,OAAO,CAAC;IACb,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,IAAI,EAAE,OAAO,CAAC;IACd,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,OAAO,CAAC;CACjB;AAgCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAwEvD"}
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/args.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,gEAAgE;IAChE,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,GAAG,EAAE,OAAO,CAAC;IACb,kGAAkG;IAClG,KAAK,EAAE,OAAO,CAAC;IACf,6EAA6E;IAC7E,kBAAkB,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,IAAI,EAAE,OAAO,CAAC;IACd,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,OAAO,CAAC;CACjB;AAgCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA4EvD"}
package/dist/src/args.js CHANGED
@@ -39,6 +39,8 @@ export function parseCliArgs(argv) {
39
39
  fragments: { type: "boolean" },
40
40
  compositions: { type: "boolean" },
41
41
  all: { type: "boolean" },
42
+ check: { type: "boolean" },
43
+ "exclude-code-prompts": { type: "boolean" },
42
44
  "max-tokens": { type: "string" },
43
45
  strict: { type: "boolean" },
44
46
  model: { type: "string" },
@@ -85,6 +87,8 @@ export function parseCliArgs(argv) {
85
87
  fragments: values.fragments ?? false,
86
88
  compositions: values.compositions ?? false,
87
89
  all: values.all ?? false,
90
+ check: values.check ?? false,
91
+ excludeCodePrompts: values["exclude-code-prompts"] ?? false,
88
92
  maxTokens,
89
93
  strict: values.strict ?? false,
90
94
  model: values.model,
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/args.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/args.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,33 @@
1
1
  import type { ParsedArgs } from "../args.js";
2
2
  import { type IO } from "../io.js";
3
3
  /**
4
- * `bundle [<dir>]`: load a prompts folder and emit it as a single importable
5
- * module exporting `book: PromptBook` (so a runtime can import the book instead
6
- * of reading the disk). Writes to stdout, or to a file with `-o`. `--json`
7
- * emits a structured dump instead of the TypeScript module.
4
+ * Bundle one book: load relativize optionally drop code-prompts serialize.
5
+ * Then either `--check` against the existing artifact, write to `--out`/the
6
+ * default `book.generated.ts`, or print to stdout (single-book, no `--out`).
7
+ *
8
+ * Returns the exit code: 0 on success / up-to-date, 1 on drift / write failure.
9
+ * `bookName` is used in `--check` output and warnings; when `inWorkspace` is
10
+ * true the artifact is always written (not printed), matching `bundle --all`.
11
+ */
12
+ export declare function bundleOne(io: IO, args: ParsedArgs, promptsDir: string, bookName: string, inWorkspace: boolean): Promise<number>;
13
+ /**
14
+ * `bundle [<dir>]`: load a prompts folder and emit it as an importable module
15
+ * exporting `book: PromptBook`. Writes to stdout, or to a file with `-o`.
16
+ *
17
+ * Flags:
18
+ * - `--json` emits the structured dump instead of the TypeScript module.
19
+ * - `--plain` drops the type-only import (e.g. for Deno consumers).
20
+ * - `--exclude-code-prompts` serializes code-prompts as an empty map so the
21
+ * runtime bundle stays lean while `code-prompts/` keeps living on disk as
22
+ * metadata for `ls` / the viewer.
23
+ * - `--check` compares the would-be output against the existing artifact
24
+ * (`book.generated.ts` next to the prompts folder, or `--out`); exits 1 on
25
+ * drift or a missing artifact and prints a short hint on stderr.
26
+ * - `--all` walks every book in the workspace and writes each to its own
27
+ * `book.generated.ts`; incompatible with `-o`.
8
28
  *
9
29
  * The folder comes from the positional `<dir>`, else `--dir`, else config /
10
- * `./prompts` (the standard resolution).
30
+ * `./prompts`. `--all` does not accept a single-book `<dir>` operand differently.
11
31
  */
12
32
  export declare function cmdBundle(args: ParsedArgs, io: IO): Promise<number>;
13
33
  //# sourceMappingURL=bundle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../src/commands/bundle.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAgB,KAAK,EAAE,EAAE,MAAM,UAAU,CAAC;AA+BjD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzE"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../src/commands/bundle.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAgB,KAAK,EAAE,EAAE,MAAM,UAAU,CAAC;AAqHjD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBzE"}
@@ -1,7 +1,10 @@
1
- import { isAbsolute, relative, resolve as resolvePath, sep } from "node:path";
1
+ import { basename, isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path";
2
2
  import { loadPrompts, serializeBook, serializeBookJson } from "@markbrutx/promptbook-core";
3
3
  import { requirePromptsDir } from "../config.js";
4
4
  import { emitWarnings } from "../io.js";
5
+ import { loadWorkspace } from "../workspace.js";
6
+ /** Default name of the artifact file each book emits alongside its sources. */
7
+ const BUNDLE_FILE = "book.generated.ts";
5
8
  /** Rewrite an absolute source path to a portable, forward-slash path relative to `dir`. */
6
9
  function relativizeSource(sourceFile, dir) {
7
10
  if (!isAbsolute(sourceFile)) {
@@ -26,28 +29,101 @@ function portableBook(book, dir) {
26
29
  warnings: book.warnings,
27
30
  };
28
31
  }
32
+ /** Return the book with `codePrompts` cleared — used by `--exclude-code-prompts`. */
33
+ function withoutCodePrompts(book) {
34
+ return { ...book, codePrompts: new Map() };
35
+ }
36
+ /** Normalize EOL so a CRLF-checked-in artifact does not falsely look stale on an LF rebuild. */
37
+ function normalizeEol(text) {
38
+ return text.replace(/\r\n/g, "\n");
39
+ }
40
+ /** First 1-based line index where two strings differ; null when equal. */
41
+ function firstDiffLine(actual, expected) {
42
+ if (actual === expected) {
43
+ return null;
44
+ }
45
+ const a = actual.split("\n");
46
+ const b = expected.split("\n");
47
+ const max = Math.max(a.length, b.length);
48
+ for (let i = 0; i < max; i += 1) {
49
+ if (a[i] !== b[i]) {
50
+ return i + 1;
51
+ }
52
+ }
53
+ // Unreachable: if the strings differ, at least one index in [0, max) must too.
54
+ throw new Error("firstDiffLine: strings differ but no line diff found");
55
+ }
56
+ /** Build the output text for one book (TypeScript module or JSON). */
57
+ function renderOutput(book, args) {
58
+ return args.json ? serializeBookJson(book) : serializeBook(book, { typed: !args.plain });
59
+ }
60
+ /** Pick the artifact path: explicit `--out`, else `book.generated.ts` next to the prompts folder. */
61
+ function targetPath(io, args, promptsDir) {
62
+ if (args.out !== undefined) {
63
+ return resolvePath(io.cwd(), args.out);
64
+ }
65
+ return join(promptsDir, BUNDLE_FILE);
66
+ }
67
+ async function checkOne(io, output, path) {
68
+ let existing;
69
+ try {
70
+ existing = await io.fs.readFile(path);
71
+ }
72
+ catch {
73
+ return { status: "stale", reason: "missing", path };
74
+ }
75
+ const line = firstDiffLine(normalizeEol(output), normalizeEol(existing));
76
+ if (line === null) {
77
+ return { status: "up-to-date", path };
78
+ }
79
+ return { status: "stale", reason: "diff", firstDiffLine: line, path };
80
+ }
81
+ function emitCheckResult(io, args, bookName, outcome) {
82
+ if (args.json) {
83
+ const diff = outcome.status === "stale"
84
+ ? outcome.reason === "missing"
85
+ ? { reason: "missing", path: outcome.path }
86
+ : { reason: "diff", firstDiffLine: outcome.firstDiffLine }
87
+ : null;
88
+ io.stderr(`${JSON.stringify({ book: bookName, status: outcome.status, diff })}\n`);
89
+ return;
90
+ }
91
+ if (outcome.status === "up-to-date") {
92
+ io.stderr(`${bookName} up to date\n`);
93
+ return;
94
+ }
95
+ if (outcome.reason === "missing") {
96
+ io.stderr(`${bookName} stale (missing ${outcome.path})\n`);
97
+ return;
98
+ }
99
+ io.stderr(`${bookName} stale (first diff at line ${outcome.firstDiffLine})\n`);
100
+ }
29
101
  /**
30
- * `bundle [<dir>]`: load a prompts folder and emit it as a single importable
31
- * module exporting `book: PromptBook` (so a runtime can import the book instead
32
- * of reading the disk). Writes to stdout, or to a file with `-o`. `--json`
33
- * emits a structured dump instead of the TypeScript module.
102
+ * Bundle one book: load relativize optionally drop code-prompts serialize.
103
+ * Then either `--check` against the existing artifact, write to `--out`/the
104
+ * default `book.generated.ts`, or print to stdout (single-book, no `--out`).
34
105
  *
35
- * The folder comes from the positional `<dir>`, else `--dir`, else config /
36
- * `./prompts` (the standard resolution).
106
+ * Returns the exit code: 0 on success / up-to-date, 1 on drift / write failure.
107
+ * `bookName` is used in `--check` output and warnings; when `inWorkspace` is
108
+ * true the artifact is always written (not printed), matching `bundle --all`.
37
109
  */
38
- export async function cmdBundle(args, io) {
39
- const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
40
- if (promptsDir === null) {
41
- return 1;
42
- }
43
- const book = portableBook(await loadPrompts(promptsDir, io.fs), promptsDir);
110
+ export async function bundleOne(io, args, promptsDir, bookName, inWorkspace) {
111
+ const loaded = await loadPrompts(promptsDir, io.fs);
112
+ // `--exclude-code-prompts` discards them outright; skipping the relativize pass is the whole point.
113
+ const source = args.excludeCodePrompts ? withoutCodePrompts(loaded) : loaded;
114
+ const book = portableBook(source, promptsDir);
44
115
  emitWarnings(io, book.warnings);
45
- const output = args.json ? serializeBookJson(book) : serializeBook(book, { typed: !args.plain });
46
- if (args.out === undefined) {
116
+ const output = renderOutput(book, args);
117
+ if (args.check) {
118
+ const outcome = await checkOne(io, output, targetPath(io, args, promptsDir));
119
+ emitCheckResult(io, args, bookName, outcome);
120
+ return outcome.status === "up-to-date" ? 0 : 1;
121
+ }
122
+ if (args.out === undefined && !inWorkspace) {
47
123
  io.stdout(output);
48
124
  return 0;
49
125
  }
50
- const outPath = resolvePath(io.cwd(), args.out);
126
+ const outPath = targetPath(io, args, promptsDir);
51
127
  try {
52
128
  await io.writeFile(outPath, output);
53
129
  }
@@ -58,4 +134,47 @@ export async function cmdBundle(args, io) {
58
134
  io.stderr(`wrote ${outPath}\n`);
59
135
  return 0;
60
136
  }
137
+ /** Run `bundleOne` over every book in the workspace in parallel; the worst exit code wins. */
138
+ async function bundleAll(io, args, root) {
139
+ const workspace = await loadWorkspace(io, root);
140
+ if (workspace.books.length === 0) {
141
+ io.stderr(`error: no books found under ${root}\n`);
142
+ return 1;
143
+ }
144
+ const codes = await Promise.all(workspace.books.map((book) => bundleOne(io, args, book.dir, book.name, true)));
145
+ return Math.max(0, ...codes);
146
+ }
147
+ /**
148
+ * `bundle [<dir>]`: load a prompts folder and emit it as an importable module
149
+ * exporting `book: PromptBook`. Writes to stdout, or to a file with `-o`.
150
+ *
151
+ * Flags:
152
+ * - `--json` emits the structured dump instead of the TypeScript module.
153
+ * - `--plain` drops the type-only import (e.g. for Deno consumers).
154
+ * - `--exclude-code-prompts` serializes code-prompts as an empty map so the
155
+ * runtime bundle stays lean while `code-prompts/` keeps living on disk as
156
+ * metadata for `ls` / the viewer.
157
+ * - `--check` compares the would-be output against the existing artifact
158
+ * (`book.generated.ts` next to the prompts folder, or `--out`); exits 1 on
159
+ * drift or a missing artifact and prints a short hint on stderr.
160
+ * - `--all` walks every book in the workspace and writes each to its own
161
+ * `book.generated.ts`; incompatible with `-o`.
162
+ *
163
+ * The folder comes from the positional `<dir>`, else `--dir`, else config /
164
+ * `./prompts`. `--all` does not accept a single-book `<dir>` operand differently.
165
+ */
166
+ export async function cmdBundle(args, io) {
167
+ if (args.all && args.out !== undefined) {
168
+ io.stderr("error: --all is not compatible with -o/--out\n");
169
+ return 1;
170
+ }
171
+ const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
172
+ if (promptsDir === null) {
173
+ return 1;
174
+ }
175
+ if (args.all) {
176
+ return bundleAll(io, args, promptsDir);
177
+ }
178
+ return bundleOne(io, args, promptsDir, basename(promptsDir), false);
179
+ }
61
180
  //# sourceMappingURL=bundle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../../src/commands/bundle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../../src/commands/bundle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { ParsedArgs } from "../args.js";
2
+ import { type IO } from "../io.js";
3
+ /**
4
+ * `watch [<dir>]`: rebuild `book.generated.ts` whenever fragments, rules,
5
+ * compositions, code-prompts or `promptbook.json` change. Streams one short
6
+ * line per rebuild to stderr (`[clock] <book> bundled (<bytes> B, <ms>ms)`);
7
+ * stdout stays empty (the contract: stdout = payload, watch has no payload).
8
+ *
9
+ * Discovers every book under the prompts folder and rebuilds each once on
10
+ * startup, then debounces per-book events with a 250 ms window so a burst of
11
+ * edits collapses into one rebuild. SIGINT / SIGTERM closes the watcher and
12
+ * exits 0. Honors `--plain`, `--exclude-code-prompts`, `--json`, and the
13
+ * config / `--dir` resolution chain.
14
+ *
15
+ * `--out <file>` only works in a single-book workspace; the multi-book mode
16
+ * always writes each book's artifact next to its sources.
17
+ */
18
+ export declare function cmdWatch(args: ParsedArgs, io: IO): Promise<number>;
19
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../../src/commands/watch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAgB,KAAK,EAAE,EAAE,MAAM,UAAU,CAAC;AA8HjD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2ExE"}
@@ -0,0 +1,193 @@
1
+ import { relative, sep } from "node:path";
2
+ import { watch as chokidarWatch } from "chokidar";
3
+ import { requirePromptsDir } from "../config.js";
4
+ import { colorEnabled } from "../io.js";
5
+ import { makeStyle } from "../style.js";
6
+ import { loadWorkspace } from "../workspace.js";
7
+ import { bundleOne } from "./bundle.js";
8
+ /** Folders inside a book whose edits warrant a rebuild (mirrors core's loader layout). */
9
+ const BOOK_DIRS = ["fragments", "rules", "code-prompts"];
10
+ /** Single-book root files we re-bundle on touch (config edits change the assembly). */
11
+ const BOOK_FILES = ["promptbook.json"];
12
+ /** Debounce window per book: a burst of edits collapses into one rebuild. */
13
+ const DEBOUNCE_MS = 250;
14
+ /** Local-time clock prefix (`hours:minutes:seconds`); slices off Date#toTimeString's TZ tail. */
15
+ function clock() {
16
+ return new Date().toTimeString().slice(0, 8);
17
+ }
18
+ /** True when `event` happened inside one of the book's watched folders (or is a root file). */
19
+ function eventInBook(book, eventPath) {
20
+ const rel = relative(book.dir, eventPath);
21
+ if (rel === "" || rel.startsWith("..")) {
22
+ return false;
23
+ }
24
+ const parts = rel.split(sep);
25
+ if (parts.length === 1) {
26
+ return BOOK_FILES.includes(parts[0]);
27
+ }
28
+ return BOOK_DIRS.includes(parts[0]);
29
+ }
30
+ /** Ignore artifacts, tests, fixtures, and everything VCS / dep manager touches. */
31
+ function shouldIgnore(eventPath) {
32
+ const lower = eventPath.toLowerCase();
33
+ if (lower.endsWith("book.generated.ts")) {
34
+ return true;
35
+ }
36
+ if (lower.endsWith(".test.ts") || lower.endsWith(".test.js")) {
37
+ return true;
38
+ }
39
+ const segments = eventPath.split(sep);
40
+ for (const segment of segments) {
41
+ if (segment === "node_modules" || segment === ".git" || segment === "fixtures") {
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ /** Wrap `io` so `bundleOne`'s `wrote <path>` chatter is dropped and the artifact size is captured. */
48
+ function captureBytes(io) {
49
+ let bytes = 0;
50
+ const sink = {
51
+ ...io,
52
+ stderr(text) {
53
+ if (text.startsWith("wrote ")) {
54
+ return;
55
+ }
56
+ io.stderr(text);
57
+ },
58
+ async writeFile(path, contents) {
59
+ bytes = contents.length;
60
+ await io.writeFile(path, contents);
61
+ },
62
+ };
63
+ return { sink, bytes: () => bytes };
64
+ }
65
+ function emitEvent(io, args, style, event, payload = {}) {
66
+ if (args.json) {
67
+ io.stderr(`${JSON.stringify({ event, ts: clock(), ...payload })}\n`);
68
+ return;
69
+ }
70
+ const ts = style.dim(`[${clock()}]`);
71
+ if (event === "started") {
72
+ const books = payload.books;
73
+ io.stderr(`${ts} watching ${books.length} book(s): ${books.join(", ")}\n`);
74
+ return;
75
+ }
76
+ if (event === "bundled") {
77
+ io.stderr(`${ts} ${payload.book} bundled (${payload.bytes} B, ${payload.ms}ms)\n`);
78
+ return;
79
+ }
80
+ if (event === "stopped") {
81
+ io.stderr("stopped\n");
82
+ return;
83
+ }
84
+ io.stderr(`${ts} ${payload.book} ${style.red("ERROR")}: ${payload.message}\n`);
85
+ }
86
+ async function rebuild(io, args, book) {
87
+ const start = Date.now();
88
+ const capture = captureBytes(io);
89
+ try {
90
+ const code = await bundleOne(capture.sink, args, book.dir, book.name, true);
91
+ if (code !== 0) {
92
+ return new Error(`bundle exited with code ${code}`);
93
+ }
94
+ return { bytes: capture.bytes(), ms: Date.now() - start };
95
+ }
96
+ catch (error) {
97
+ return error;
98
+ }
99
+ }
100
+ /** Run one rebuild and emit its bundled/error event. */
101
+ async function rebuildAndReport(io, args, style, book) {
102
+ const result = await rebuild(io, args, book);
103
+ if (result instanceof Error) {
104
+ emitEvent(io, args, style, "error", { book: book.name, message: result.message });
105
+ }
106
+ else {
107
+ emitEvent(io, args, style, "bundled", { book: book.name, bytes: result.bytes, ms: result.ms });
108
+ }
109
+ }
110
+ /**
111
+ * `watch [<dir>]`: rebuild `book.generated.ts` whenever fragments, rules,
112
+ * compositions, code-prompts or `promptbook.json` change. Streams one short
113
+ * line per rebuild to stderr (`[clock] <book> bundled (<bytes> B, <ms>ms)`);
114
+ * stdout stays empty (the contract: stdout = payload, watch has no payload).
115
+ *
116
+ * Discovers every book under the prompts folder and rebuilds each once on
117
+ * startup, then debounces per-book events with a 250 ms window so a burst of
118
+ * edits collapses into one rebuild. SIGINT / SIGTERM closes the watcher and
119
+ * exits 0. Honors `--plain`, `--exclude-code-prompts`, `--json`, and the
120
+ * config / `--dir` resolution chain.
121
+ *
122
+ * `--out <file>` only works in a single-book workspace; the multi-book mode
123
+ * always writes each book's artifact next to its sources.
124
+ */
125
+ export async function cmdWatch(args, io) {
126
+ if (args.check) {
127
+ io.stderr("error: --check is not supported by watch (run `bundle --check --all` from CI)\n");
128
+ return 1;
129
+ }
130
+ const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
131
+ if (promptsDir === null) {
132
+ return 1;
133
+ }
134
+ const workspace = await loadWorkspace(io, promptsDir);
135
+ if (workspace.books.length === 0) {
136
+ io.stderr(`error: no books found under ${promptsDir}\n`);
137
+ return 1;
138
+ }
139
+ if (args.out !== undefined && workspace.books.length > 1) {
140
+ io.stderr("error: --out requires a single book; drop --out to write each book's book.generated.ts\n");
141
+ return 1;
142
+ }
143
+ const style = makeStyle(colorEnabled(io));
144
+ emitEvent(io, args, style, "started", { books: workspace.books.map((b) => b.name) });
145
+ // Initial pass runs in parallel: each book writes its own artifact, no contention.
146
+ await Promise.all(workspace.books.map((book) => rebuildAndReport(io, args, style, book)));
147
+ const watcher = chokidarWatch(promptsDir, {
148
+ ignoreInitial: true,
149
+ awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 25 },
150
+ ignored: (eventPath) => shouldIgnore(eventPath),
151
+ });
152
+ const timers = new Map();
153
+ const scheduleRebuild = (book) => {
154
+ const existing = timers.get(book.name);
155
+ if (existing !== undefined) {
156
+ clearTimeout(existing);
157
+ }
158
+ const timer = setTimeout(() => {
159
+ timers.delete(book.name);
160
+ void rebuildAndReport(io, args, style, book);
161
+ }, DEBOUNCE_MS);
162
+ timers.set(book.name, timer);
163
+ };
164
+ watcher.on("all", (_event, eventPath) => {
165
+ const book = workspace.books.find((b) => eventInBook(b, eventPath));
166
+ if (book === undefined) {
167
+ return;
168
+ }
169
+ scheduleRebuild(book);
170
+ });
171
+ // Hold the promise open until SIGINT / SIGTERM closes the watcher.
172
+ return new Promise((resolveDone) => {
173
+ let stopped = false;
174
+ const stop = async () => {
175
+ if (stopped) {
176
+ return;
177
+ }
178
+ stopped = true;
179
+ for (const timer of timers.values()) {
180
+ clearTimeout(timer);
181
+ }
182
+ timers.clear();
183
+ await watcher.close();
184
+ emitEvent(io, args, style, "stopped");
185
+ process.off("SIGINT", stop);
186
+ process.off("SIGTERM", stop);
187
+ resolveDone(0);
188
+ };
189
+ process.once("SIGINT", stop);
190
+ process.once("SIGTERM", stop);
191
+ });
192
+ }
193
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../../src/commands/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,KAAK,IAAI,aAAa,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,YAAY,EAAW,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,SAAS,EAAc,MAAM,aAAa,CAAC;AACpD,OAAO,EAAa,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,0FAA0F;AAC1F,MAAM,SAAS,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;AAEzD,uFAAuF;AACvF,MAAM,UAAU,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAEvC,6EAA6E;AAC7E,MAAM,WAAW,GAAG,GAAG,CAAC;AAOxB,iGAAiG;AACjG,SAAS,KAAK;IACZ,OAAO,IAAI,IAAI,EAAE,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,+FAA+F;AAC/F,SAAS,WAAW,CAAC,IAAU,EAAE,SAAiB;IAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAC;AAChD,CAAC;AAED,mFAAmF;AACnF,SAAS,YAAY,CAAC,SAAiB;IACrC,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IACtC,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,cAAc,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;YAC/E,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sGAAsG;AACtG,SAAS,YAAY,CAAC,EAAM;IAC1B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,IAAI,GAAO;QACf,GAAG,EAAE;QACL,MAAM,CAAC,IAAI;YACT,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9B,OAAO;YACT,CAAC;YACD,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ;YAC5B,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;YACxB,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAChB,EAAM,EACN,IAAgB,EAChB,KAAY,EACZ,KAAkD,EAClD,OAAO,GAA4B,EAAE;IAErC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QACrE,OAAO;IACT,CAAC;IACD,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,EAAE,GAAG,CAAC,CAAC;IACrC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAiB,CAAC;QACxC,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,aAAa,KAAK,CAAC,MAAM,aAAa,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3E,OAAO;IACT,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,IAAI,aAAa,OAAO,CAAC,KAAK,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QACnF,OAAO;IACT,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACvB,OAAO;IACT,CAAC;IACD,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;AACjF,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,EAAM,EAAE,IAAgB,EAAE,IAAU;IACzD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5E,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACf,OAAO,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,KAAc,CAAC;IACxB,CAAC;AACH,CAAC;AAED,wDAAwD;AACxD,KAAK,UAAU,gBAAgB,CAAC,EAAM,EAAE,IAAgB,EAAE,KAAY,EAAE,IAAU;IAChF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;QAC5B,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IACpF,CAAC;SAAM,CAAC;QACN,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACjG,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAgB,EAAE,EAAM;IACrD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,EAAE,CAAC,MAAM,CAAC,iFAAiF,CAAC,CAAC;QAC7F,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7E,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,EAAE,CAAC,MAAM,CAAC,+BAA+B,UAAU,IAAI,CAAC,CAAC;QACzD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,EAAE,CAAC,MAAM,CAAC,0FAA0F,CAAC,CAAC;QACtG,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAErF,mFAAmF;IACnF,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAE1F,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,EAAE;QACxC,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE;QAC9D,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC;KAChD,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAC;IACjD,MAAM,eAAe,GAAG,CAAC,IAAU,EAAQ,EAAE;QAC3C,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,YAAY,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,KAAK,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC/C,CAAC,EAAE,WAAW,CAAC,CAAC;QAChB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;QACpE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,eAAe,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,mEAAmE;IACnE,OAAO,IAAI,OAAO,CAAS,CAAC,WAAW,EAAE,EAAE;QACzC,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;YACrC,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO;YACT,CAAC;YACD,OAAO,GAAG,IAAI,CAAC;YACf,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gBACpC,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;YACD,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC7B,WAAW,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAUA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,SAAS,CAAC;AAsD7C;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,EAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC/E"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAWA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,SAAS,CAAC;AAyD7C;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,EAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2C/E"}
package/dist/src/run.js CHANGED
@@ -8,8 +8,9 @@ import { cmdLint } from "./commands/lint.js";
8
8
  import { cmdLs } from "./commands/ls.js";
9
9
  import { cmdResolve } from "./commands/resolve.js";
10
10
  import { cmdView } from "./commands/view.js";
11
+ import { cmdWatch } from "./commands/watch.js";
11
12
  import { defaultIO } from "./io.js";
12
- const HELP = `promptbook compose prompts from reusable fragments
13
+ const HELP = `promptbook · compose prompts from reusable fragments
13
14
 
14
15
  Usage:
15
16
  promptbook <command> [options]
@@ -18,7 +19,8 @@ Commands:
18
19
  resolve [<book>/]<prompt> Assemble a prompt and print it to stdout (--all: every book)
19
20
  lint [<prompt>] Run static checks; with no prompt, book rules only
20
21
  eval [<name|glob>] Run fixtures through a model adapter, report pass-rate
21
- bundle [<dir>] Compile a prompts folder into an importable book module
22
+ bundle [<dir>] Compile a prompts folder into an importable book module (--all/--check)
23
+ watch [<dir>] Rebuild book.generated.ts as fragments/rules/compositions change
22
24
  view Start the local web viewer over the workspace (book switcher)
23
25
  annotations <action> Drain the viewer's feedback queue: list | resolve <id> | clear
24
26
  ls List compositions and fragments (--all: cross-book inventory)
@@ -38,13 +40,15 @@ Options:
38
40
  --samples N eval: default samples per fixture (default 1; a fixture's own samples wins)
39
41
  --threshold R eval: a fixture passes when passRate >= R (default 1)
40
42
  --lint eval: run a static lint gate over every variant first
41
- -o, --out <file> bundle: write the generated module to a file (default: stdout)
42
- --plain bundle: emit a plain module (no type-only import; e.g. for Deno)
43
+ -o, --out <file> bundle/watch: write to a file (default: stdout for bundle, <bookDir>/book.generated.ts for watch/--all)
44
+ --plain bundle/watch: emit a plain module (no type-only import; e.g. for Deno)
45
+ --check bundle: compare with the existing output; exit 1 on drift or missing artifact
46
+ --exclude-code-prompts bundle/watch: serialize code-prompts as an empty map (runtime-lean bundle)
43
47
  --port N view: port for the viewer server (default: a free port)
44
48
  --no-open view: do not open the browser after starting
45
49
  --fragments ls: list fragments only
46
50
  --compositions ls: list compositions only
47
- --all ls/resolve: span every book in the workspace
51
+ --all ls/resolve/bundle: span every book in the workspace
48
52
  -h, --help Show this help
49
53
  -v, --version Show the version
50
54
 
@@ -95,6 +99,8 @@ export async function run(argv, io = defaultIO()) {
95
99
  return cmdEval(args, io);
96
100
  case "bundle":
97
101
  return cmdBundle(args, io);
102
+ case "watch":
103
+ return cmdWatch(args, io);
98
104
  case "view":
99
105
  return cmdView(args, io);
100
106
  case "annotations":
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAW,MAAM,SAAS,CAAC;AAE7C,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCZ,CAAC;AAEF,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,oBAAoB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC3E,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc,EAAE,EAAE,GAAO,SAAS,EAAE;IAC5D,IAAI,IAAqC,CAAC;IAC1C,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,EAAE,CAAC,MAAM,CAAC,UAAW,KAAe,CAAC,OAAO,IAAI,CAAC,CAAC;QAClD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,EAAE,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE,IAAI,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC/B,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9B,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3B,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3B,KAAK,QAAQ;YACX,OAAO,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC7B,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3B,KAAK,aAAa;YAChB,OAAO,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClC,KAAK,IAAI;YACP,OAAO,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzB;YACE,EAAE,CAAC,MAAM,CAAC,2BAA2B,IAAI,CAAC,OAAO,+BAA+B,CAAC,CAAC;YAClF,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markbrutx/promptbook-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Thin terminal surface over @markbrutx/promptbook-core: resolve and ls for agents and CI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,11 +48,12 @@
48
48
  "check": "biome check ."
49
49
  },
50
50
  "dependencies": {
51
- "@markbrutx/promptbook-core": "^0.3.0",
52
- "@markbrutx/promptbook-openrouter": "^0.3.0"
51
+ "@markbrutx/promptbook-core": "^0.4.1",
52
+ "@markbrutx/promptbook-openrouter": "^0.4.1",
53
+ "chokidar": "^4.0.3"
53
54
  },
54
55
  "optionalDependencies": {
55
- "@markbrutx/promptbook-viewer": "^0.3.0"
56
+ "@markbrutx/promptbook-viewer": "^0.4.1"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@biomejs/biome": "latest",
package/src/args.ts CHANGED
@@ -20,8 +20,12 @@ export interface ParsedArgs {
20
20
  contextFile?: string;
21
21
  fragments: boolean;
22
22
  compositions: boolean;
23
- /** ls/resolve: operate across every book in the workspace. */
23
+ /** ls/resolve/bundle: operate across every book in the workspace. */
24
24
  all: boolean;
25
+ /** bundle: compare the generated output with the existing artifact and exit non-zero on drift. */
26
+ check: boolean;
27
+ /** bundle: serialize without code-prompts so a runtime bundle stays lean. */
28
+ excludeCodePrompts: boolean;
25
29
  /** lint: estimated token ceiling for the token-budget rule. */
26
30
  maxTokens?: number;
27
31
  /** lint: treat warnings as failures for the exit code. */
@@ -91,6 +95,8 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
91
95
  fragments: { type: "boolean" },
92
96
  compositions: { type: "boolean" },
93
97
  all: { type: "boolean" },
98
+ check: { type: "boolean" },
99
+ "exclude-code-prompts": { type: "boolean" },
94
100
  "max-tokens": { type: "string" },
95
101
  strict: { type: "boolean" },
96
102
  model: { type: "string" },
@@ -137,6 +143,8 @@ export function parseCliArgs(argv: string[]): ParsedArgs {
137
143
  fragments: values.fragments ?? false,
138
144
  compositions: values.compositions ?? false,
139
145
  all: values.all ?? false,
146
+ check: values.check ?? false,
147
+ excludeCodePrompts: values["exclude-code-prompts"] ?? false,
140
148
  maxTokens,
141
149
  strict: values.strict ?? false,
142
150
  model: values.model,
@@ -1,9 +1,13 @@
1
- import { isAbsolute, relative, resolve as resolvePath, sep } from "node:path";
1
+ import { basename, isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path";
2
2
  import type { PromptBook } from "@markbrutx/promptbook-core";
3
3
  import { loadPrompts, serializeBook, serializeBookJson } from "@markbrutx/promptbook-core";
4
4
  import type { ParsedArgs } from "../args.js";
5
5
  import { requirePromptsDir } from "../config.js";
6
6
  import { emitWarnings, type IO } from "../io.js";
7
+ import { loadWorkspace } from "../workspace.js";
8
+
9
+ /** Default name of the artifact file each book emits alongside its sources. */
10
+ const BUNDLE_FILE = "book.generated.ts";
7
11
 
8
12
  /** Rewrite an absolute source path to a portable, forward-slash path relative to `dir`. */
9
13
  function relativizeSource(sourceFile: string, dir: string): string {
@@ -34,32 +38,124 @@ function portableBook(book: PromptBook, dir: string): PromptBook {
34
38
  };
35
39
  }
36
40
 
41
+ /** Return the book with `codePrompts` cleared — used by `--exclude-code-prompts`. */
42
+ function withoutCodePrompts(book: PromptBook): PromptBook {
43
+ return { ...book, codePrompts: new Map() };
44
+ }
45
+
46
+ /** Normalize EOL so a CRLF-checked-in artifact does not falsely look stale on an LF rebuild. */
47
+ function normalizeEol(text: string): string {
48
+ return text.replace(/\r\n/g, "\n");
49
+ }
50
+
51
+ /** First 1-based line index where two strings differ; null when equal. */
52
+ function firstDiffLine(actual: string, expected: string): number | null {
53
+ if (actual === expected) {
54
+ return null;
55
+ }
56
+ const a = actual.split("\n");
57
+ const b = expected.split("\n");
58
+ const max = Math.max(a.length, b.length);
59
+ for (let i = 0; i < max; i += 1) {
60
+ if (a[i] !== b[i]) {
61
+ return i + 1;
62
+ }
63
+ }
64
+ // Unreachable: if the strings differ, at least one index in [0, max) must too.
65
+ throw new Error("firstDiffLine: strings differ but no line diff found");
66
+ }
67
+
68
+ /** Build the output text for one book (TypeScript module or JSON). */
69
+ function renderOutput(book: PromptBook, args: ParsedArgs): string {
70
+ return args.json ? serializeBookJson(book) : serializeBook(book, { typed: !args.plain });
71
+ }
72
+
73
+ /** Pick the artifact path: explicit `--out`, else `book.generated.ts` next to the prompts folder. */
74
+ function targetPath(io: IO, args: ParsedArgs, promptsDir: string): string {
75
+ if (args.out !== undefined) {
76
+ return resolvePath(io.cwd(), args.out);
77
+ }
78
+ return join(promptsDir, BUNDLE_FILE);
79
+ }
80
+
81
+ /** One book's `--check` outcome: file present and matching, drifted, or missing. */
82
+ type CheckOutcome =
83
+ | { status: "up-to-date"; path: string }
84
+ | { status: "stale"; reason: "missing"; path: string }
85
+ | { status: "stale"; reason: "diff"; firstDiffLine: number; path: string };
86
+
87
+ async function checkOne(io: IO, output: string, path: string): Promise<CheckOutcome> {
88
+ let existing: string;
89
+ try {
90
+ existing = await io.fs.readFile(path);
91
+ } catch {
92
+ return { status: "stale", reason: "missing", path };
93
+ }
94
+ const line = firstDiffLine(normalizeEol(output), normalizeEol(existing));
95
+ if (line === null) {
96
+ return { status: "up-to-date", path };
97
+ }
98
+ return { status: "stale", reason: "diff", firstDiffLine: line, path };
99
+ }
100
+
101
+ function emitCheckResult(io: IO, args: ParsedArgs, bookName: string, outcome: CheckOutcome): void {
102
+ if (args.json) {
103
+ const diff =
104
+ outcome.status === "stale"
105
+ ? outcome.reason === "missing"
106
+ ? { reason: "missing", path: outcome.path }
107
+ : { reason: "diff", firstDiffLine: outcome.firstDiffLine }
108
+ : null;
109
+ io.stderr(`${JSON.stringify({ book: bookName, status: outcome.status, diff })}\n`);
110
+ return;
111
+ }
112
+ if (outcome.status === "up-to-date") {
113
+ io.stderr(`${bookName} up to date\n`);
114
+ return;
115
+ }
116
+ if (outcome.reason === "missing") {
117
+ io.stderr(`${bookName} stale (missing ${outcome.path})\n`);
118
+ return;
119
+ }
120
+ io.stderr(`${bookName} stale (first diff at line ${outcome.firstDiffLine})\n`);
121
+ }
122
+
37
123
  /**
38
- * `bundle [<dir>]`: load a prompts folder and emit it as a single importable
39
- * module exporting `book: PromptBook` (so a runtime can import the book instead
40
- * of reading the disk). Writes to stdout, or to a file with `-o`. `--json`
41
- * emits a structured dump instead of the TypeScript module.
124
+ * Bundle one book: load relativize optionally drop code-prompts serialize.
125
+ * Then either `--check` against the existing artifact, write to `--out`/the
126
+ * default `book.generated.ts`, or print to stdout (single-book, no `--out`).
42
127
  *
43
- * The folder comes from the positional `<dir>`, else `--dir`, else config /
44
- * `./prompts` (the standard resolution).
128
+ * Returns the exit code: 0 on success / up-to-date, 1 on drift / write failure.
129
+ * `bookName` is used in `--check` output and warnings; when `inWorkspace` is
130
+ * true the artifact is always written (not printed), matching `bundle --all`.
45
131
  */
46
- export async function cmdBundle(args: ParsedArgs, io: IO): Promise<number> {
47
- const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
48
- if (promptsDir === null) {
49
- return 1;
50
- }
51
-
52
- const book = portableBook(await loadPrompts(promptsDir, io.fs), promptsDir);
132
+ export async function bundleOne(
133
+ io: IO,
134
+ args: ParsedArgs,
135
+ promptsDir: string,
136
+ bookName: string,
137
+ inWorkspace: boolean,
138
+ ): Promise<number> {
139
+ const loaded = await loadPrompts(promptsDir, io.fs);
140
+ // `--exclude-code-prompts` discards them outright; skipping the relativize pass is the whole point.
141
+ const source = args.excludeCodePrompts ? withoutCodePrompts(loaded) : loaded;
142
+ const book = portableBook(source, promptsDir);
53
143
  emitWarnings(io, book.warnings);
54
144
 
55
- const output = args.json ? serializeBookJson(book) : serializeBook(book, { typed: !args.plain });
145
+ const output = renderOutput(book, args);
56
146
 
57
- if (args.out === undefined) {
147
+ if (args.check) {
148
+ const outcome = await checkOne(io, output, targetPath(io, args, promptsDir));
149
+ emitCheckResult(io, args, bookName, outcome);
150
+ return outcome.status === "up-to-date" ? 0 : 1;
151
+ }
152
+
153
+ if (args.out === undefined && !inWorkspace) {
58
154
  io.stdout(output);
59
155
  return 0;
60
156
  }
61
157
 
62
- const outPath = resolvePath(io.cwd(), args.out);
158
+ const outPath = targetPath(io, args, promptsDir);
63
159
  try {
64
160
  await io.writeFile(outPath, output);
65
161
  } catch (error) {
@@ -69,3 +165,53 @@ export async function cmdBundle(args: ParsedArgs, io: IO): Promise<number> {
69
165
  io.stderr(`wrote ${outPath}\n`);
70
166
  return 0;
71
167
  }
168
+
169
+ /** Run `bundleOne` over every book in the workspace in parallel; the worst exit code wins. */
170
+ async function bundleAll(io: IO, args: ParsedArgs, root: string): Promise<number> {
171
+ const workspace = await loadWorkspace(io, root);
172
+ if (workspace.books.length === 0) {
173
+ io.stderr(`error: no books found under ${root}\n`);
174
+ return 1;
175
+ }
176
+ const codes = await Promise.all(
177
+ workspace.books.map((book) => bundleOne(io, args, book.dir, book.name, true)),
178
+ );
179
+ return Math.max(0, ...codes);
180
+ }
181
+
182
+ /**
183
+ * `bundle [<dir>]`: load a prompts folder and emit it as an importable module
184
+ * exporting `book: PromptBook`. Writes to stdout, or to a file with `-o`.
185
+ *
186
+ * Flags:
187
+ * - `--json` emits the structured dump instead of the TypeScript module.
188
+ * - `--plain` drops the type-only import (e.g. for Deno consumers).
189
+ * - `--exclude-code-prompts` serializes code-prompts as an empty map so the
190
+ * runtime bundle stays lean while `code-prompts/` keeps living on disk as
191
+ * metadata for `ls` / the viewer.
192
+ * - `--check` compares the would-be output against the existing artifact
193
+ * (`book.generated.ts` next to the prompts folder, or `--out`); exits 1 on
194
+ * drift or a missing artifact and prints a short hint on stderr.
195
+ * - `--all` walks every book in the workspace and writes each to its own
196
+ * `book.generated.ts`; incompatible with `-o`.
197
+ *
198
+ * The folder comes from the positional `<dir>`, else `--dir`, else config /
199
+ * `./prompts`. `--all` does not accept a single-book `<dir>` operand differently.
200
+ */
201
+ export async function cmdBundle(args: ParsedArgs, io: IO): Promise<number> {
202
+ if (args.all && args.out !== undefined) {
203
+ io.stderr("error: --all is not compatible with -o/--out\n");
204
+ return 1;
205
+ }
206
+
207
+ const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
208
+ if (promptsDir === null) {
209
+ return 1;
210
+ }
211
+
212
+ if (args.all) {
213
+ return bundleAll(io, args, promptsDir);
214
+ }
215
+
216
+ return bundleOne(io, args, promptsDir, basename(promptsDir), false);
217
+ }
@@ -0,0 +1,221 @@
1
+ import { relative, sep } from "node:path";
2
+ import { watch as chokidarWatch } from "chokidar";
3
+ import type { ParsedArgs } from "../args.js";
4
+ import { requirePromptsDir } from "../config.js";
5
+ import { colorEnabled, type IO } from "../io.js";
6
+ import { makeStyle, type Style } from "../style.js";
7
+ import { type Book, loadWorkspace } from "../workspace.js";
8
+ import { bundleOne } from "./bundle.js";
9
+
10
+ /** Folders inside a book whose edits warrant a rebuild (mirrors core's loader layout). */
11
+ const BOOK_DIRS = ["fragments", "rules", "code-prompts"];
12
+
13
+ /** Single-book root files we re-bundle on touch (config edits change the assembly). */
14
+ const BOOK_FILES = ["promptbook.json"];
15
+
16
+ /** Debounce window per book: a burst of edits collapses into one rebuild. */
17
+ const DEBOUNCE_MS = 250;
18
+
19
+ interface RebuildStats {
20
+ bytes: number;
21
+ ms: number;
22
+ }
23
+
24
+ /** Local-time clock prefix (`hours:minutes:seconds`); slices off Date#toTimeString's TZ tail. */
25
+ function clock(): string {
26
+ return new Date().toTimeString().slice(0, 8);
27
+ }
28
+
29
+ /** True when `event` happened inside one of the book's watched folders (or is a root file). */
30
+ function eventInBook(book: Book, eventPath: string): boolean {
31
+ const rel = relative(book.dir, eventPath);
32
+ if (rel === "" || rel.startsWith("..")) {
33
+ return false;
34
+ }
35
+ const parts = rel.split(sep);
36
+ if (parts.length === 1) {
37
+ return BOOK_FILES.includes(parts[0] as string);
38
+ }
39
+ return BOOK_DIRS.includes(parts[0] as string);
40
+ }
41
+
42
+ /** Ignore artifacts, tests, fixtures, and everything VCS / dep manager touches. */
43
+ function shouldIgnore(eventPath: string): boolean {
44
+ const lower = eventPath.toLowerCase();
45
+ if (lower.endsWith("book.generated.ts")) {
46
+ return true;
47
+ }
48
+ if (lower.endsWith(".test.ts") || lower.endsWith(".test.js")) {
49
+ return true;
50
+ }
51
+ const segments = eventPath.split(sep);
52
+ for (const segment of segments) {
53
+ if (segment === "node_modules" || segment === ".git" || segment === "fixtures") {
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+
60
+ /** Wrap `io` so `bundleOne`'s `wrote <path>` chatter is dropped and the artifact size is captured. */
61
+ function captureBytes(io: IO): { sink: IO; bytes: () => number } {
62
+ let bytes = 0;
63
+ const sink: IO = {
64
+ ...io,
65
+ stderr(text) {
66
+ if (text.startsWith("wrote ")) {
67
+ return;
68
+ }
69
+ io.stderr(text);
70
+ },
71
+ async writeFile(path, contents) {
72
+ bytes = contents.length;
73
+ await io.writeFile(path, contents);
74
+ },
75
+ };
76
+ return { sink, bytes: () => bytes };
77
+ }
78
+
79
+ function emitEvent(
80
+ io: IO,
81
+ args: ParsedArgs,
82
+ style: Style,
83
+ event: "started" | "bundled" | "error" | "stopped",
84
+ payload: Record<string, unknown> = {},
85
+ ): void {
86
+ if (args.json) {
87
+ io.stderr(`${JSON.stringify({ event, ts: clock(), ...payload })}\n`);
88
+ return;
89
+ }
90
+ const ts = style.dim(`[${clock()}]`);
91
+ if (event === "started") {
92
+ const books = payload.books as string[];
93
+ io.stderr(`${ts} watching ${books.length} book(s): ${books.join(", ")}\n`);
94
+ return;
95
+ }
96
+ if (event === "bundled") {
97
+ io.stderr(`${ts} ${payload.book} bundled (${payload.bytes} B, ${payload.ms}ms)\n`);
98
+ return;
99
+ }
100
+ if (event === "stopped") {
101
+ io.stderr("stopped\n");
102
+ return;
103
+ }
104
+ io.stderr(`${ts} ${payload.book} ${style.red("ERROR")}: ${payload.message}\n`);
105
+ }
106
+
107
+ async function rebuild(io: IO, args: ParsedArgs, book: Book): Promise<RebuildStats | Error> {
108
+ const start = Date.now();
109
+ const capture = captureBytes(io);
110
+ try {
111
+ const code = await bundleOne(capture.sink, args, book.dir, book.name, true);
112
+ if (code !== 0) {
113
+ return new Error(`bundle exited with code ${code}`);
114
+ }
115
+ return { bytes: capture.bytes(), ms: Date.now() - start };
116
+ } catch (error) {
117
+ return error as Error;
118
+ }
119
+ }
120
+
121
+ /** Run one rebuild and emit its bundled/error event. */
122
+ async function rebuildAndReport(io: IO, args: ParsedArgs, style: Style, book: Book): Promise<void> {
123
+ const result = await rebuild(io, args, book);
124
+ if (result instanceof Error) {
125
+ emitEvent(io, args, style, "error", { book: book.name, message: result.message });
126
+ } else {
127
+ emitEvent(io, args, style, "bundled", { book: book.name, bytes: result.bytes, ms: result.ms });
128
+ }
129
+ }
130
+
131
+ /**
132
+ * `watch [<dir>]`: rebuild `book.generated.ts` whenever fragments, rules,
133
+ * compositions, code-prompts or `promptbook.json` change. Streams one short
134
+ * line per rebuild to stderr (`[clock] <book> bundled (<bytes> B, <ms>ms)`);
135
+ * stdout stays empty (the contract: stdout = payload, watch has no payload).
136
+ *
137
+ * Discovers every book under the prompts folder and rebuilds each once on
138
+ * startup, then debounces per-book events with a 250 ms window so a burst of
139
+ * edits collapses into one rebuild. SIGINT / SIGTERM closes the watcher and
140
+ * exits 0. Honors `--plain`, `--exclude-code-prompts`, `--json`, and the
141
+ * config / `--dir` resolution chain.
142
+ *
143
+ * `--out <file>` only works in a single-book workspace; the multi-book mode
144
+ * always writes each book's artifact next to its sources.
145
+ */
146
+ export async function cmdWatch(args: ParsedArgs, io: IO): Promise<number> {
147
+ if (args.check) {
148
+ io.stderr("error: --check is not supported by watch (run `bundle --check --all` from CI)\n");
149
+ return 1;
150
+ }
151
+
152
+ const promptsDir = await requirePromptsDir(io, args.operands[0] ?? args.dir);
153
+ if (promptsDir === null) {
154
+ return 1;
155
+ }
156
+
157
+ const workspace = await loadWorkspace(io, promptsDir);
158
+ if (workspace.books.length === 0) {
159
+ io.stderr(`error: no books found under ${promptsDir}\n`);
160
+ return 1;
161
+ }
162
+ if (args.out !== undefined && workspace.books.length > 1) {
163
+ io.stderr("error: --out requires a single book; drop --out to write each book's book.generated.ts\n");
164
+ return 1;
165
+ }
166
+
167
+ const style = makeStyle(colorEnabled(io));
168
+ emitEvent(io, args, style, "started", { books: workspace.books.map((b) => b.name) });
169
+
170
+ // Initial pass runs in parallel: each book writes its own artifact, no contention.
171
+ await Promise.all(workspace.books.map((book) => rebuildAndReport(io, args, style, book)));
172
+
173
+ const watcher = chokidarWatch(promptsDir, {
174
+ ignoreInitial: true,
175
+ awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 25 },
176
+ ignored: (eventPath) => shouldIgnore(eventPath),
177
+ });
178
+
179
+ const timers = new Map<string, NodeJS.Timeout>();
180
+ const scheduleRebuild = (book: Book): void => {
181
+ const existing = timers.get(book.name);
182
+ if (existing !== undefined) {
183
+ clearTimeout(existing);
184
+ }
185
+ const timer = setTimeout(() => {
186
+ timers.delete(book.name);
187
+ void rebuildAndReport(io, args, style, book);
188
+ }, DEBOUNCE_MS);
189
+ timers.set(book.name, timer);
190
+ };
191
+
192
+ watcher.on("all", (_event, eventPath) => {
193
+ const book = workspace.books.find((b) => eventInBook(b, eventPath));
194
+ if (book === undefined) {
195
+ return;
196
+ }
197
+ scheduleRebuild(book);
198
+ });
199
+
200
+ // Hold the promise open until SIGINT / SIGTERM closes the watcher.
201
+ return new Promise<number>((resolveDone) => {
202
+ let stopped = false;
203
+ const stop = async (): Promise<void> => {
204
+ if (stopped) {
205
+ return;
206
+ }
207
+ stopped = true;
208
+ for (const timer of timers.values()) {
209
+ clearTimeout(timer);
210
+ }
211
+ timers.clear();
212
+ await watcher.close();
213
+ emitEvent(io, args, style, "stopped");
214
+ process.off("SIGINT", stop);
215
+ process.off("SIGTERM", stop);
216
+ resolveDone(0);
217
+ };
218
+ process.once("SIGINT", stop);
219
+ process.once("SIGTERM", stop);
220
+ });
221
+ }
package/src/run.ts CHANGED
@@ -8,9 +8,10 @@ import { cmdLint } from "./commands/lint.js";
8
8
  import { cmdLs } from "./commands/ls.js";
9
9
  import { cmdResolve } from "./commands/resolve.js";
10
10
  import { cmdView } from "./commands/view.js";
11
+ import { cmdWatch } from "./commands/watch.js";
11
12
  import { defaultIO, type IO } from "./io.js";
12
13
 
13
- const HELP = `promptbook compose prompts from reusable fragments
14
+ const HELP = `promptbook · compose prompts from reusable fragments
14
15
 
15
16
  Usage:
16
17
  promptbook <command> [options]
@@ -19,7 +20,8 @@ Commands:
19
20
  resolve [<book>/]<prompt> Assemble a prompt and print it to stdout (--all: every book)
20
21
  lint [<prompt>] Run static checks; with no prompt, book rules only
21
22
  eval [<name|glob>] Run fixtures through a model adapter, report pass-rate
22
- bundle [<dir>] Compile a prompts folder into an importable book module
23
+ bundle [<dir>] Compile a prompts folder into an importable book module (--all/--check)
24
+ watch [<dir>] Rebuild book.generated.ts as fragments/rules/compositions change
23
25
  view Start the local web viewer over the workspace (book switcher)
24
26
  annotations <action> Drain the viewer's feedback queue: list | resolve <id> | clear
25
27
  ls List compositions and fragments (--all: cross-book inventory)
@@ -39,13 +41,15 @@ Options:
39
41
  --samples N eval: default samples per fixture (default 1; a fixture's own samples wins)
40
42
  --threshold R eval: a fixture passes when passRate >= R (default 1)
41
43
  --lint eval: run a static lint gate over every variant first
42
- -o, --out <file> bundle: write the generated module to a file (default: stdout)
43
- --plain bundle: emit a plain module (no type-only import; e.g. for Deno)
44
+ -o, --out <file> bundle/watch: write to a file (default: stdout for bundle, <bookDir>/book.generated.ts for watch/--all)
45
+ --plain bundle/watch: emit a plain module (no type-only import; e.g. for Deno)
46
+ --check bundle: compare with the existing output; exit 1 on drift or missing artifact
47
+ --exclude-code-prompts bundle/watch: serialize code-prompts as an empty map (runtime-lean bundle)
44
48
  --port N view: port for the viewer server (default: a free port)
45
49
  --no-open view: do not open the browser after starting
46
50
  --fragments ls: list fragments only
47
51
  --compositions ls: list compositions only
48
- --all ls/resolve: span every book in the workspace
52
+ --all ls/resolve/bundle: span every book in the workspace
49
53
  -h, --help Show this help
50
54
  -v, --version Show the version
51
55
 
@@ -98,6 +102,8 @@ export async function run(argv: string[], io: IO = defaultIO()): Promise<number>
98
102
  return cmdEval(args, io);
99
103
  case "bundle":
100
104
  return cmdBundle(args, io);
105
+ case "watch":
106
+ return cmdWatch(args, io);
101
107
  case "view":
102
108
  return cmdView(args, io);
103
109
  case "annotations":