@zvndev/darkmown 2.4.1-next.7.ge25dbba → 2.5.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.
@@ -3,15 +3,86 @@
3
3
  * @returns {DirectiveCatalog}
4
4
  */
5
5
  export function directiveCatalog(): DirectiveCatalog;
6
+ /**
7
+ * One page of a built site, as it appears in the generated `llms.txt` index and
8
+ * `llms-full.txt` corpus.
9
+ * @typedef {object} SitePage
10
+ * @property {string} title
11
+ * @property {string} url Absolute URL when the site declared `site_url`, else the route path.
12
+ * @property {string} description "" when the page has no `description:`.
13
+ * @property {string} [body] The page's source body (frontmatter stripped). Only
14
+ * the corpus carries it; the index omits it.
15
+ */
16
+ /**
17
+ * The site a generated llms file describes. Plain data, passed in by the builder
18
+ * (the only layer that knows the routes), so this module stays a pure renderer.
19
+ * @typedef {object} SiteCorpus
20
+ * @property {string} title
21
+ * @property {string} description
22
+ * @property {string} url Site origin, or "" when the home page set no `site_url`.
23
+ * @property {SitePage[]} pages Every emitted route, in route order.
24
+ */
6
25
  /**
7
26
  * Render the catalog as a compact llms.txt-style markdown cheatsheet — the
8
27
  * artifact an app pastes into a small model's system prompt. Generated entirely
9
28
  * from {@link directiveCatalog}, so it can never disagree with what compiles.
29
+ *
30
+ * When a `site` is supplied (the build always supplies one) the cheatsheet is
31
+ * followed by an INDEX of the site's pages and a pointer to `llms-full.txt`,
32
+ * which is the llms.txt convention: a short index, with the complete corpus one
33
+ * fetch away.
34
+ * @param {SiteCorpus} [site] The built site to index.
10
35
  * @returns {string}
11
36
  */
12
- export function llmsText(): string;
37
+ export function llmsText(site?: SiteCorpus): string;
38
+ /**
39
+ * Render `llms-full.txt`: the COMPLETE corpus that `llms.txt` indexes. Where the
40
+ * cheatsheet is one line per directive, this carries the full syntax template,
41
+ * description, example, and reactivity for every one, plus every compile-error
42
+ * code with its cause and fix: and then the full source text of every page on
43
+ * the site. Same generator, same catalog, so index and corpus cannot disagree.
44
+ * @param {SiteCorpus} [site] The built site whose pages form the corpus.
45
+ * @returns {string}
46
+ */
47
+ export function llmsFullText(site?: SiteCorpus): string;
13
48
  /** The action tokens the catalog exposes — used by the drift guard. */
14
49
  export const CATALOG_ACTION_TOKENS: string[];
50
+ /**
51
+ * One page of a built site, as it appears in the generated `llms.txt` index and
52
+ * `llms-full.txt` corpus.
53
+ */
54
+ export type SitePage = {
55
+ title: string;
56
+ /**
57
+ * Absolute URL when the site declared `site_url`, else the route path.
58
+ */
59
+ url: string;
60
+ /**
61
+ * "" when the page has no `description:`.
62
+ */
63
+ description: string;
64
+ /**
65
+ * The page's source body (frontmatter stripped). Only
66
+ * the corpus carries it; the index omits it.
67
+ */
68
+ body?: string | undefined;
69
+ };
70
+ /**
71
+ * The site a generated llms file describes. Plain data, passed in by the builder
72
+ * (the only layer that knows the routes), so this module stays a pure renderer.
73
+ */
74
+ export type SiteCorpus = {
75
+ title: string;
76
+ description: string;
77
+ /**
78
+ * Site origin, or "" when the home page set no `site_url`.
79
+ */
80
+ url: string;
81
+ /**
82
+ * Every emitted route, in route order.
83
+ */
84
+ pages: SitePage[];
85
+ };
15
86
  /**
16
87
  * Whether the directive
17
88
  * ships zero JS (static), needs the reactive runtime (reactive), or can be
@@ -84,6 +155,14 @@ export type DirectiveCatalog = {
84
155
  * Logical joiners (`and`/`or`/`not`).
85
156
  */
86
157
  predicateJoiners: string[];
158
+ /**
159
+ * Frontmatter keys the framework reads.
160
+ */
161
+ frontmatterKeys: CatalogEntry[];
162
+ /**
163
+ * The `schema:` JSON-LD types the compiler can populate.
164
+ */
165
+ schemaTypes: string[];
87
166
  /**
88
167
  * The `WDxxx` code blocks.
89
168
  */
@@ -66,6 +66,32 @@
66
66
  * read threads through — the real filesystem by default, or an in-memory map
67
67
  * for `compileFromMemory`. Set once by `createCompilation` and reached via
68
68
  * `ctx.comp.reader` in the directive handlers (includes, loop data).
69
+ * @property {Symbol[]} symbols Every declared thing this compile saw, in source
70
+ * order, recorded by {@link recordSymbol} from the handler that parsed it.
71
+ * Surfaced on {@link CompiledPage} so a tool layer can answer "what is in this
72
+ * page?" without parsing `.wd` a second time.
73
+ */
74
+ /**
75
+ * One declared thing in a page, as the compiler saw it.
76
+ *
77
+ * `kind` is the directive family; `name` is what an author would call the thing
78
+ * and is what `refs` matches on. `detail` is a short human string in the same
79
+ * vocabulary the model writes, never a serialized AST: the tool layer's job is
80
+ * to hand back text a model can nearly paste, and translation is where small
81
+ * models lose.
82
+ *
83
+ * @typedef {object} Symbol
84
+ * @property {"state" | "store" | "computed" | "theme" | "fetch" | "action" | "loop" | "form" | "field" | "if" | "include" | "read"} kind
85
+ * @property {string} name The symbol's own name, or the thing it targets.
86
+ * @property {string} [detail] Short source-shaped description, e.g. `cart append p`.
87
+ * @property {boolean} [reactive] For loops: whether it stays reactive at runtime.
88
+ * @property {string} [target] For actions: the state key written.
89
+ * @property {string} [op] For actions: the validated action token.
90
+ * @property {string} file Absolute path of the file the symbol was declared in.
91
+ * @property {number} [endLine] For a block directive (`@loop`, `:if`), the
92
+ * 1-based line its closer sits on, so a tool can address the whole block.
93
+ * @property {number | null} line 1-based line in that file, or null for a read
94
+ * recorded by {@link recordRead} (see there for why prose reads carry no line).
69
95
  */
70
96
  /**
71
97
  * A lexical scope chain for static interpolation values (include args, loop vars).
@@ -119,6 +145,8 @@
119
145
  * `@loop` recorded, or null. The builder reads it to multiply routes.
120
146
  * @property {Set<string>} deps Absolute source-file dependencies of this compile.
121
147
  * @property {Set<string>} collectionsUsed Collection names this compile looped.
148
+ * @property {Symbol[]} symbols Every declared thing the compile saw, in
149
+ * source order. See {@link recordSymbol}.
122
150
  */
123
151
  /**
124
152
  * A compiled page: the document plus its full HTML shell.
@@ -131,6 +159,8 @@
131
159
  * `@loop` recorded, or null. The builder reads it to multiply routes.
132
160
  * @property {Set<string>} deps Absolute source-file dependencies of this compile.
133
161
  * @property {Set<string>} collectionsUsed Collection names this compile looped.
162
+ * @property {Symbol[]} symbols Every declared thing the compile saw, in
163
+ * source order. See {@link recordSymbol}.
134
164
  */
135
165
  /**
136
166
  * Structured, machine-readable companion to a compile error's string message.
@@ -208,6 +238,68 @@ export function normalizeNewlines(text: string): string;
208
238
  * @returns {Compilation}
209
239
  */
210
240
  export function createCompilation(reader: import("./reader.js").Reader): Compilation;
241
+ /**
242
+ * Record one declared thing as the handler that owns it parses it.
243
+ *
244
+ * This is the compiler telling a tool layer what is in a page, rather than a
245
+ * tool layer parsing `.wd` a second time. A shadow parser drifts, and the day it
246
+ * disagrees with `compilePage` it tells a model something true about a file that
247
+ * will not compile.
248
+ *
249
+ * Called from the `handle*` functions, where the 0-based `index` is live, so
250
+ * every symbol carries a real `file:line`. Include bodies share the parent's
251
+ * `Compilation` but compile under their own `ctx.file`, so attribution stays
252
+ * correct with no extra plumbing.
253
+ *
254
+ * @param {Ctx} ctx
255
+ * @param {number} index 0-based line index into the current body slice.
256
+ * @param {Omit<Symbol, "file" | "line">} sym
257
+ * @returns {Symbol} The pushed record, so a caller whose reactive/static decision
258
+ * happens in a later branch can stamp it rather than duplicate that logic.
259
+ */
260
+ export function recordSymbol(ctx: Ctx, index: number, sym: Omit<Symbol, "file" | "line">): Symbol;
261
+ /**
262
+ * Record where a block directive CLOSES, so a tool layer can address the whole
263
+ * construct rather than only its opening line.
264
+ *
265
+ * Measured need, not speculation: with only the opener line recorded, the 1.5B
266
+ * targeted a `@loop` for replacement and swapped out its header, leaving the
267
+ * `@endloop` behind. That produced `[WD010] Stray "@endloop"` as the single
268
+ * most common compile failure of the round-4 tool run. A `@loop` is three or
269
+ * more lines and a tool has to be able to say so.
270
+ *
271
+ * The dispatcher owns this because only it has the block's extent, from
272
+ * `scanBlock`/`scanConditional`. `at` is the index the block handler's own
273
+ * symbol was pushed to; nested symbols recorded while compiling the body land
274
+ * after it and are left alone.
275
+ *
276
+ * @param {Ctx} ctx
277
+ * @param {number} at Index into `ctx.comp.symbols` the block handler wrote to.
278
+ * @param {number} endIndex 0-based body index of the closing line.
279
+ * @returns {void}
280
+ */
281
+ export function stampBlockEnd(ctx: Ctx, at: number, endIndex: number): void;
282
+ /**
283
+ * Record that a page READS a state key through an interpolation.
284
+ *
285
+ * Deliberately line-less. Prose is rendered a chunk at a time by markdown-it, so
286
+ * no 0-based body `index` is in scope at the point a `{ name }` binding resolves,
287
+ * and threading one through the markdown layer would mean tracking source
288
+ * positions inside a renderer that does not care about them.
289
+ *
290
+ * Instead the compiler records the two things only it can know: that the
291
+ * expression resolved to this state key, and the exact expression text. The tool
292
+ * layer locates the line by searching the file for that compiler-supplied
293
+ * string, which is a substring search for a known needle rather than a second
294
+ * parser. Duplicates (a static loop re-renders its body once per row) are the
295
+ * caller's to fold.
296
+ *
297
+ * @param {Ctx} ctx
298
+ * @param {string} key Resolved state key.
299
+ * @param {string} expr The binding expression as authored, e.g. `cart | count`.
300
+ * @returns {void}
301
+ */
302
+ export function recordRead(ctx: Ctx, key: string, expr: string): void;
211
303
  /**
212
304
  * @param {Scope | null} parent
213
305
  * @param {Record<string, unknown>} [vars]
@@ -396,6 +488,59 @@ export type Compilation = {
396
488
  * `ctx.comp.reader` in the directive handlers (includes, loop data).
397
489
  */
398
490
  reader: import("./reader.js").Reader;
491
+ /**
492
+ * Every declared thing this compile saw, in source
493
+ * order, recorded by {@link recordSymbol} from the handler that parsed it.
494
+ * Surfaced on {@link CompiledPage} so a tool layer can answer "what is in this
495
+ * page?" without parsing `.wd` a second time.
496
+ */
497
+ symbols: Symbol[];
498
+ };
499
+ /**
500
+ * One declared thing in a page, as the compiler saw it.
501
+ *
502
+ * `kind` is the directive family; `name` is what an author would call the thing
503
+ * and is what `refs` matches on. `detail` is a short human string in the same
504
+ * vocabulary the model writes, never a serialized AST: the tool layer's job is
505
+ * to hand back text a model can nearly paste, and translation is where small
506
+ * models lose.
507
+ */
508
+ export type Symbol = {
509
+ kind: "state" | "store" | "computed" | "theme" | "fetch" | "action" | "loop" | "form" | "field" | "if" | "include" | "read";
510
+ /**
511
+ * The symbol's own name, or the thing it targets.
512
+ */
513
+ name: string;
514
+ /**
515
+ * Short source-shaped description, e.g. `cart append p`.
516
+ */
517
+ detail?: string | undefined;
518
+ /**
519
+ * For loops: whether it stays reactive at runtime.
520
+ */
521
+ reactive?: boolean | undefined;
522
+ /**
523
+ * For actions: the state key written.
524
+ */
525
+ target?: string | undefined;
526
+ /**
527
+ * For actions: the validated action token.
528
+ */
529
+ op?: string | undefined;
530
+ /**
531
+ * Absolute path of the file the symbol was declared in.
532
+ */
533
+ file: string;
534
+ /**
535
+ * For a block directive (`@loop`, `:if`), the
536
+ * 1-based line its closer sits on, so a tool can address the whole block.
537
+ */
538
+ endLine?: number | undefined;
539
+ /**
540
+ * 1-based line in that file, or null for a read
541
+ * recorded by {@link recordRead} (see there for why prose reads carry no line).
542
+ */
543
+ line: number | null;
399
544
  };
400
545
  /**
401
546
  * A lexical scope chain for static interpolation values (include args, loop vars).
@@ -517,6 +662,11 @@ export type CompiledDocument = {
517
662
  * Collection names this compile looped.
518
663
  */
519
664
  collectionsUsed: Set<string>;
665
+ /**
666
+ * Every declared thing the compile saw, in
667
+ * source order. See {@link recordSymbol}.
668
+ */
669
+ symbols: Symbol[];
520
670
  };
521
671
  /**
522
672
  * A compiled page: the document plus its full HTML shell.
@@ -539,6 +689,11 @@ export type CompiledPage = {
539
689
  * Collection names this compile looped.
540
690
  */
541
691
  collectionsUsed: Set<string>;
692
+ /**
693
+ * Every declared thing the compile saw, in
694
+ * source order. See {@link recordSymbol}.
695
+ */
696
+ symbols: Symbol[];
542
697
  };
543
698
  /**
544
699
  * Structured, machine-readable companion to a compile error's string message.
@@ -2,13 +2,15 @@
2
2
  * Compile a page source file into a full HTML document plus its assets.
3
3
  * @param {string} file Absolute path to the source `.md`/`.wd` file.
4
4
  * @param {Paths} context Resolved project paths.
5
- * @param {{ feed?: { href: string, title: string }, collections?: Map<string, import("./collections.js").CollectionRow[]>, vars?: Record<string, unknown>, reader?: import("./reader.js").Reader }} [options]
5
+ * @param {{ feed?: { href: string, title: string }, site?: SiteContext, collections?: Map<string, import("./collections.js").CollectionRow[]>, vars?: Record<string, unknown>, reader?: import("./reader.js").Reader }} [options]
6
6
  * When a site-wide RSS feed is emitted (the home page set `site_url` and the
7
7
  * site has dated posts), `feed` carries its absolute href + title so every page
8
- * links it. `collections` is the build-time collection index a bare-name
9
- * `@loop` resolves against; `vars` seeds the document scope (the pager `page`
10
- * object on a paginated route). `reader` is the source reader (fs-backed by
11
- * default via `src/compiler.js`; in-memory for `compileFromMemory`).
8
+ * links it. `site` carries the origin + this page's own route (and its resolved
9
+ * breadcrumb trail) so the shell can state a canonical URL. `collections` is the
10
+ * build-time collection index a bare-name `@loop` resolves against; `vars` seeds
11
+ * the document scope (the pager `page` object on a paginated route). `reader` is
12
+ * the source reader (fs-backed by default via `src/compiler.js`; in-memory for
13
+ * `compileFromMemory`).
12
14
  * @returns {CompiledPage}
13
15
  */
14
16
  export function compilePage(file: string, context: Paths, options?: {
@@ -16,6 +18,7 @@ export function compilePage(file: string, context: Paths, options?: {
16
18
  href: string;
17
19
  title: string;
18
20
  };
21
+ site?: SiteContext;
19
22
  collections?: Map<string, import("./collections.js").CollectionRow[]>;
20
23
  vars?: Record<string, unknown>;
21
24
  reader?: import("./reader.js").Reader;
@@ -86,7 +89,7 @@ export function compileFile(file: string, context: Paths, stack: string[], scope
86
89
  * @param {Record<string, string> | Map<string, string>} files Project-relative
87
90
  * path → source content.
88
91
  * @param {string} entryPath Project-relative path of the page to compile.
89
- * @param {{ feed?: { href: string, title: string }, collections?: Map<string, import("./collections.js").CollectionRow[]>, vars?: Record<string, unknown>, cwd?: string }} [options]
92
+ * @param {{ feed?: { href: string, title: string }, site?: SiteContext, collections?: Map<string, import("./collections.js").CollectionRow[]>, vars?: Record<string, unknown>, cwd?: string }} [options]
90
93
  * Same shell options as {@link compilePage}; `cwd` overrides the virtual
91
94
  * project root the relative keys resolve against (defaults to `/`).
92
95
  * @returns {CompiledPage}
@@ -96,10 +99,34 @@ export function compileFromMemory(files: Record<string, string> | Map<string, st
96
99
  href: string;
97
100
  title: string;
98
101
  };
102
+ site?: SiteContext;
99
103
  collections?: Map<string, import("./collections.js").CollectionRow[]>;
100
104
  vars?: Record<string, unknown>;
101
105
  cwd?: string;
102
106
  }): CompiledPage;
107
+ /**
108
+ * Where this page sits on a real, deployed site: the origin the home page
109
+ * declared with `site_url`, the concrete route this page is written to, and the
110
+ * breadcrumb trail the builder resolved from the routes that actually exist.
111
+ * Supplied by `src/builder.js`, the only layer that knows all three.
112
+ */
113
+ export type SiteContext = {
114
+ /**
115
+ * Absolute site origin, e.g. `https://example.com`.
116
+ */
117
+ url: string;
118
+ /**
119
+ * Public route path for THIS page (trailing-slashed).
120
+ */
121
+ route: string;
122
+ /**
123
+ * Resolved crumb trail.
124
+ */
125
+ breadcrumbs?: {
126
+ name: string;
127
+ url: string;
128
+ }[] | undefined;
129
+ };
103
130
  export type Paths = import("./context.js").Paths;
104
131
  export type Meta = import("./context.js").Meta;
105
132
  export type Scope = import("./context.js").Scope;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Whether a page's frontmatter declares an article-family `schema:` type. Used
3
+ * by the page shell to pick `og:type` (`article` vs `website`).
4
+ * @param {Meta} meta
5
+ * @returns {boolean}
6
+ */
7
+ export function isArticleSchema(meta: Meta): boolean;
8
+ /**
9
+ * Everything the JSON-LD builders may read. Only page-derived facts: the shell's
10
+ * already-resolved title/description/image/lang, the page's frontmatter, and the
11
+ * absolute URL the builder resolved for it.
12
+ * @typedef {object} SchemaInput
13
+ * @property {Meta} meta Page frontmatter.
14
+ * @property {string} file Source path, for compile errors.
15
+ * @property {string} title Resolved page title.
16
+ * @property {string} description Resolved page description ("" when absent).
17
+ * @property {string} image Absolute social image URL ("" when absent).
18
+ * @property {string} lang Document language.
19
+ * @property {string} canonical Absolute canonical URL ("" when no `site_url`).
20
+ * @property {{ name: string, url: string }[]} breadcrumbs Resolved crumb trail.
21
+ */
22
+ /**
23
+ * Build the page's `<script type="application/ld+json">` block, or "" when the
24
+ * page opts into nothing. Two independent inputs feed it: the `schema:`
25
+ * frontmatter type (or types), and a breadcrumb trail the builder resolved for a
26
+ * nested route. Two or more nodes are emitted as a JSON array in one script,
27
+ * which is valid JSON-LD and one fewer tag in the head.
28
+ * @param {SchemaInput} input
29
+ * @returns {string} The script tag, or "" when there is nothing to say.
30
+ */
31
+ export function buildJsonLd(input: SchemaInput): string;
32
+ /**
33
+ * @typedef {import("./context.js").Meta} Meta
34
+ */
35
+ /**
36
+ * The `schema:` types Darkmown knows how to populate from real page data:
37
+ * article-family types from a page's title/description/date/author, and the two
38
+ * site-identity types from what a home page already declares. Anything else is
39
+ * a compile error rather than a guess. `BreadcrumbList` is not listed because it
40
+ * is never opted into: it is derived automatically from routes that exist.
41
+ * @type {string[]}
42
+ */
43
+ export const SCHEMA_TYPES: string[];
44
+ /** A concrete, copyable `schema:` frontmatter line for docs and error hints. */
45
+ export const SCHEMA_EXAMPLE: "schema: BlogPosting";
46
+ /**
47
+ * Everything the JSON-LD builders may read. Only page-derived facts: the shell's
48
+ * already-resolved title/description/image/lang, the page's frontmatter, and the
49
+ * absolute URL the builder resolved for it.
50
+ */
51
+ export type SchemaInput = {
52
+ /**
53
+ * Page frontmatter.
54
+ */
55
+ meta: Meta;
56
+ /**
57
+ * Source path, for compile errors.
58
+ */
59
+ file: string;
60
+ /**
61
+ * Resolved page title.
62
+ */
63
+ title: string;
64
+ /**
65
+ * Resolved page description ("" when absent).
66
+ */
67
+ description: string;
68
+ /**
69
+ * Absolute social image URL ("" when absent).
70
+ */
71
+ image: string;
72
+ /**
73
+ * Document language.
74
+ */
75
+ lang: string;
76
+ /**
77
+ * Absolute canonical URL ("" when no `site_url`).
78
+ */
79
+ canonical: string;
80
+ /**
81
+ * Resolved crumb trail.
82
+ */
83
+ breadcrumbs: {
84
+ name: string;
85
+ url: string;
86
+ }[];
87
+ };
88
+ export type Meta = import("./context.js").Meta;
@@ -4,7 +4,7 @@
4
4
  * a different source (see `compileFromMemory`).
5
5
  * @param {string} file Absolute path to the source `.md`/`.wd` file.
6
6
  * @param {import("./config.js").Paths} context Resolved project paths.
7
- * @param {{ feed?: { href: string, title: string }, collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>, vars?: Record<string, unknown>, reader?: import("./compiler/reader.js").Reader }} [options]
7
+ * @param {{ feed?: { href: string, title: string }, site?: import("./compiler/page.js").SiteContext, collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>, vars?: Record<string, unknown>, reader?: import("./compiler/reader.js").Reader }} [options]
8
8
  * @returns {CompiledPage}
9
9
  */
10
10
  export function compilePage(file: string, context: import("./config.js").Paths, options?: {
@@ -12,6 +12,7 @@ export function compilePage(file: string, context: import("./config.js").Paths,
12
12
  href: string;
13
13
  title: string;
14
14
  };
15
+ site?: import("./compiler/page.js").SiteContext;
15
16
  collections?: Map<string, import("./compiler/collections.js").CollectionRow[]>;
16
17
  vars?: Record<string, unknown>;
17
18
  reader?: import("./compiler/reader.js").Reader;
package/types/feeds.d.ts CHANGED
@@ -109,13 +109,56 @@ export function buildRss(channel: {
109
109
  feedUrl: string;
110
110
  }, items: RssItem[]): string;
111
111
  /**
112
- * Build `robots.txt`. Always allows everything (hidden/draft routes are never
113
- * built, so there's nothing to `Disallow`). The `Sitemap:` line is included
114
- * ONLY when `siteUrl` is set — a sitemap URL is only meaningful with an origin.
112
+ * Build `robots.txt`. The wildcard group always allows everything (hidden/draft
113
+ * routes are never built, so there's nothing to `Disallow`), then every
114
+ * {@link AI_CRAWLERS} token is named explicitly with the site's declared policy.
115
+ * The `Sitemap:` line is included ONLY when `siteUrl` is set: a sitemap URL is
116
+ * only meaningful with an origin.
115
117
  * @param {string} [siteUrl] Absolute origin, or "" / undefined to omit the line.
118
+ * @param {string} [policy] `"allow"` (default) or `"deny"`, from the home page's
119
+ * `ai_crawlers:` frontmatter. Validated by the caller ({@link aiCrawlerPolicy}).
116
120
  * @returns {string} The robots.txt body (trailing newline).
117
121
  */
118
- export function buildRobots(siteUrl?: string): string;
122
+ export function buildRobots(siteUrl?: string, policy?: string): string;
123
+ /**
124
+ * Validate the home page's `ai_crawlers:` frontmatter into a policy string.
125
+ * Absent means `allow` (the status quo the wildcard group already grants). A
126
+ * value that is neither `allow` nor `deny` throws rather than defaulting: the
127
+ * likely intent behind a typo like `ai_crawlers: block` is to opt OUT, and
128
+ * silently allowing instead would be exactly the wrong failure direction.
129
+ * @param {import("./compiler.js").Meta} meta Home page frontmatter.
130
+ * @param {string} [file] Home page source path, for the error message.
131
+ * @returns {string} One of {@link AI_CRAWLER_POLICIES}.
132
+ */
133
+ export function aiCrawlerPolicy(meta: import("./compiler.js").Meta, file?: string): string;
134
+ /**
135
+ * The AI crawler and answer-engine robots.txt tokens Darkmown names explicitly.
136
+ *
137
+ * EVERY token here is copied from its own operator's published documentation
138
+ * (checked 2026-08-09): none are guessed, inferred from a log, or carried over
139
+ * from a third-party list. A wrong token is worse than a missing one: it looks
140
+ * like a policy the site is stating and is silently ignored by everybody.
141
+ *
142
+ * `Allow: /` for these is not a permission change (the `User-agent: *` group
143
+ * already allows them); it is an explicit, machine-readable statement of intent,
144
+ * which is what a publisher wants when the goal is to BE cited by answer engines.
145
+ * The same list is what `ai_crawlers: deny` turns into `Disallow: /`, which is
146
+ * the only form that actually changes anything.
147
+ *
148
+ * The `note` is written into the generated file next to the group. Search
149
+ * crawling, model training, and live user-triggered fetches are DIFFERENT
150
+ * permissions from the same operator, and a future reader editing robots.txt by
151
+ * hand needs to see that before collapsing a group.
152
+ * @type {{ operator: string, docs: string, note: string, agents: string[] }[]}
153
+ */
154
+ export const AI_CRAWLERS: {
155
+ operator: string;
156
+ docs: string;
157
+ note: string;
158
+ agents: string[];
159
+ }[];
160
+ /** The accepted `ai_crawlers:` frontmatter values. */
161
+ export const AI_CRAWLER_POLICIES: string[];
119
162
  /** Most-recent-first RSS post cap — a feed is a recent window, not an archive. */
120
163
  export const RSS_ITEM_LIMIT: 20;
121
164
  export type Route = import("./router.js").Route;
package/types/index.d.ts CHANGED
@@ -5,6 +5,9 @@
5
5
  export { buildSite } from "./builder.js";
6
6
  export type { RouteManifestEntry, Paths, Assets } from "./builder.js";
7
7
 
8
+ export { directiveCatalog, llmsText } from "./catalog.js";
9
+ export type { DirectiveCatalog, DirectiveEntry, CatalogEntry, Reactivity } from "./catalog.js";
10
+
8
11
  export {
9
12
  compilePage,
10
13
  compileDocument,
@@ -7,7 +7,9 @@ export function availableTemplates(): string[];
7
7
  * Scaffold a new Darkmown project into `root` from a template, creating files
8
8
  * that don't already exist (never overwriting). `package.json` is generated (so
9
9
  * the name tracks the directory and the `@zvndev/darkmown` dep pins the installed
10
- * version); every other file is copied verbatim from the chosen template.
10
+ * version); `AGENTS.md` is copied from the package so coding agents find the
11
+ * directive reference at the project root; `CLAUDE.md` and `.gitignore` are
12
+ * generated; every other file is copied verbatim from the chosen template.
11
13
  * @param {string} root Absolute path to the target project directory.
12
14
  * @param {{ template?: string }} [options] `template` defaults to `"starter"`.
13
15
  * @returns {{ root: string, template: string }}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Resolve one edit's target to a 1-based inclusive line range in `source`.
3
+ *
4
+ * Every rejection names what was wrong AND what would have worked, because a
5
+ * rejection a model cannot act on is just a failed attempt.
6
+ *
7
+ * @param {string} source
8
+ * @param {Edit} edit
9
+ * @param {import("../compiler/context.js").Symbol[]} symbols
10
+ * @returns {{ok: true, from: number, to: number} | {ok: false, error: string}}
11
+ */
12
+ export function resolveTarget(source: string, edit: Edit, symbols: import("../compiler/context.js").Symbol[]): {
13
+ ok: true;
14
+ from: number;
15
+ to: number;
16
+ } | {
17
+ ok: false;
18
+ error: string;
19
+ };
20
+ /**
21
+ * TOOL ENTRY POINT.
22
+ *
23
+ * @param {Record<string, string>} files
24
+ * @param {string} entry File to edit, in the same key form as `files`.
25
+ * @param {Edit[]} [edits] Missing or empty is reported, not thrown.
26
+ * @returns {{ok: boolean, text: string, data: object}}
27
+ */
28
+ export function apply(files: Record<string, string>, entry: string, edits?: Edit[]): {
29
+ ok: boolean;
30
+ text: string;
31
+ data: object;
32
+ };
33
+ /** The operations a caller may ask for. Closed, and validated per edit. */
34
+ export const OPS: Set<string>;
35
+ /**
36
+ * One edit, as a caller (or a model) writes it.
37
+ *
38
+ * Every field is optional because a malformed edit is REPORTED, not thrown: the
39
+ * whole point of this surface is that a model gets a sentence back telling it
40
+ * what to fix. Typing them as required would describe a contract this module
41
+ * deliberately does not enforce at the boundary.
42
+ */
43
+ export type Edit = {
44
+ /**
45
+ * One of {@link OPS}.
46
+ */
47
+ op?: string | undefined;
48
+ /**
49
+ * 1-based line, as printed by `outline`.
50
+ */
51
+ line?: number | undefined;
52
+ /**
53
+ * A symbol target, e.g. `state:cart`.
54
+ */
55
+ symbol?: string | undefined;
56
+ /**
57
+ * Exact source text to find.
58
+ */
59
+ anchor?: string | undefined;
60
+ /**
61
+ * Replacement or inserted text.
62
+ */
63
+ text?: string | undefined;
64
+ /**
65
+ * What the caller believed is at `line`, for staleness.
66
+ */
67
+ expect?: string | undefined;
68
+ };