diff --git a/app/how-it-works/page.tsx b/app/how-it-works/page.tsx
index 8f071e3..5d1b59d 100644
--- a/app/how-it-works/page.tsx
+++ b/app/how-it-works/page.tsx
@@ -114,10 +114,13 @@ export default function HowItWorksPage() {
One system for humans and agents. The registry
- endpoints are machine-readable JSON with full source, so an AI coding assistant
- installs and composes these components exactly the way a person does. Agent
- interfaces, installable by agents, is not a gimmick; it's the distribution
- model.
+ endpoints are machine-readable JSON with full source, and the contract an agent
+ consumes is generated from the component TypeScript at build time: descriptions,
+ props, and principle tags are parsed from source, never hand-typed, so the feed
+ cannot drift. A dense, prose-free version sized for a context window lives at{" "}
+ /r/contract.json
+ , and it carries something prop schemas can't: the behavioral guarantees each
+ component makes, derived from the principles.
Designed for the pit, not the lap. The system
diff --git a/scripts/build-registry.mjs b/scripts/build-registry.mjs
index 5bb6d71..9c2f9e4 100644
--- a/scripts/build-registry.mjs
+++ b/scripts/build-registry.mjs
@@ -1,7 +1,17 @@
/**
- * Generates shadcn-compatible registry JSON for every component in /registry.
- * Output: public/r/.json — consumed by:
- * npx shadcn@latest add https://agent-pitstop.vercel.app/r/.json
+ * Generates the machine contract for every component in /registry.
+ *
+ * The rule (per the "agent-ready" bar): nothing an agent consumes is
+ * hand-typed. Descriptions, props, and principle tags are parsed from
+ * the component source at build time, so the contract cannot drift
+ * from the truth. Outputs:
+ *
+ * public/r/.json shadcn registry item (full source embedded)
+ * + generated `meta` (description, props, principles)
+ * public/r/index.json catalog, one fetch discovers everything
+ * public/r/contract.json dense mode: prose-free, sized for a context window
+ * public/llms.txt human-shaped overview, generated from the same parse
+ *
* Runs automatically before `next build` (see "prebuild" in package.json).
*/
import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
@@ -10,62 +20,108 @@ import path from "node:path";
const ROOT = process.cwd();
const REGISTRY_DIR = path.join(ROOT, "registry");
const OUT_DIR = path.join(ROOT, "public", "r");
+const SITE = "https://agent-pitstop.vercel.app";
+
+/** Parse the component's header comment: title, description, principles applied. */
+function parseHeader(src) {
+ const block = src.match(/\/\*\*([\s\S]*?)\*\//);
+ if (!block) return { title: null, description: "", principles: [] };
+ const lines = block[1]
+ .split("\n")
+ .map((l) => l.replace(/^\s*\*\s?/, "").trim());
+
+ const titleLine = lines.find((l) => l.includes("— Agent Pit Stop"));
+ const title = titleLine ? titleLine.split("—")[0].trim() : null;
+
+ const descLines = [];
+ let started = false;
+ for (const l of lines) {
+ if (l.includes("— Agent Pit Stop")) {
+ started = true;
+ continue;
+ }
+ if (!started) continue;
+ if (l.startsWith("Principles applied") || l.startsWith("Contributor notes")) break;
+ if (l) descLines.push(l);
+ }
+ const description = descLines.join(" ").trim();
+
+ const principles = [];
+ const pStart = lines.findIndex((l) => l.startsWith("Principles applied"));
+ if (pStart !== -1) {
+ for (let i = pStart + 1; i < lines.length; i++) {
+ const m = lines[i].match(/^-\s*([A-Za-z ]+?):/);
+ if (m) principles.push(m[1].trim());
+ else if (lines[i].startsWith("-") === false && lines[i] !== "") break;
+ }
+ }
+ return { title, description, principles };
+}
+
+/** Parse `export interface Props { ... }` into structured props. */
+function parseProps(src, componentName) {
+ const iface = src.match(
+ new RegExp(`export interface ${componentName}Props \\{([\\s\\S]*?)\\n\\}`),
+ );
+ if (!iface) return [];
+ const body = iface[1];
+ const props = [];
+ const re = /(?:\/\*\*\s*([\s\S]*?)\s*\*\/\s*)?(\w+)(\?)?:\s*([^;]+);/g;
+ let m;
+ while ((m = re.exec(body)) !== null) {
+ props.push({
+ name: m[2],
+ type: m[4].trim().replace(/\s+/g, " "),
+ required: m[3] !== "?",
+ description: (m[1] ?? "").replace(/\s+/g, " ").trim(),
+ });
+ }
+ return props;
+}
-const meta = {
- "tool-call-card": {
- title: "Tool Call Card",
- description: "Show an agent's tool use with legible, progressive disclosure.",
- },
- "approval-gate": {
- title: "Approval Gate",
- description: "Inline permission request with itemized scope. Nothing moves until the human says so.",
- },
- "agent-task-list": {
- title: "Agent Task List",
- description: "The agent's plan, made visible: pending, active, done, and failed.",
- },
- "interrupt-bar": {
- title: "Interrupt Bar",
- description: "The steering wheel and the brake for a running agent. Stop keeps partial work; steering redirects without restarting.",
- },
- "confidence-meter": {
- title: "Confidence Meter",
- description: "Honest uncertainty as coarse bands, paired with a verification affordance below the threshold.",
- },
- "agent-roster": {
- title: "Agent Roster",
- description: "The team sheet for multi-agent runs: role, state, and live activity per agent, blockers named.",
- },
- "agent-inbox": {
- title: "Agent Inbox",
- description: "The review queue for background agents. Needs-review items carry the reason, not just a red dot.",
- },
- "context-budget": {
- title: "Context Budget",
- description: "A delegation contract's limits made visible: consumption vs caps in honest bands, cap-hit as a rendered state.",
- },
-};
+function pascal(name) {
+ return name
+ .split("-")
+ .map((p) => p[0].toUpperCase() + p.slice(1))
+ .join("");
+}
await mkdir(OUT_DIR, { recursive: true });
const entries = await readdir(REGISTRY_DIR, { withFileTypes: true });
let count = 0;
const index = [];
+const dense = [];
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
const name = entry.name;
const dir = path.join(REGISTRY_DIR, name);
const files = (await readdir(dir)).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
+ const mainSrc = await readFile(path.join(dir, `${name}.tsx`), "utf8");
+
+ const header = parseHeader(mainSrc);
+ const componentName = pascal(name);
+ const props = parseProps(mainSrc, componentName);
+ const title = header.title ?? componentName;
+ // first sentence of the parsed description = the one-liner
+ const oneLiner = (header.description.match(/^.*?[.!?](\s|$)/)?.[0] ?? header.description).trim();
const item = {
$schema: "https://ui.shadcn.com/schema/registry-item.json",
name,
type: "registry:component",
- title: meta[name]?.title ?? name,
- description: meta[name]?.description ?? "",
+ title,
+ description: oneLiner,
author: "Anton Castro ",
dependencies: ["motion"],
+ meta: {
+ component: componentName,
+ longDescription: header.description,
+ principles: header.principles,
+ props,
+ docs: `${SITE}/components/${name}`,
+ },
files: await Promise.all(
files.map(async (f) => ({
path: `registry/${name}/${f}`,
@@ -78,30 +134,62 @@ for (const entry of entries) {
await writeFile(path.join(OUT_DIR, `${name}.json`), JSON.stringify(item, null, 2));
index.push({
name,
- title: item.title,
- description: item.description,
- url: `https://agent-pitstop.vercel.app/r/${name}.json`,
- docs: `https://agent-pitstop.vercel.app/components/${name}`,
+ title,
+ description: oneLiner,
+ principles: header.principles,
+ url: `${SITE}/r/${name}.json`,
+ docs: `${SITE}/components/${name}`,
+ });
+ dense.push({
+ n: name,
+ c: componentName,
+ d: oneLiner,
+ pr: header.principles,
+ p: props.map((p) => `${p.name}${p.required ? "" : "?"}:${p.type}${p.description ? ` // ${p.description}` : ""}`),
});
count++;
}
-// machine-readable catalog: one fetch discovers every component
+// full catalog: one fetch discovers every component
await writeFile(path.join(OUT_DIR, "index.json"), JSON.stringify({ items: index }, null, 2));
-// llms.txt: the whole system, consumable by an AI assistant in one request
+// dense mode: prose-free, sized for an agent's context window
+await writeFile(
+ path.join(OUT_DIR, "contract.json"),
+ JSON.stringify(
+ {
+ system: "agent-pitstop",
+ install: `npx shadcn@latest add ${SITE}/r/.json`,
+ deps: ["react", "tailwind", "motion"],
+ guarantees: [
+ "state is never communicated by color alone",
+ "failures keep their inputs visible",
+ "stopping preserves partial work",
+ "nothing irreversible without explicit consent",
+ "confidence renders as bands, never decimals",
+ ],
+ components: dense,
+ },
+ null,
+ 1,
+ ),
+);
+
+// llms.txt: human-shaped overview, generated from the same parse
const llms = `# Agent Pit Stop
> An open source design system for agent interfaces: UX principles with live
> demos and production React components for the moments where agents and
> humans sync (approval, inspection, handoff, recovery). MIT licensed.
> Components depend only on React, Tailwind, and Motion, and install via the
-> shadcn registry. Site: https://agent-pitstop.vercel.app
+> shadcn registry. Site: ${SITE}
## Install
-Any component: npx shadcn@latest add https://agent-pitstop.vercel.app/r/.json
-Catalog (JSON): https://agent-pitstop.vercel.app/r/index.json
+Any component: npx shadcn@latest add ${SITE}/r/.json
+Catalog (JSON): ${SITE}/r/index.json
+Dense contract for agents (props, principles, guarantees): ${SITE}/r/contract.json
+This file, the catalog, and the contract are generated from component source at build time; they cannot drift.
## Components
@@ -109,18 +197,20 @@ ${index.map((i) => `- [${i.title}](${i.docs}): ${i.description} (source: ${i.url
## Principles
-- [Legible Thinking](https://agent-pitstop.vercel.app/principles/legible-thinking): agents lose trust by being wrong invisibly; show intent, state, and evidence
-- [Interruptibility](https://agent-pitstop.vercel.app/principles/interruptibility): users delegate more to agents they can stop; steer beats stop
-- [Delegation Contracts](https://agent-pitstop.vercel.app/principles/delegation-contracts): itemize scope before consent; boundaries beat permissions
-- [Calibrated Trust](https://agent-pitstop.vercel.app/principles/calibrated-trust): bands not decimals; verification cheaper than redoing
-- [Graceful Failure](https://agent-pitstop.vercel.app/principles/graceful-failure): failure is a rendered state; partial work is never discarded
+- [Legible Thinking](${SITE}/principles/legible-thinking): agents lose trust by being wrong invisibly; show intent, state, and evidence
+- [Interruptibility](${SITE}/principles/interruptibility): users delegate more to agents they can stop; steer beats stop
+- [Delegation Contracts](${SITE}/principles/delegation-contracts): itemize scope before consent; boundaries beat permissions
+- [Calibrated Trust](${SITE}/principles/calibrated-trust): bands not decimals; verification cheaper than redoing
+- [Graceful Failure](${SITE}/principles/graceful-failure): failure is a rendered state; partial work is never discarded
## Docs
-- [How it works](https://agent-pitstop.vercel.app/how-it-works)
-- [Rules for the pit](https://agent-pitstop.vercel.app/principles)
+- [How it works](${SITE}/how-it-works)
+- [Rules for the pit](${SITE}/principles)
- [Contributing](https://github.com/acaspx/agent-pitstop/blob/main/CONTRIBUTING.md)
`;
await writeFile(path.join(ROOT, "public", "llms.txt"), llms);
-console.log(`registry: wrote ${count} item(s) + index.json + llms.txt to public/`);
+console.log(
+ `registry: wrote ${count} item(s) + index.json + contract.json + llms.txt (all generated from source)`,
+);