youmd 0.13.10 → 0.13.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "youmd",
3
- "version": "0.13.10",
3
+ "version": "0.13.11",
4
4
  "description": "Identity context protocol for the agent internet — an MCP where the context is you. CLI for the You.md platform.",
5
5
  "bin": {
6
6
  "you": "dist/you.js",
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const FONT = {
8
+ mono: '"SFMono-Regular",Consolas,"Liberation Mono",monospace',
9
+ sans: 'Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',
10
+ serif: 'Iowan Old Style,"Palatino Linotype",Palatino,Georgia,serif',
11
+ };
12
+
13
+ function escapeHtml(value) {
14
+ return String(value).replace(/[&<>"']/g, (character) => ({
15
+ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
16
+ })[character]);
17
+ }
18
+
19
+ function stableJson(value) {
20
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
21
+ if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
22
+ return JSON.stringify(value);
23
+ }
24
+
25
+ function sha256(value) {
26
+ return crypto.createHash("sha256").update(value).digest("hex");
27
+ }
28
+
29
+ function channel(hex, offset) {
30
+ return Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;
31
+ }
32
+
33
+ function luminance(hex) {
34
+ const linear = [1, 3, 5].map((offset) => {
35
+ const value = channel(hex, offset);
36
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
37
+ });
38
+ return (0.2126 * linear[0]) + (0.7152 * linear[1]) + (0.0722 * linear[2]);
39
+ }
40
+
41
+ export function contrastRatio(first, second) {
42
+ const [bright, dark] = [luminance(first), luminance(second)].sort((a, b) => b - a);
43
+ return (bright + 0.05) / (dark + 0.05);
44
+ }
45
+
46
+ export function assertTheme(theme) {
47
+ if (!theme) throw new Error("artifact themeRef does not resolve to a declared theme");
48
+ if (contrastRatio(theme.palette.text, theme.palette.canvas) < 4.5) throw new Error(`${theme.id}: text/canvas contrast must be at least 4.5:1`);
49
+ if (contrastRatio(theme.palette.muted, theme.palette.canvas) < 3) throw new Error(`${theme.id}: muted/canvas contrast must be at least 3:1`);
50
+ if (contrastRatio(theme.palette.accent, theme.palette.canvas) < 3) throw new Error(`${theme.id}: accent/canvas contrast must be at least 3:1`);
51
+ }
52
+
53
+ function list(items, className = "") {
54
+ return `<ul${className ? ` class="${className}"` : ""}>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
55
+ }
56
+
57
+ function renderHorizon(name, horizon) {
58
+ return `<article class="horizon"><div class="eyebrow">${escapeHtml(name)} · ${escapeHtml(horizon.period)}</div><h3>${escapeHtml(horizon.objective)}</h3>${list(horizon.outcomes)}<footer>${escapeHtml(horizon.status)} · review ${escapeHtml(horizon.reviewAt.slice(0, 10))}</footer></article>`;
59
+ }
60
+
61
+ function renderLane(lane) {
62
+ return `<li class="lane"><span class="status status-${escapeHtml(lane.status)}"></span><div><strong>${escapeHtml(lane.name)}</strong><small>${escapeHtml(lane.kind)} · ${escapeHtml(lane.status)} · ${escapeHtml(lane.ownerAgentRef)}</small></div></li>`;
63
+ }
64
+
65
+ export function renderArtifact({ manifest, artifact, markdown }) {
66
+ const theme = (manifest.themes || []).find((candidate) => candidate.id === artifact.themeRef);
67
+ assertTheme(theme);
68
+ const p = theme.palette;
69
+ const compact = theme.typography.density === "compact";
70
+ const briefing = theme.presentation === "briefing";
71
+ const manifestHash = sha256(stableJson(manifest));
72
+ const markdownHash = sha256(markdown);
73
+ const relevantLanes = manifest.lanes.filter((lane) => lane.scopeRefs.some((scope) => artifact.scopeRefs.includes(scope)));
74
+ const provenance = [...new Set([...artifact.provenance, ...manifest.northStar.evidenceRefs])];
75
+ const horizonOrder = ["year", "quarter", "month", "week"];
76
+ const headline = briefing ? "Operating brief" : "Living vision";
77
+ return `<!doctype html>
78
+ <html lang="en" data-theme="${escapeHtml(theme.id)}" data-manifest-sha256="${manifestHash}" data-markdown-sha256="${markdownHash}">
79
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="${escapeHtml(theme.mode)}"><title>${escapeHtml(artifact.title)}</title>
80
+ <style>
81
+ :root{--canvas:${p.canvas};--surface:${p.surface};--text:${p.text};--muted:${p.muted};--accent:${p.accent};--line:${p.line};--positive:${p.positive};--attention:${p.attention};--display:${FONT[theme.typography.display]};--body:${FONT[theme.typography.body]};--space:${compact ? "20px" : "28px"}}
82
+ *{box-sizing:border-box}html{background:var(--canvas);color:var(--text)}body{margin:0;background:var(--canvas);font:15px/1.55 var(--body)}a{color:inherit}main{max-width:${briefing ? "1180px" : "980px"};margin:auto;padding:clamp(32px,6vw,84px) clamp(20px,5vw,64px)}header{border-bottom:1px solid var(--line);padding-bottom:clamp(36px,7vw,90px)}.brand,.eyebrow,.meta,footer,small{font-family:var(--display);letter-spacing:.07em}.brand{display:flex;align-items:center;gap:12px;text-transform:uppercase;font-size:12px;color:var(--muted)}.mark{display:grid;place-items:center;width:32px;height:32px;background:var(--accent);color:var(--canvas);font-weight:700}.eyebrow{margin-top:52px;color:var(--accent);font-size:11px;text-transform:uppercase}h1,h2,h3{font-family:var(--display);font-weight:500;letter-spacing:-.035em}h1{max-width:900px;margin:14px 0 22px;font-size:clamp(42px,8vw,88px);line-height:.98}h2{font-size:clamp(28px,4vw,46px);line-height:1.05}h3{font-size:20px;line-height:1.2}.lede{max-width:800px;font-size:clamp(18px,2.5vw,27px);color:var(--muted)}.meta{display:flex;flex-wrap:wrap;gap:9px 18px;margin-top:32px;color:var(--muted);font-size:10px}.meta span:first-child{color:var(--attention)}section{padding:clamp(36px,6vw,72px) 0;border-bottom:1px solid var(--line)}.north-star{display:grid;grid-template-columns:${briefing ? "minmax(180px,.6fr) minmax(0,1.4fr)" : "1fr"};gap:var(--space)}.north-star blockquote{margin:0;font:clamp(26px,4vw,48px)/1.16 var(--display);letter-spacing:-.035em}.north-star ul{color:var(--muted)}.grid{display:grid;grid-template-columns:repeat(${briefing ? "2" : "1"},minmax(0,1fr));gap:1px;background:var(--line);border:1px solid var(--line)}.horizon{min-height:${compact ? "210px" : "250px"};padding:var(--space);background:var(--surface)}.horizon .eyebrow{margin-top:0}.horizon ul{padding-left:18px;color:var(--muted)}footer{margin-top:24px;color:var(--muted);font-size:10px;text-transform:uppercase}.lanes{list-style:none;padding:0;margin:0}.lane{display:grid;grid-template-columns:10px 1fr;gap:14px;padding:17px 0;border-top:1px solid var(--line)}.lane small{display:block;margin-top:3px;color:var(--muted);font-size:10px}.status{width:7px;height:7px;margin-top:8px;background:var(--muted)}.status-active,.status-done{background:var(--positive)}.status-blocked,.status-review{background:var(--attention)}.evidence{columns:${briefing ? "2" : "1"};padding-left:18px;color:var(--muted);word-break:break-word}.review{background:var(--surface);padding:var(--space);border-left:3px solid var(--attention)}@media(max-width:720px){.north-star,.grid{grid-template-columns:1fr}.evidence{columns:1}main{padding-inline:20px}}
83
+ </style></head>
84
+ <body><main><header><div class="brand"><span class="mark">${escapeHtml(theme.mark.monogram)}</span><span>${escapeHtml(theme.mark.label)}</span></div><div class="eyebrow">${headline} · ${escapeHtml(artifact.family)}</div><h1>${escapeHtml(artifact.title)}</h1><p class="lede">${escapeHtml(manifest.positioning[theme.positioningKey])}</p><div class="meta"><span>${escapeHtml(artifact.status)}</span><span>${escapeHtml(artifact.candidateRevision)}</span><span>v${artifact.version}</span><span>${escapeHtml(artifact.trustDomain)}</span><span>review ${escapeHtml(artifact.reviewAt.slice(0, 10))}</span></div></header>
85
+ <section class="north-star"><div><div class="eyebrow">North Star</div><h2>What does not drift</h2></div><div><blockquote>${escapeHtml(manifest.northStar.statement)}</blockquote>${list(manifest.northStar.invariants)}</div></section>
86
+ <section><div class="eyebrow">Focus horizons</div><h2>One direction, four clocks</h2><div class="grid">${horizonOrder.map((name) => renderHorizon(name, manifest.horizons[name])).join("")}</div></section>
87
+ <section><div class="eyebrow">Agent lanes</div><h2>Who is moving what</h2><ul class="lanes">${relevantLanes.map(renderLane).join("") || "<li class=\"lane\"><span class=\"status\"></span><div><strong>No scoped lane declared</strong></div></li>"}</ul></section>
88
+ <section><div class="eyebrow">Evidence</div><h2>Receipts, not vibes</h2>${list(provenance, "evidence")}</section>
89
+ <section><div class="review"><div class="eyebrow">Human review required</div><h2>${artifact.status === "accepted" ? "Accepted evidence is recorded" : "This candidate changes nothing until you decide"}</h2><p>Artifact ${escapeHtml(artifact.id)} is bound to ${escapeHtml(artifact.candidateRevision)}. Its rendered HTML is derived from the declared JSON and Markdown hashes above.</p></div></section>
90
+ </main></body></html>`;
91
+ }
92
+
93
+ export function renderFromManifest(manifestPath, artifactId) {
94
+ const absoluteManifest = path.resolve(manifestPath);
95
+ let root = path.dirname(absoluteManifest);
96
+ while (path.dirname(root) !== root && !fs.existsSync(path.join(root, ".git"))) root = path.dirname(root);
97
+ if (!fs.existsSync(path.join(root, ".git"))) throw new Error("could not find a Git repository from manifest path");
98
+ const manifest = JSON.parse(fs.readFileSync(absoluteManifest, "utf8"));
99
+ const artifact = artifactId ? manifest.artifacts.find((item) => item.id === artifactId) : manifest.artifacts.at(-1);
100
+ if (!artifact) throw new Error(`artifact not found: ${artifactId || "latest"}`);
101
+ if (!artifact.formats?.markdown || !artifact.formats?.html) throw new Error(`${artifact.id}: markdown and html formats are required for rendering`);
102
+ const markdownPath = path.resolve(root, artifact.formats.markdown);
103
+ const outputPath = path.resolve(root, artifact.formats.html);
104
+ if (!markdownPath.startsWith(`${root}${path.sep}`) || !outputPath.startsWith(`${root}${path.sep}`)) throw new Error("artifact projection path escapes repository");
105
+ const html = renderArtifact({ manifest, artifact, markdown: fs.readFileSync(markdownPath, "utf8") });
106
+ fs.writeFileSync(outputPath, html);
107
+ return { artifact, html, outputPath };
108
+ }
109
+
110
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
111
+ const [manifestPath, artifactId] = process.argv.slice(2);
112
+ if (!manifestPath) throw new Error("usage: render-living-vision-artifact.mjs path/to/VISION_ARTIFACTS.json [artifact-id]");
113
+ const result = renderFromManifest(manifestPath, artifactId);
114
+ console.log(`rendered ${result.artifact.id} -> ${result.outputPath}`);
115
+ }
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { createRequire } from "node:module";
5
5
  import { execFileSync } from "node:child_process";
6
+ import { assertTheme, renderArtifact } from "./render-living-vision-artifact.mjs";
6
7
 
7
8
  const require = createRequire(import.meta.url);
8
9
  const Ajv2020 = require("ajv/dist/2020").default;
@@ -41,10 +42,18 @@ const ajv = new Ajv2020({ allErrors: true, strict: true });
41
42
  addFormats(ajv);
42
43
  const validate = ajv.compile(schema);
43
44
  if (!validate(manifest)) throw new Error(`invalid Vision Artifact JSON: ${ajv.errorsText(validate.errors)}`);
45
+ const themes = new Map();
46
+ for (const theme of manifest.themes || []) {
47
+ if (themes.has(theme.id)) throw new Error(`${theme.id}: duplicate theme id`);
48
+ assertTheme(theme);
49
+ themes.set(theme.id, theme);
50
+ }
44
51
  for (const artifact of manifest.artifacts) {
45
52
  if (!artifact.candidateRevision) throw new Error(`${artifact.id}: candidateRevision is required for a new Living Vision Artifact`);
46
53
  if (artifact.status === "accepted" && !artifact.lineage.acceptanceRef) throw new Error(`${artifact.id}: accepted artifacts require an attributable acceptanceRef`);
47
54
  if (artifact.status !== "accepted" && artifact.lineage.acceptanceRef) throw new Error(`${artifact.id}: agents must not pre-populate acceptanceRef`);
55
+ if (!artifact.themeRef) throw new Error(`${artifact.id}: themeRef is required for a governed Living Vision Artifact`);
56
+ if (!themes.has(artifact.themeRef)) throw new Error(`${artifact.id}: themeRef does not resolve to a declared theme`);
48
57
  for (const relative of Object.values(artifact.formats)) {
49
58
  try { safeRepositoryFile(root, relative); }
50
59
  catch (error) { throw new Error(`${artifact.id}: ${error.message}`); }
@@ -55,5 +64,7 @@ for (const artifact of manifest.artifacts) {
55
64
  const html = fs.readFileSync(safeRepositoryFile(root, artifact.formats.html), "utf8");
56
65
  if (!markdown.includes(`Candidate revision:** \`${artifact.candidateRevision}\``)) throw new Error(`${artifact.id}: Markdown is not bound to candidateRevision`);
57
66
  if (/<script\b/i.test(html) || !html.includes(artifact.candidateRevision)) throw new Error(`${artifact.id}: HTML must be script-free and revision-bound`);
67
+ const expected = renderArtifact({ manifest, artifact, markdown });
68
+ if (html !== expected) throw new Error(`${artifact.id}: HTML is not the deterministic projection of its JSON, Markdown, and theme`);
58
69
  }
59
70
  console.log(`validated ${manifest.artifacts.length} Living Vision Artifact(s)`);
@@ -7,6 +7,7 @@ import test from "node:test";
7
7
 
8
8
  const repo = path.resolve(import.meta.dirname, "../..");
9
9
  const validator = path.join(repo, "cli/scripts/validate-living-vision-artifact.mjs");
10
+ const renderer = path.join(repo, "cli/scripts/render-living-vision-artifact.mjs");
10
11
  test("keeps the packaged validator schema identical to the canonical contract", () => {
11
12
  const canonical = JSON.parse(fs.readFileSync(path.join(repo, "contracts/project-brain/vision-artifacts.schema.json"), "utf8"));
12
13
  const packaged = JSON.parse(fs.readFileSync(path.join(repo, "cli/skills/templates/living-vision-artifact/vision-artifacts.schema.json"), "utf8"));
@@ -27,14 +28,18 @@ function makeRepo() {
27
28
  }
28
29
  function manifest(sha, status = "proposed") { return {
29
30
  $schema: "https://you.md/schemas/project-vision-artifacts/v1", schemaVersion: "you-md/project-vision-artifacts/v1", kind: "project-vision-artifacts", projectRef: "project:fixture",
31
+ themes: [{ id: "fixture-dark", name: "Fixture dark", mode: "dark", presentation: "editorial", positioningKey: "youmd", mark: { label: "Fixture", monogram: "FX" }, palette: { canvas: "#0D0D0D", surface: "#171513", text: "#F2EEE9", muted: "#A69F98", accent: "#D8753E", line: "#39332E", positive: "#64A879", attention: "#E09A63" }, typography: { display: "mono", body: "sans", density: "comfortable" } }],
30
32
  northStar: { statement: "North star", invariants: ["Invariant"], evidenceRefs: ["project-context/source.md"] },
31
33
  positioning: { youmd: "You", bamfAi: "AI", bamfOs: "OS" },
32
34
  horizons: Object.fromEntries(["year", "quarter", "month", "week"].map((key) => [key, { period: "2026", objective: "Objective", outcomes: ["Outcome"], focusRefs: ["project:fixture"], status: "planned", reviewAt: "2026-12-01T00:00:00.000Z" }])),
33
35
  lanes: [{ id: "vision", name: "Vision", kind: "product", ownerAgentRef: "agent:fixture", scopeRefs: ["project:fixture"], claimMode: "propose", status: "planned", dependsOn: [], reviewAt: "2026-12-01T00:00:00.000Z" }],
34
- artifacts: [{ id: "fixture-vision", title: "Fixture Vision", family: "vision", scopeRefs: ["project:fixture"], horizon: "week", version: 1, candidateRevision: `git:${sha}`, status, trustDomain: "owner-private", createdAt: "2026-08-13T00:00:00.000Z", reviewAt: "2026-08-20T00:00:00.000Z", formats: { markdown: "project-context/VISION.md", json: "project-context/VISION_ARTIFACTS.json", html: "project-context/VISION.html" }, lineage: { relation: "main", parentVersionId: null, main: true }, provenance: ["project-context/source.md"] }], updatedAt: "2026-08-13T00:00:00.000Z",
36
+ artifacts: [{ id: "fixture-vision", title: "Fixture Vision", family: "vision", scopeRefs: ["project:fixture"], horizon: "week", version: 1, candidateRevision: `git:${sha}`, themeRef: "fixture-dark", status, trustDomain: "owner-private", createdAt: "2026-08-13T00:00:00.000Z", reviewAt: "2026-08-20T00:00:00.000Z", formats: { markdown: "project-context/VISION.md", json: "project-context/VISION_ARTIFACTS.json", html: "project-context/VISION.html" }, lineage: { relation: "main", parentVersionId: null, main: true }, provenance: ["project-context/source.md"] }], updatedAt: "2026-08-13T00:00:00.000Z",
35
37
  }; }
36
- function writeValid(root, sha) { const value = manifest(sha); fs.writeFileSync(path.join(root, "project-context/source.md"), "source\n"); fs.writeFileSync(path.join(root, "project-context/VISION.md"), `# Vision\n\n**Candidate revision:** \`git:${sha}\`\n`); fs.writeFileSync(path.join(root, "project-context/VISION.html"), `<p>git:${sha}</p>`); fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); return value; }
38
+ function render(root) { return execFileSync("node", [renderer, "project-context/VISION_ARTIFACTS.json", "fixture-vision"], { cwd: root, encoding: "utf8" }); }
39
+ function writeValid(root, sha) { const value = manifest(sha); fs.writeFileSync(path.join(root, "project-context/source.md"), "source\n"); fs.writeFileSync(path.join(root, "project-context/VISION.md"), `# Vision\n\n**Candidate revision:** \`git:${sha}\`\n`); fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); render(root); return value; }
37
40
  function run(root) { try { return execFileSync("node", [validator, "project-context/VISION_ARTIFACTS.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); } catch (error) { throw new Error(String(error.stderr || error.message)); } }
38
41
  test("validates a nested project-context manifest from its repository root", () => { const { root, sha } = makeRepo(); try { writeValid(root, sha); assert.match(run(root), /validated 1/); } finally { fs.rmSync(root, { recursive: true, force: true }); } });
39
42
  test("uses the packaged schema in an arbitrary Git project", () => { const { root, sha } = makeRepo(); try { writeValid(root, sha); fs.rmSync(path.join(root, "contracts"), { recursive: true, force: true }); assert.match(run(root), /validated 1/); } finally { fs.rmSync(root, { recursive: true, force: true }); } });
40
43
  test("rejects traversal, missing projections, and unaccepted acceptance receipts", () => { const { root, sha } = makeRepo(); try { const value = writeValid(root, sha); value.artifacts[0].formats.markdown = "../escape.md"; fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); assert.throws(() => run(root), /invalid Vision Artifact JSON/); value.artifacts[0].formats.markdown = "project-context/missing.md"; fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); assert.throws(() => run(root), /missing artifact projection/); value.artifacts[0].formats.markdown = "project-context/VISION.md"; value.artifacts[0].lineage.acceptanceRef = "review:fake"; fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); assert.throws(() => run(root), /must not pre-populate acceptanceRef/); } finally { fs.rmSync(root, { recursive: true, force: true }); } });
44
+ test("rejects low-contrast, undeclared, and hand-edited theme projections", () => { const { root, sha } = makeRepo(); try { const value = writeValid(root, sha); value.themes[0].palette.text = "#111111"; fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); assert.throws(() => run(root), /text\/canvas contrast/); value.themes[0].palette.text = "#F2EEE9"; value.artifacts[0].themeRef = "missing-theme"; fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); assert.throws(() => run(root), /themeRef does not resolve/); value.artifacts[0].themeRef = "fixture-dark"; fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); render(root); fs.appendFileSync(path.join(root, "project-context/VISION.html"), "<!-- hand edit -->"); assert.throws(() => run(root), /not the deterministic projection/); } finally { fs.rmSync(root, { recursive: true, force: true }); } });
45
+ test("renders distinct governed project themes from the same canonical facts", () => { const { root, sha } = makeRepo(); try { const value = writeValid(root, sha); const dark = fs.readFileSync(path.join(root, "project-context/VISION.html"), "utf8"); value.themes.push({ id: "fixture-light", name: "Fixture light", mode: "light", presentation: "briefing", positioningKey: "bamfOs", mark: { label: "Fixture OS", monogram: "FO" }, palette: { canvas: "#F5F1EA", surface: "#FFFFFF", text: "#171513", muted: "#625B54", accent: "#7457D9", line: "#D9D1C8", positive: "#317A50", attention: "#A23F2D" }, typography: { display: "sans", body: "sans", density: "compact" } }); value.artifacts[0].themeRef = "fixture-light"; fs.writeFileSync(path.join(root, "project-context/VISION_ARTIFACTS.json"), JSON.stringify(value)); render(root); const light = fs.readFileSync(path.join(root, "project-context/VISION.html"), "utf8"); assert.notEqual(light, dark); assert.match(light, /data-theme="fixture-light"/); assert.match(light, /Operating brief/); assert.match(light, /OS/); assert.match(dark, /Living vision/); } finally { fs.rmSync(root, { recursive: true, force: true }); } });
@@ -20,10 +20,10 @@ Use this You.md-owned skill for a project vision, strategy, roadmap, audit, plan
20
20
 
21
21
  1. Read `project-context/VISION_ARTIFACTS.json`, the current accepted artifact, and cited evidence. Resolve the project slug from `.you-project` before writing.
22
22
  2. Capture the immutable candidate Git revision with `git rev-parse HEAD`, recorded exactly as `git:<sha>`. If the worktree is dirty, record that fact in the Markdown review note; do not claim the candidate is committed.
23
- 3. Copy the three files in `cli/skills/templates/living-vision-artifact/` using one shared stem, e.g. `PROJECT_STRATEGY_2026-08-13.{md,json,html}`. Fill every placeholder from evidence only.
24
- 4. In the JSON manifest, keep all four horizons and every lane explicit. Give the artifact `status: "proposed"`, `trustDomain`, paths for all three formats, exact provenance paths, a new monotonically increasing version, and lineage with the prior version id. `acceptanceRef` stays absent until human acceptance.
25
- 5. Render the branded HTML from the Markdown/JSON facts: include the artifact id, candidate Git revision, status, horizon, lane ids, provenance, and review date in the visible metadata. Block scripts and external embeds.
26
- 6. Validate with the repository script when present: `node cli/scripts/validate-living-vision-artifact.mjs project-context/VISION_ARTIFACTS.json`. In another project, use the installed runtime: `node ~/.you/npm-current/lib/node_modules/youmd/scripts/validate-living-vision-artifact.mjs project-context/VISION_ARTIFACTS.json`. It carries the contract schema and checks exact three-format paths, candidate revision bindings, no-script HTML, and acceptance discipline. Then verify every claimed Git ref resolves locally. In the You.md source repo, also run `npm run project-brain:contract-check`.
23
+ 3. Copy the Markdown and JSON templates using one shared stem, e.g. `PROJECT_STRATEGY_2026-08-13.{md,json,html}`. Fill every placeholder from evidence only; the HTML file is generated, never hand-edited.
24
+ 4. In the JSON manifest, keep all four horizons and every lane explicit. Declare a bounded project theme using palette, typography, presentation, and text-only mark tokens; point the artifact at it with `themeRef`. Give the artifact `status: "proposed"`, `trustDomain`, paths for all three formats, exact provenance paths, a new monotonically increasing version, and lineage with the prior version id. `acceptanceRef` stays absent until human acceptance. Theme tokens cannot contain CSS, scripts, external fonts, URLs, or arbitrary assets.
25
+ 5. Render the branded HTML deterministically: `node cli/scripts/render-living-vision-artifact.mjs project-context/VISION_ARTIFACTS.json <artifact-id>` in the source repo, or `node ~/.you/npm-current/lib/node_modules/youmd/scripts/render-living-vision-artifact.mjs ...` from the installed runtime. The renderer includes artifact status, candidate Git revision, horizons, scoped lanes, provenance, review date, and JSON/Markdown hashes in a script-free projection. Do not edit the result by hand.
26
+ 6. Validate with the repository script when present: `node cli/scripts/validate-living-vision-artifact.mjs project-context/VISION_ARTIFACTS.json`. In another project, use the installed runtime: `node ~/.you/npm-current/lib/node_modules/youmd/scripts/validate-living-vision-artifact.mjs project-context/VISION_ARTIFACTS.json`. It carries the contract schema and checks exact three-format paths, candidate revision bindings, theme contrast/ownership, byte-exact deterministic HTML, no scripts, and acceptance discipline. Then verify every claimed Git ref resolves locally. In the You.md source repo, also run `npm run project-brain:contract-check`.
27
27
  7. Present the candidate and exact Git revision for human review. Only after a human supplies an attributable receipt may a separate proposal change it to `accepted` and set `lineage.acceptanceRef` to an immutable receipt/ref.
28
28
 
29
29
  ## Required Markdown sections
@@ -33,3 +33,7 @@ Use: `Decision status`, `Candidate revision`, `North Star`, `Positioning`, `Hori
33
33
  ## Update rule
34
34
 
35
35
  Cosmetic HTML repairs may retain a version only if Markdown and JSON semantics and their candidate revision are unchanged. Any change to North Star, positioning, horizon outcome, lane scope/status, trust domain, or provenance requires a new proposed version.
36
+
37
+ ## Theme rule
38
+
39
+ A theme expresses project identity without becoming executable content. Use one declared `themeRef` per artifact. `editorial` is for narrative vision and audits; `briefing` is for operating plans and denser weekly reviews. A project may define multiple named themes, but agents must reuse an existing fitting theme before adding another. The validator requires WCAG-readable text, muted text, and accent contrast against the canvas. Icons, screenshots, and connected-service marks belong in evidence metadata, not arbitrary theme HTML.
@@ -3,6 +3,12 @@
3
3
  "schemaVersion": "you-md/project-vision-artifacts/v1",
4
4
  "kind": "project-vision-artifacts",
5
5
  "projectRef": "project:{{project_slug}}",
6
+ "themes": [{
7
+ "id": "{{theme_id}}", "name": "{{theme_name}}", "mode": "dark", "presentation": "editorial", "positioningKey": "youmd",
8
+ "mark": { "label": "{{project_label}}", "monogram": "{{project_monogram}}" },
9
+ "palette": { "canvas": "#0D0D0D", "surface": "#171513", "text": "#F2EEE9", "muted": "#A69F98", "accent": "#D8753E", "line": "#39332E", "positive": "#64A879", "attention": "#E09A63" },
10
+ "typography": { "display": "mono", "body": "sans", "density": "comfortable" }
11
+ }],
6
12
  "northStar": { "statement": "{{north_star}}", "invariants": ["{{invariant}}"], "evidenceRefs": ["{{evidence_path}}"] },
7
13
  "positioning": { "youmd": "{{youmd_positioning}}", "bamfAi": "{{bamf_ai_positioning}}", "bamfOs": "{{bamf_os_positioning}}" },
8
14
  "horizons": {
@@ -12,6 +18,6 @@
12
18
  "week": { "period": "{{week_period}}", "objective": "{{week_objective}}", "outcomes": ["{{week_outcome}}"], "focusRefs": ["{{focus_ref}}"], "status": "planned", "reviewAt": "{{week_review_at}}" }
13
19
  },
14
20
  "lanes": [{ "id": "{{lane_id}}", "name": "{{lane_name}}", "kind": "product", "ownerAgentRef": "agent:{{owner_agent}}", "scopeRefs": ["{{scope_ref}}"], "claimMode": "propose", "status": "planned", "dependsOn": [], "reviewAt": "{{lane_review_at}}" }],
15
- "artifacts": [{ "id": "{{artifact_id}}", "title": "{{title}}", "family": "vision", "scopeRefs": ["{{scope_ref}}"], "horizon": "week", "version": 1, "candidateRevision": "git:{{git_sha}}", "status": "proposed", "trustDomain": "owner-private", "createdAt": "{{created_at}}", "reviewAt": "{{review_at}}", "formats": { "markdown": "{{markdown_path}}", "json": "{{json_path}}", "html": "{{html_path}}" }, "lineage": { "relation": "main", "parentVersionId": null, "main": true }, "provenance": ["{{evidence_path}}"] }],
21
+ "artifacts": [{ "id": "{{artifact_id}}", "title": "{{title}}", "family": "vision", "scopeRefs": ["{{scope_ref}}"], "horizon": "week", "version": 1, "candidateRevision": "git:{{git_sha}}", "themeRef": "{{theme_id}}", "status": "proposed", "trustDomain": "owner-private", "createdAt": "{{created_at}}", "reviewAt": "{{review_at}}", "formats": { "markdown": "{{markdown_path}}", "json": "{{json_path}}", "html": "{{html_path}}" }, "lineage": { "relation": "main", "parentVersionId": null, "main": true }, "provenance": ["{{evidence_path}}"] }],
16
22
  "updatedAt": "{{created_at}}"
17
23
  }
@@ -10,6 +10,7 @@
10
10
  "schemaVersion": { "const": "you-md/project-vision-artifacts/v1" },
11
11
  "kind": { "const": "project-vision-artifacts" },
12
12
  "projectRef": { "$ref": "#/$defs/projectRef" },
13
+ "themes": { "type": "array", "maxItems": 32, "items": { "$ref": "#/$defs/theme" } },
13
14
  "northStar": { "type": "object", "additionalProperties": false, "required": ["statement", "invariants", "evidenceRefs"], "properties": {
14
15
  "statement": { "type": "string", "minLength": 1, "maxLength": 1200 },
15
16
  "invariants": { "type": "array", "minItems": 1, "maxItems": 32, "uniqueItems": true, "items": { "type": "string", "minLength": 1, "maxLength": 1200 } },
@@ -38,6 +39,7 @@
38
39
  "scopeRefs": { "type": "array", "minItems": 1, "maxItems": 64, "uniqueItems": true, "items": { "type": "string", "minLength": 1, "maxLength": 220 } },
39
40
  "horizon": { "enum": ["year", "quarter", "month", "week"] }, "version": { "type": "integer", "minimum": 1 },
40
41
  "candidateRevision": { "type": "string", "pattern": "^git:[a-f0-9]{7,64}$" },
42
+ "themeRef": { "$ref": "#/$defs/id" },
41
43
  "status": { "enum": ["draft", "proposed", "accepted", "superseded"] }, "trustDomain": { "enum": ["owner-private", "project-private", "shared", "public"] },
42
44
  "createdAt": { "type": "string", "format": "date-time" }, "reviewAt": { "type": "string", "format": "date-time" },
43
45
  "formats": { "type": "object", "additionalProperties": false, "minProperties": 1, "properties": {
@@ -54,6 +56,13 @@
54
56
  "$defs": {
55
57
  "projectRef": { "type": "string", "pattern": "^project:[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$" },
56
58
  "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,127}$" },
59
+ "hexColor": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" },
60
+ "theme": { "type": "object", "additionalProperties": false, "required": ["id", "name", "mode", "presentation", "positioningKey", "mark", "palette", "typography"], "properties": {
61
+ "id": { "$ref": "#/$defs/id" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "mode": { "enum": ["light", "dark"] }, "presentation": { "enum": ["editorial", "briefing"] }, "positioningKey": { "enum": ["youmd", "bamfAi", "bamfOs"] },
62
+ "mark": { "type": "object", "additionalProperties": false, "required": ["label", "monogram"], "properties": { "label": { "type": "string", "minLength": 1, "maxLength": 40 }, "monogram": { "type": "string", "minLength": 1, "maxLength": 4, "pattern": "^[A-Za-z0-9.]+$" } } },
63
+ "palette": { "type": "object", "additionalProperties": false, "required": ["canvas", "surface", "text", "muted", "accent", "line", "positive", "attention"], "properties": { "canvas": { "$ref": "#/$defs/hexColor" }, "surface": { "$ref": "#/$defs/hexColor" }, "text": { "$ref": "#/$defs/hexColor" }, "muted": { "$ref": "#/$defs/hexColor" }, "accent": { "$ref": "#/$defs/hexColor" }, "line": { "$ref": "#/$defs/hexColor" }, "positive": { "$ref": "#/$defs/hexColor" }, "attention": { "$ref": "#/$defs/hexColor" } } },
64
+ "typography": { "type": "object", "additionalProperties": false, "required": ["display", "body", "density"], "properties": { "display": { "enum": ["mono", "sans", "serif"] }, "body": { "enum": ["sans", "serif"] }, "density": { "enum": ["compact", "comfortable"] } } }
65
+ } },
57
66
  "relativePath": { "type": "string", "minLength": 1, "maxLength": 500, "pattern": "^(?!/)(?![A-Za-z]:)(?!.*\\\\)(?!.*//)(?!.*(?:^|/)(?:\\.{1,2})(?:/|$))(?!.*[\\u0000-\\u001f\\u007f])(?!.*\\/$)[^/]+(?:/[^/]+)*$" },
58
67
  "horizon": { "type": "object", "additionalProperties": false, "required": ["period", "objective", "outcomes", "focusRefs", "status", "reviewAt"], "properties": {
59
68
  "period": { "type": "string", "minLength": 1, "maxLength": 40 }, "objective": { "type": "string", "minLength": 1, "maxLength": 1200 },
@@ -1 +0,0 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{title}}</title><style>body{margin:0;background:#0d0d0d;color:#e8e4df;font:16px/1.6 Inter,system-ui,sans-serif}main{max-width:900px;margin:auto;padding:64px 28px}code,.meta,h1,h2{font-family:"JetBrains Mono",monospace}h1{font-size:clamp(2.4rem,7vw,5rem);line-height:1.05}.meta{color:#c46a3a;border-block:1px solid #332d28;padding:14px 0}h2{margin-top:3rem}a{color:#c46a3a}</style></head><body><main><p class="meta">{{artifact_id}} · {{status}} · candidate git:{{git_sha}} · {{horizon}} · review {{review_at}}</p><h1>{{title}}</h1><section><h2>North Star</h2><p>{{north_star}}</p></section><section><h2>Lanes</h2><p>{{lane_summary}}</p></section><section><h2>Evidence</h2><p>{{provenance_summary}}</p></section><section><h2>Human review</h2><p>{{review_request}}</p></section></main></body></html>