Skip to content

Commit 96af847

Browse files
dmealingclaude
andcommitted
feat(docs): meta docs surfaces prompt text + a prompt data-flow view
`meta docs --site` now shows the actual prompt TEXT and a directional data-flow diagram of the prompt-construction pipeline. - --prompts <dir>: the site's prompt-source search now includes <root>/templates/ and an explicit --prompts dir, so a project whose .mustache sources live outside metaobjects/ or templates/ (e.g. data/templates/) renders the real prompt text on each prompt page (each {{var}} linked to its payload field) instead of a "source missing" note. - Prompt data-flow diagram on the site index: request payload VO --input--> prompt, prompt/output --produces/parses--> response VO, and (where a VO references an entity) the DB-entity --source/persists-- bookends. Derived from template.prompt @payloadRef/@responseRef + template.output/toolcall @payloadRef + each VO's entity refs. A new @responseRef graph edge completes the pipeline. Additive. Gated by the docs-site golden (AI fixture now exercises the full pipeline incl. @responseRef) + a link-graph unit test; docs-site 44 + cli 406 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014xy8powhHYJ6gfFt9Ut8dL
1 parent 16f7f28 commit 96af847

14 files changed

Lines changed: 152 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
## [Unreleased]
99

10+
### Added — `meta docs` surfaces prompt TEXT + a prompt data-flow view (site)
11+
12+
The browsable HTML doc site (`meta docs --site`) now shows the actual prompt text and a
13+
directional prompt-construction data-flow diagram (npm-only; `docs-site` + `cli`):
14+
15+
- **`--prompts <dir>`** — the `--site` prompt-source search now includes `<root>/templates/`
16+
and an explicit `--prompts` dir, so a project whose prompt `.mustache` sources live
17+
outside `metaobjects/` or `templates/` (e.g. `data/templates/`) renders the actual
18+
prompt TEXT on each prompt page (with every `{{var}}` linked to its payload field)
19+
instead of a "source missing" note.
20+
- **Prompt data-flow diagram** on the site index — a directional Mermaid flowchart of the
21+
pipeline: request payload VO ──input──▶ prompt, prompt/output ──produces/parses──▶
22+
response VO, and (where a VO references an entity) the DB-entity ──source/persists──
23+
bookends. Derived structurally from `template.prompt @payloadRef`/`@responseRef` and
24+
`template.output`/`template.toolcall @payloadRef`, plus each VO's entity references. A
25+
new `@responseRef` graph edge (prompt → response VO) completes the pipeline.
26+
27+
Additive: markdown surfaces and non-pipeline site output are unchanged. Gated by the
28+
`docs-site` golden (its AI fixture now exercises the full pipeline) + a link-graph unit test.
29+
1030
## [0.20.2] — 2026-07-25
1131

1232
**npm `0.20.2` only** (NuGet `0.19.3` / PyPI `0.19.5` / Maven Central `7.11.3` unchanged — no changed product file; D1 is a TS-only dialect). A bug-fix patch, no API or vocabulary change.

server/typescript/packages/cli/src/commands/docs.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ interface DocsFlags {
5050
/** Optional override for the project root used to resolve adopter
5151
* `templates/` overrides. Defaults to the metadata root. */
5252
templates?: string;
53+
/** Optional directory holding the prompt `.mustache` sources, for a project
54+
* whose templates live outside the conventional `metaobjects/` or `templates/`
55+
* roots (e.g. `data/templates/`). Added to the `--site` prompt-source search
56+
* path so the HTML site can show the prompt TEXT. Mirrors `verify --prompts`. */
57+
prompts?: string;
5358
/** Which doc surfaces to emit, when overridden on the CLI. `--model` ⇒
5459
* ["model"], `--api` ⇒ ["api"], both ⇒ ["model","api"]. Unset ⇒ defer to the
5560
* resolved `docs:` config (default both). */
@@ -85,6 +90,7 @@ function parseDocsArgs(argv: string[], cwd: string): DocsFlags {
8590
let metadata: string | undefined;
8691
let out: string | undefined;
8792
let templates: string | undefined;
93+
let prompts: string | undefined;
8894
let layout: DocsLayout | undefined;
8995
let baseUrl: string | undefined;
9096
let wantModel = false;
@@ -132,6 +138,12 @@ function parseDocsArgs(argv: string[], cwd: string): DocsFlags {
132138
templates = v;
133139
} else if (a.startsWith("--templates=")) {
134140
templates = a.slice("--templates=".length);
141+
} else if (a === "--prompts") {
142+
const v = argv[++i];
143+
if (v === undefined) throw new Error(`${a} requires a directory argument`);
144+
prompts = v;
145+
} else if (a.startsWith("--prompts=")) {
146+
prompts = a.slice("--prompts=".length);
135147
} else if (a.startsWith("-")) {
136148
throw new Error(`unknown flag: ${a}`);
137149
} else if (metadata === undefined) {
@@ -166,6 +178,7 @@ function parseDocsArgs(argv: string[], cwd: string): DocsFlags {
166178
...(surfaces.length > 0 || wantSite ? { surfaces } : {}),
167179
...(baseUrl !== undefined ? { baseUrl } : {}),
168180
...(templates !== undefined ? { templates } : {}),
181+
...(prompts !== undefined ? { prompts } : {}),
169182
};
170183
}
171184

@@ -187,6 +200,9 @@ export async function docsCommand(args: string[], cwd: string): Promise<number>
187200
}
188201

189202
const metaRoot = resolvePath(cwd, flags.metadata);
203+
// Absolute prompt-source dir for the site (--prompts), for a project whose
204+
// templates live outside metaobjects/ or templates/ (e.g. data/templates/).
205+
const promptsDir = flags.prompts !== undefined ? resolvePath(cwd, flags.prompts) : undefined;
190206

191207
// `--scaffold-site`: copy the docs-site templates + assets into codegen/docs-site/
192208
// so the consumer owns them (ADR-0034 scaffold-and-own). Scaffold and return —
@@ -261,7 +277,7 @@ export async function docsCommand(args: string[], cwd: string): Promise<number>
261277
// WITHOUT building the markdown GenContext — decoupled and one fewer failure
262278
// surface. Combined with --model/--api it is emitted after them (below).
263279
if (flags.site && docsCfg.surfaces.length === 0) {
264-
return emitSite(metaRoot, outDir, configProviders);
280+
return emitSite(metaRoot, outDir, configProviders, promptsDir);
265281
}
266282

267283
// Load metadata standalone — same loader path as migrate/gen. Threads any
@@ -428,7 +444,7 @@ export async function docsCommand(args: string[], cwd: string): Promise<number>
428444

429445
// SITE surface (additive) — emit after the markdown surfaces so both coexist.
430446
if (flags.site) {
431-
const siteRc = await emitSite(metaRoot, outDir, configProviders);
447+
const siteRc = await emitSite(metaRoot, outDir, configProviders, promptsDir);
432448
if (siteRc !== 0) return siteRc;
433449
}
434450

@@ -501,9 +517,26 @@ async function emitSite(
501517
metaRoot: string,
502518
outDir: string,
503519
configProviders?: readonly MetaDataTypeProvider[],
520+
promptsDir?: string,
504521
): Promise<number> {
505522
const siteOutDir = resolvePath(outDir, "site");
523+
// metaobjects/ is REQUIRED (the site loads the model from it) and always first.
524+
// Prompt `.mustache` source is additionally searched in the conventional
525+
// <root>/templates/ and any explicit --prompts dir (for a project whose templates
526+
// live elsewhere, e.g. data/templates/) — else the site can't show the prompt TEXT
527+
// and prints a "source missing" note. Only existing dirs are added, and dirs are
528+
// deduped by BASENAME (the site keys source groups by basename, and rejects a dup).
506529
const sourceDirs = [join(metaRoot, DEFAULT_METADATA_DIR)];
530+
const seenBasenames = new Set([basename(join(metaRoot, DEFAULT_METADATA_DIR))]);
531+
if (promptsDir !== undefined && !existsSync(promptsDir)) {
532+
log.warn(`docs: --prompts dir does not exist: ${promptsDir}`);
533+
}
534+
for (const d of [join(metaRoot, "templates"), ...(promptsDir !== undefined ? [promptsDir] : [])]) {
535+
if (existsSync(d) && !seenBasenames.has(basename(d))) {
536+
sourceDirs.push(d);
537+
seenBasenames.add(basename(d));
538+
}
539+
}
507540
// Scaffold-and-own: when the consumer has copied templates/assets into
508541
// codegen/docs-site/ (via --scaffold-site), use those; else the bundled defaults.
509542
const ownedTemplates = join(metaRoot, "codegen/docs-site/templates");

server/typescript/packages/cli/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ DOCS FLAGS:
4242
<metadata> Project root holding metaobjects/ (default: current directory)
4343
--out <dir>, -o Output directory for the pages (default: ./docs)
4444
--templates <dir> Project root to resolve adopter templates/ overrides (default: <metadata>)
45+
--prompts <dir> Extra dir holding prompt .mustache sources for --site (e.g. data/templates/)
4546
4647
VERIFY FLAGS (ADR-0021 D2 — explicit subverbs; combine any; exit 1 on ANY drift):
4748
--templates Template/prompt {{field}}↔payload drift (the bare-verify default)
@@ -159,6 +160,8 @@ FLAGS:
159160
--site Generate the browsable HTML documentation site (<out>/site/)
160161
--scaffold-site Copy the site's templates + assets into codegen/docs-site/ to own (theme) them
161162
--templates <dir> Project root to resolve adopter templates/ overrides (default: <metadata>)
163+
--prompts <dir> Extra dir holding prompt .mustache sources (for --site) when they
164+
live outside metaobjects/ or templates/ (e.g. data/templates/)
162165
--help, -h Print this help
163166
`,
164167
init: `meta init — scaffold metaobjects/ + .metaobjects/ in the current repo

server/typescript/packages/cli/test/__snapshots__/cli.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ DOCS FLAGS:
3636
<metadata> Project root holding metaobjects/ (default: current directory)
3737
--out <dir>, -o Output directory for the pages (default: ./docs)
3838
--templates <dir> Project root to resolve adopter templates/ overrides (default: <metadata>)
39+
--prompts <dir> Extra dir holding prompt .mustache sources for --site (e.g. data/templates/)
3940
4041
VERIFY FLAGS (ADR-0021 D2 — explicit subverbs; combine any; exit 1 on ANY drift):
4142
--templates Template/prompt {{field}}↔payload drift (the bare-verify default)

server/typescript/packages/docs-site/src/builders/index-data.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,62 @@
11
import { LinkGraph, fqnOf } from "../link-graph.js";
2+
import type { DocNode } from "../link-graph.js";
23
import type { CoverageTracker } from "../coverage.js";
34
import { flowchartDomain, packageFlowchart } from "../mermaid.js";
45
import { harvestPackageDocs } from "../package-docs.js";
56
import { esc } from "../badges.js";
67

78
export interface PkgCard { pkg: string; href: string; objectCount: number; promptCount: number; contractCount: number; purpose: string; }
89
export interface CoreConfig { pin?: string[]; exclude?: string[]; n?: number; }
9-
export interface IndexPageData { title: string; stamp: string; commit: string; stats: { objects: number; tables: number; packages: number; promptVos: number; prompts: number; contracts: number; enums: number }; coreMermaid: string; coreCaption: string; coreLegend: { pkg: string; fill: string; stroke: string }[]; packageMermaid: string; fullEdges: { from: string; to: string; n: number }[]; dataPackages: PkgCard[]; promptPackages: PkgCard[]; }
10+
export interface IndexPageData { title: string; stamp: string; commit: string; stats: { objects: number; tables: number; packages: number; promptVos: number; prompts: number; contracts: number; enums: number }; coreMermaid: string; coreCaption: string; coreLegend: { pkg: string; fill: string; stroke: string }[]; packageMermaid: string; dataflowMermaid: string; dataflowLegend: { pkg: string; fill: string; stroke: string }[]; fullEdges: { from: string; to: string; n: number }[]; dataPackages: PkgCard[]; promptPackages: PkgCard[]; }
11+
12+
/**
13+
* The prompt-construction DATA-FLOW pipeline as a directional flowchart:
14+
* DB entity ──source──▶ request payload VO ──input──▶ prompt
15+
* prompt/output ──produces/parses──▶ response VO ──persists──▶ DB entity
16+
* Derived structurally from `template.prompt @payloadRef` (request VO) / `@responseRef`
17+
* (response VO) and `template.output`/`template.toolcall @payloadRef` (response VO),
18+
* plus each VO's field/origin/extends links back to entities. Returns undefined when
19+
* the model declares no template pipeline (nothing to draw).
20+
*/
21+
function buildDataflow(g: LinkGraph): { mermaid: string; legend: { pkg: string; fill: string; stroke: string }[] } | undefined {
22+
const isEntity = (d: DocNode | undefined): d is DocNode => !!d && d.kind === "object" && d.node.subType === "entity";
23+
// Entities a VO is linked to, in EITHER direction (VO.field→entity, or entity extends VO).
24+
const linkedEntities = (voFqn: string): DocNode[] => {
25+
const out: DocNode[] = [];
26+
for (const r of g.refsFrom(voFqn)) if (r.kind === "field" || r.kind === "origin" || r.kind === "fk") { const d = g.byFqn(r.to); if (isEntity(d)) out.push(d); }
27+
for (const r of g.refsTo(voFqn)) if (r.kind === "extends" || r.kind === "field" || r.kind === "origin" || r.kind === "fk") { const d = g.byFqn(r.from); if (isEntity(d)) out.push(d); }
28+
return out;
29+
};
30+
const nodes = new Map<string, { name: string; pkg: string; kind: string }>();
31+
const edges: { from: string; to: string; label: string }[] = [];
32+
const seen = new Set<string>();
33+
const addNode = (d: DocNode): string => { nodes.set(d.name, { name: d.name, pkg: d.pkg, kind: d.node.subType }); return d.name; };
34+
const addEdge = (from: string, to: string, label: string): void => { const k = `${from}|${to}`; if (from !== to && !seen.has(k)) { seen.add(k); edges.push({ from, to, label }); } };
35+
36+
for (const t of g.nodes().filter((n) => n.kind !== "object")) {
37+
const tFqn = fqnOf(t.node);
38+
const isPrompt = t.kind === "prompt";
39+
const payload = g.refsFrom(tFqn).find((r) => r.kind === "payload");
40+
const response = g.refsFrom(tFqn).find((r) => r.kind === "response");
41+
const tName = addNode(t);
42+
if (isPrompt && payload) {
43+
const vo = g.byFqn(payload.to);
44+
if (vo) { const voName = addNode(vo);
45+
for (const e of linkedEntities(payload.to)) addEdge(addNode(e), voName, "source");
46+
addEdge(voName, tName, "input"); }
47+
}
48+
// RESPONSE half: a prompt's @responseRef VO, or an output/toolcall's @payloadRef VO.
49+
const respRef = isPrompt ? response : payload;
50+
if (respRef) {
51+
const vo = g.byFqn(respRef.to);
52+
if (vo) { const voName = addNode(vo);
53+
addEdge(tName, voName, isPrompt ? "produces" : "parses");
54+
for (const e of linkedEntities(respRef.to)) addEdge(voName, addNode(e), "persists"); }
55+
}
56+
}
57+
if (edges.length === 0) return undefined;
58+
return flowchartDomain([...nodes.values()], edges);
59+
}
1060

1161
export function buildIndexPage(g: LinkGraph, cov: CoverageTracker, opts: { title: string; stamp: string; commit: string; core?: CoreConfig | undefined; sourceDirs?: string[] | undefined }): IndexPageData {
1262
const objs = g.nodes().filter((n) => n.kind === "object");
@@ -56,6 +106,7 @@ export function buildIndexPage(g: LinkGraph, cov: CoverageTracker, opts: { title
56106
const heroNodes = [...shown.values()].filter((dn) => connected.has(dn.name));
57107
const hero = flowchartDomain(heroNodes.map((dn) => ({ name: dn.name, pkg: dn.pkg, kind: dn.node.subType })), heroEdges);
58108
const coreMermaid = hero.mermaid, coreLegend = hero.legend;
109+
const dataflow = buildDataflow(g);
59110
const coreCaption = `${heroNodes.length} of the most-connected objects (entities, views, and payloads), colored by domain.`;
60111
// package docs for purpose cards
61112
const pdocs = harvestPackageDocs(opts.sourceDirs ?? []);
@@ -89,6 +140,8 @@ export function buildIndexPage(g: LinkGraph, cov: CoverageTracker, opts: { title
89140
coreCaption,
90141
coreLegend,
91142
packageMermaid: packageFlowchart(fullEdges.filter((e) => e.n >= 2), counts),
143+
dataflowMermaid: dataflow?.mermaid ?? "",
144+
dataflowLegend: dataflow?.legend ?? [],
92145
fullEdges,
93146
dataPackages: pkgs.filter((p) => !promptPkgSet.has(p)).map(card),
94147
promptPackages: pkgs.filter((p) => promptPkgSet.has(p)).map(card) };

server/typescript/packages/docs-site/src/link-graph.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { type LoadedModel, treeOf } from "./load.js";
55
export interface DocNode { kind: "object" | "prompt" | "output"; name: string; pkg: string; pkgPath: string; href: string; node: MetaData; tree: string; }
66
export interface Ref {
77
from: string; to: string; via: string;
8-
kind: "field" | "fk" | "extends" | "payload" | "relationship" | "origin";
8+
kind: "field" | "fk" | "extends" | "payload" | "response" | "relationship" | "origin";
99
cardinality?: "one" | "many" | undefined;
1010
through?: string | undefined; // junction FQN (M:N)
1111
sourceJoinField?: string | undefined; // junction source FK (M:N)
@@ -145,6 +145,13 @@ export class LinkGraph {
145145
const to = resolveRef(p, dn.pkg);
146146
if (to) addRef({ from: fqn, to, via: "payloadRef", kind: "payload" });
147147
}
148+
// A prompt's @responseRef is the response value-object it PRODUCES — the
149+
// missing hop of the data-flow pipeline (prompt → response VO → entity).
150+
const r = dn.node.attr("responseRef");
151+
if (typeof r === "string") {
152+
const to = resolveRef(r, dn.pkg);
153+
if (to) addRef({ from: fqn, to, via: "responseRef", kind: "response" });
154+
}
148155
}
149156
}
150157
}

server/typescript/packages/docs-site/templates/index.html.mustache

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
<div class="text-[11px] opacity-50 mt-1"><span class="font-mono">▭</span> entity · <span class="font-mono">⬭</span> value object · <span class="font-mono">▱</span> view</div>{{/coreLegend.length}}
1818
</div>
1919
{{/coreMermaid}}
20+
{{#dataflowMermaid}}
21+
<div class="card bg-base-100 border border-base-300 p-4 my-4">
22+
<div class="font-semibold mb-2">Prompt data-flow</div>
23+
<div class="pl-diagram"><pre class="mermaid">{{dataflowMermaid}}</pre></div>
24+
<div class="text-xs opacity-50 mt-1">The prompt-construction pipeline: request payload VOs flow <span class="font-mono">input</span>→ prompts; tool-calls/outputs <span class="font-mono">parse/produce</span>→ response VOs. A VO that references an entity adds the DB <span class="font-mono">source</span>/<span class="font-mono">persists</span> hops (entity → payload VO … response VO → entity).</div>
25+
{{#dataflowLegend.length}}<div class="flex flex-wrap gap-1 mt-2 text-xs">{{#dataflowLegend}}<span class="badge badge-xs" style="background:{{fill}};border-color:{{stroke}};color:#cbd5e1">{{pkg}}</span>{{/dataflowLegend}}</div>{{/dataflowLegend.length}}
26+
</div>
27+
{{/dataflowMermaid}}
2028
{{#packageMermaid}}
2129
<div class="card bg-base-100 border border-base-300 p-4 my-4">
2230
<div class="font-semibold mb-2">Package dependencies</div>

server/typescript/packages/docs-site/test/fixture/golden/acme/ai/NpcResponse.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ <h1 class="text-2xl font-bold font-mono mt-1">NpcResponse
8181

8282

8383

84-
<section id="s-referenced-by" class="mt-6 text-sm"><span class="opacity-50">referenced by</span> <a class="link font-mono text-xs" href="npcReviewOutput.html" title="payloadRef">npcReviewOutput</a> </section>
84+
<section id="s-referenced-by" class="mt-6 text-sm"><span class="opacity-50">referenced by</span> <a class="link font-mono text-xs" href="npcReview.html" title="responseRef">npcReview</a> <a class="link font-mono text-xs" href="npcReviewOutput.html" title="payloadRef">npcReviewOutput</a> </section>
8585
<div class="text-sm mt-1"><span class="opacity-50">used by templates</span> <a class="link font-mono text-xs" href="npcReviewOutput.html">npcReviewOutput</a> </div>
8686
<div class="text-[10px] opacity-40 mt-6 font-mono">meta.ai.yaml</div>
8787
</main>

server/typescript/packages/docs-site/test/fixture/golden/acme/ai/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
<h1 class="text-2xl font-bold font-mono">Fixture</h1>
5050

5151
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 my-4">
52-
<a href="ItemView.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">ItemView</div><div class="text-xs opacity-50">1 refs</div></a><a href="NpcPayload.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">NpcPayload</div><div class="text-xs opacity-50">1 refs</div></a><a href="NpcResponse.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">NpcResponse</div><div class="text-xs opacity-50">1 refs</div></a><a href="OrderLine.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">OrderLine</div><div class="text-xs opacity-50">0 refs</div></a>
52+
<a href="NpcResponse.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">NpcResponse</div><div class="text-xs opacity-50">2 refs</div></a><a href="ItemView.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">ItemView</div><div class="text-xs opacity-50">1 refs</div></a><a href="NpcPayload.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">NpcPayload</div><div class="text-xs opacity-50">1 refs</div></a><a href="OrderLine.html" class="card bg-base-100 border border-base-300 p-3"><div class="font-mono font-semibold">OrderLine</div><div class="text-xs opacity-50">0 refs</div></a>
5353
</div>
5454
<div class="card bg-base-100 border border-base-300 p-2 pl-diagram my-4"><pre class="mermaid">%%{init: {'theme':'base','themeVariables':{'darkMode':true,'background':'#0b1220','primaryColor':'#1e2a3a','primaryTextColor':'#cbd5e1','primaryBorderColor':'#4a7fa5','lineColor':'#64748b','secondaryColor':'#1a2535','tertiaryColor':'#0f1826','fontSize':'13px'}}}%%
5555
erDiagram

0 commit comments

Comments
 (0)