diff --git a/.agents/skills/README.md b/.agents/skills/README.md
index 708296d..bcddfcc 100644
--- a/.agents/skills/README.md
+++ b/.agents/skills/README.md
@@ -12,7 +12,7 @@ Installed skills:
- `meta-module-build/` — metadata-first module scaffolding
- `new-retain-old/` — safe replacement workflow that preserves the old implementation while creating a new active path
- `manifest/` — living-spec generator for `CLAUDE.md` (vendored from
- `The-Interdependency/skill-lib@d6e4d78`). Generates the mechanical facts
+ `The-Interdependency/skill-lib@d0f6209`). Generates the mechanical facts
block in `CLAUDE.md` from `backend/pyproject.toml` + the tree; the
`manifest drift check` workflow runs `generate.py --check` in CI. Refresh with
`python .agents/skills/manifest/generate.py --pyproject backend/pyproject.toml --write`.
diff --git a/.agents/skills/manifest/SKILL.md b/.agents/skills/manifest/SKILL.md
index abb3d5b..4c11611 100644
--- a/.agents/skills/manifest/SKILL.md
+++ b/.agents/skills/manifest/SKILL.md
@@ -65,6 +65,21 @@ Flags: `--root
` (default `.`), `--file ` (default `CLAUDE.md`),
`--pyproject ` (default `pyproject.toml`), and exactly one of
`--write` / `--check` / `--print`.
+## Field requirements
+
+The required generated fields are the observable repo facts the runner can
+derive: package metadata, runtime dependencies, optional extras, top-level
+layout, and CI workflow names. Judgement, rationale, test-command guesses, and
+doctrine stay hand-authored outside the generated block. Unknown observable
+facts render as `hmmm`.
+
+## Runner contract
+
+A compliant manifest runner is stdlib-only, deterministic, idempotent, and
+non-destructive. It reads `pyproject.toml` plus the file tree, rewrites only the
+bytes between the manifest markers, supports `--write`, `--check`, and
+`--print`, and exits non-zero when `--check` detects drift.
+
## Wiring a repo (the propagation recipe)
1. Vendor `generate.py` to `.agents/skills/manifest/generate.py` (verbatim copy
@@ -109,3 +124,11 @@ Flags: `--root ` (default `.`), `--file ` (default `CLAUDE.md`),
- **Additive scope.** Start with the high-signal/low-noise fields above. New
derived fields are an extension here (bump the block, keep markers stable), not
a per-repo fork — portability depends on one generator.
+
+## Anti-patterns
+
+- Hand-editing bytes inside the generated manifest markers.
+- Emitting fuzzy or judgement-shaped facts as if they were mechanically derived.
+- Forking the vendored generator in a consuming repo instead of changing this
+ canonical source and re-vendoring.
+- Running `--write` in CI when the intended gate is `--check`.
diff --git a/.agents/skills/manifest/generate.py b/.agents/skills/manifest/generate.py
index eb0a494..f0a4630 100755
--- a/.agents/skills/manifest/generate.py
+++ b/.agents/skills/manifest/generate.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+# ratios: loc_comments=209:37 imports_exports=6:5 calls_definitions=81:16
"""manifest — generate the mechanical half of a repo's CLAUDE.md from source.
Living-spec tool (msdmd family). It derives *observable* facts about a repo —
@@ -300,3 +300,4 @@ def main(argv: list[str] | None = None) -> int:
if __name__ == "__main__":
raise SystemExit(main())
+# ratios: loc_comments=209:37 imports_exports=6:5 calls_definitions=81:16
diff --git a/.agents/skills/manifest/generate.py.sha256 b/.agents/skills/manifest/generate.py.sha256
index 8bfb34a..276a3ad 100644
--- a/.agents/skills/manifest/generate.py.sha256
+++ b/.agents/skills/manifest/generate.py.sha256
@@ -1 +1 @@
-a7100dd7895270498a1562aa9f4efc485e05b14e25c9fe0421a5bf3cb3b77408 generate.py
+06cec293ebd7b9a04d84fbfcd4cee55ebb058b6f265e758b6acadd0efd86595d generate.py
diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md
index 9448aa3..0af6b20 100644
--- a/.agents/skills/msdmd/SKILL.md
+++ b/.agents/skills/msdmd/SKILL.md
@@ -1,6 +1,6 @@
---
name: msdmd
-description: Module Self-Declared Metadata in Markdown — the foundational convention where each source module declares its own structured metadata in a fenced comment block. Other skills in this lib (test-build, doc-build, cap-build, etc.) are thin applications on top of this convention. Load this when authoring a new metadata-driven skill, when extending the block schema, or when building a parser/executor for a new application.
+description: Module Self-Declared Metadata in Markdown — the foundational convention where each source module declares its own structured metadata in a fenced comment block. Other skills in this lib (doc-build, cap-build, deps-build, owner-build, test-build, meta-module-build, risk-boundary-build, ratios, etc.) are thin applications on top of this convention. Load this when authoring a new metadata-driven skill, when extending the block schema, or when building a parser/executor for a new application.
---
# msdmd — Module Self-Declared Metadata in Markdown
@@ -119,6 +119,71 @@ A reference implementation in pure stdlib Python lives at
Both commit to zero non-stdlib dependencies so you can copy them into
any project.
+## Repo collection point and visualizer
+
+Every consuming repo SHOULD maintain one repo-level collection point named
+`_msdmd.ts` (for example, `a0_msdmd.ts`). This file is the
+canonical aggregation surface for all parsed msdmd declarations in that
+repo. It does not replace module-local blocks; it is generated from them
+or maintained as a thin index over them.
+
+The collection point SHOULD use the shared shapes in `msdmd/collection.ts`
+(or a verbatim copy in consuming repos) and export a `MsdmdCollection`:
+
+```typescript
+import { defineMsdmdCollection } from "./.agents/skills/msdmd/collection";
+
+export default defineMsdmdCollection({
+ repo: "",
+ declarations: [
+ { file: "path/to/module.py", block: "CONTRACTS", id: "...", fields: { summary: "..." } },
+ ],
+ gaps: [
+ { file: "path/to/module.py", missing: ["CONTRACTS", "DOCS"] },
+ ],
+ edges: [
+ { from: "module_a", to: "module_b", kind: "requires", source_block: "DEPENDENCIES", source_id: "..." },
+ ],
+});
+
+export const declarations = [];
+export const gaps = [];
+```
+
+A repo-level msdmd visualizer SHOULD read `_msdmd.ts` and render
+relationships between modules using the `MsdmdEdge` shape: `DEPENDENCIES.requires`,
+`CAPABILITIES.exposes`, `OWNERS.owner`, `BOUNDARIES` risk fields, `DOCS.covers`,
+`CONTRACTS.call`, and any `requires` edges shared across application skills.
+The visualizer is a consumer of the collection point, not a second metadata
+source.
+
+If a repo has no collection point or visualizer yet, record that as `hmmm` in
+repo-local planning rather than pretending the graph exists.
+
+A small stdlib generator prototype lives at `msdmd/collect.py`. Consuming repos
+can run it directly or copy it as a starting point:
+
+```bash
+python -m msdmd.collect --root . --repo --out _msdmd.ts
+```
+
+The generator is intentionally conservative: it parses module-local blocks,
+emits declarations, optional expected-block gaps, and simple relationship
+edges from reserved fields. Repo-specific runners may enrich the output, but
+should preserve the `MsdmdCollection` shape.
+
+A minimal Mermaid visualizer prototype lives at `msdmd/visualize.py` and reads
+raw JSON or generated TypeScript collection points:
+
+```bash
+python -m msdmd.visualize _msdmd.ts --out _msdmd.mmd
+```
+
+The visualizer is deliberately small: it renders declaration nodes, normalized
+edge relationships, and visible gap nodes. Rich repo-specific UIs should consume
+the same collection shape rather than re-parsing source files.
+
+
## The runner protocol
A msdmd runner combines a parser and an executor:
@@ -183,7 +248,10 @@ their own SKILL.md.
executor's behavior, and at least one worked example.
`test-build/` is the canonical reference application. Read its
-SKILL.md alongside this one to see the pattern fully realized.
+SKILL.md alongside this one to see the pattern fully realized; read
+`doc-build/`, `cap-build/`, `deps-build/`, `owner-build/`,
+`risk-boundary-build/`, and `ratios/` for additional applications over
+the same parser contract.
## Anti-patterns
diff --git a/.agents/skills/msdmd/collect.py b/.agents/skills/msdmd/collect.py
new file mode 100644
index 0000000..5e34235
--- /dev/null
+++ b/.agents/skills/msdmd/collect.py
@@ -0,0 +1,175 @@
+# ratios: loc_comments=141:7 imports_exports=6:3 calls_definitions=35:6
+"""Generate repo-level msdmd collection-point TypeScript.
+
+This is a small stdlib helper for consuming repos that want to generate a
+`_msdmd.ts` aggregation file from module-local msdmd blocks.
+It uses the universal parser and emits data shaped by `msdmd/collection.ts`.
+"""
+from __future__ import annotations
+
+import argparse
+import json
+from pathlib import Path
+from typing import Iterable
+
+from msdmd.parsers.universal import walk_tree
+
+DEFAULT_BLOCK_NAMES = (
+ "DOCS",
+ "CAPABILITIES",
+ "DEPENDENCIES",
+ "OWNERS",
+ "CONTRACTS",
+ "MODULE_BUILD",
+ "BOUNDARIES",
+ "RATIOS",
+ "LLMS",
+ "FRONTEND_META",
+)
+
+EDGE_FIELDS = {
+ "requires": "requires",
+ "exposes": "exposes",
+ "owner": "owns",
+ "covers": "covers",
+ "call": "calls",
+ "boundaries": "risk",
+}
+
+
+def _split_targets(value: str) -> list[str]:
+ return [part.strip() for part in value.split(",") if part.strip()]
+
+
+def _declaration(file: Path, root: Path, block: str, entry: dict) -> dict:
+ fields = {str(key): str(value) for key, value in entry.items() if key != "id"}
+ return {
+ "file": file.relative_to(root).as_posix(),
+ "block": block,
+ "id": str(entry["id"]),
+ "fields": fields,
+ }
+
+
+def _edges_for(declaration: dict) -> list[dict]:
+ edges: list[dict] = []
+ fields = declaration["fields"]
+ source = declaration["id"]
+ for field, kind in EDGE_FIELDS.items():
+ value = fields.get(field)
+ if not value or value == "hmmm":
+ continue
+ for target in _split_targets(value):
+ edges.append(
+ {
+ "from": source,
+ "to": target,
+ "kind": kind,
+ "source_block": declaration["block"],
+ "source_id": source,
+ }
+ )
+ return edges
+
+
+def collect(
+ root: Path,
+ repo: str,
+ *,
+ block_names: Iterable[str] = DEFAULT_BLOCK_NAMES,
+ expected_blocks: Iterable[str] = (),
+ source_commit: str | None = None,
+) -> dict:
+ """Collect msdmd declarations and optional coverage gaps under ``root``."""
+ root = root.resolve()
+ block_names = tuple(block_names)
+ expected_blocks = tuple(expected_blocks)
+
+ declarations: list[dict] = []
+ missing_by_file: dict[str, set[str]] = {}
+
+ for block in block_names:
+ annotated, _ = walk_tree(root, block)
+ for file, entries in annotated:
+ for entry in entries:
+ if "id" not in entry:
+ continue
+ declarations.append(_declaration(file.resolve(), root, block, entry))
+
+ for block in expected_blocks:
+ _, missing_files = walk_tree(root, block)
+ for file in missing_files:
+ relative = file.resolve().relative_to(root).as_posix()
+ missing_by_file.setdefault(relative, set()).add(block)
+
+ declarations.sort(key=lambda item: (item["file"], item["block"], item["id"]))
+ gaps = [
+ {"file": file, "missing": sorted(missing)}
+ for file, missing in sorted(missing_by_file.items())
+ ]
+ edges = [edge for declaration in declarations for edge in _edges_for(declaration)]
+ edges.sort(key=lambda item: (item["source_block"], item["source_id"], item["kind"], item["to"]))
+
+ collection = {
+ "repo": repo,
+ "declarations": declarations,
+ "gaps": gaps,
+ "edges": edges,
+ }
+ if source_commit:
+ collection["source_commit"] = source_commit
+ return collection
+
+
+def render_typescript(collection: dict, *, import_path: str) -> str:
+ """Render a collection as a `_msdmd.ts` module."""
+ payload = json.dumps(collection, indent=2, sort_keys=True)
+ return (
+ f'import {{ defineMsdmdCollection }} from "{import_path}";\n\n'
+ f"export default defineMsdmdCollection({payload});\n"
+ )
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--root", type=Path, default=Path("."), help="repo root to scan")
+ parser.add_argument("--repo", required=True, help="repository slug for the collection")
+ parser.add_argument("--out", type=Path, help="output .ts path; stdout when omitted")
+ parser.add_argument(
+ "--block",
+ action="append",
+ dest="blocks",
+ help="block name to collect; may be repeated; defaults to all known blocks",
+ )
+ parser.add_argument(
+ "--expected-block",
+ action="append",
+ default=[],
+ help="block expected on every source file for gap reporting; may be repeated",
+ )
+ parser.add_argument(
+ "--import-path",
+ default="./.agents/skills/msdmd/collection",
+ help="TypeScript import path for defineMsdmdCollection",
+ )
+ parser.add_argument("--source-commit", help="source commit SHA to record")
+ args = parser.parse_args()
+
+ collection = collect(
+ args.root,
+ args.repo,
+ block_names=args.blocks or DEFAULT_BLOCK_NAMES,
+ expected_blocks=args.expected_block,
+ source_commit=args.source_commit,
+ )
+ rendered = render_typescript(collection, import_path=args.import_path)
+ if args.out:
+ args.out.write_text(rendered, encoding="utf-8")
+ else:
+ print(rendered, end="")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
+# ratios: loc_comments=141:7 imports_exports=6:3 calls_definitions=35:6
diff --git a/.agents/skills/msdmd/collection.ts b/.agents/skills/msdmd/collection.ts
new file mode 100644
index 0000000..e86a6cf
--- /dev/null
+++ b/.agents/skills/msdmd/collection.ts
@@ -0,0 +1,74 @@
+// ratios: loc_comments=66:0 imports_exports=0:0 calls_definitions=1:0
+/**
+ * Shared TypeScript shapes for repo-level msdmd collection points.
+ *
+ * A consuming repo's `_msdmd.ts` file may import or copy these
+ * types, then export a `MsdmdCollection` generated from module-local msdmd
+ * blocks. This file is type-only: it does not parse source files or validate
+ * declarations.
+ */
+export type MsdmdBlockName =
+ | "DOCS"
+ | "CAPABILITIES"
+ | "DEPENDENCIES"
+ | "OWNERS"
+ | "CONTRACTS"
+ | "MODULE_BUILD"
+ | "BOUNDARIES"
+ | "RATIOS"
+ | "LLMS"
+ | "FRONTEND_META";
+
+export type MsdmdFieldMap = Record;
+
+export interface MsdmdDeclaration {
+ /** Repository-relative source file that owns the declaration. */
+ file: string;
+ /** msdmd application block name, such as CONTRACTS or DOCS. */
+ block: MsdmdBlockName;
+ /** Stable entry id declared inside the block. */
+ id: string;
+ /** Flat parsed fields, excluding id unless a generator intentionally repeats it. */
+ fields: MsdmdFieldMap;
+}
+
+export interface MsdmdGap {
+ /** Repository-relative source file with missing expected block coverage. */
+ file: string;
+ /** Block types expected by local policy but absent from this file. */
+ missing: MsdmdBlockName[];
+ /** Optional explanation from the collector or policy layer. */
+ reason?: string;
+}
+
+export interface MsdmdEdge {
+ /** Source declaration id or file path. */
+ from: string;
+ /** Target declaration id, capability id, owner, route, file, or external system. */
+ to: string;
+ /** Relationship kind: requires, exposes, owns, covers, calls, risk, etc. */
+ kind: string;
+ /** Block that produced this edge. */
+ source_block: MsdmdBlockName;
+ /** Entry id that produced this edge. */
+ source_id: string;
+}
+
+export interface MsdmdCollection {
+ /** Repository slug, for example a0 or skill-lib. */
+ repo: string;
+ /** Parsed module-local msdmd entries. */
+ declarations: MsdmdDeclaration[];
+ /** Visible coverage gaps emitted by collectors or local policy. */
+ gaps: MsdmdGap[];
+ /** Optional normalized relationship graph for visualizers. */
+ edges?: MsdmdEdge[];
+ /** Optional collector metadata. */
+ generated_at?: string;
+ source_commit?: string;
+}
+
+export function defineMsdmdCollection(collection: MsdmdCollection): MsdmdCollection {
+ return collection;
+}
+// ratios: loc_comments=66:0 imports_exports=0:0 calls_definitions=1:0
diff --git a/.agents/skills/msdmd/parsers/__init__.py b/.agents/skills/msdmd/parsers/__init__.py
index 9e9a557..13f9bf0 100644
--- a/.agents/skills/msdmd/parsers/__init__.py
+++ b/.agents/skills/msdmd/parsers/__init__.py
@@ -1,7 +1,7 @@
-# 0:4 0:0 0:0
+# ratios: loc_comments=0:4 imports_exports=0:0 calls_definitions=0:0
"""msdmd reference parsers.
`universal` is the canonical Python implementation of the parser
contract defined in ``msdmd/SKILL.md``. Pure stdlib; copy anywhere.
"""
-# 0:4 0:0 0:0
+# ratios: loc_comments=0:4 imports_exports=0:0 calls_definitions=0:0
diff --git a/.agents/skills/msdmd/parsers/universal.py b/.agents/skills/msdmd/parsers/universal.py
index 5e6268f..7bc86bf 100644
--- a/.agents/skills/msdmd/parsers/universal.py
+++ b/.agents/skills/msdmd/parsers/universal.py
@@ -1,4 +1,4 @@
-# 85:29 0:0 0:0
+# ratios: loc_comments=128:49 imports_exports=4:7 calls_definitions=51:10
"""Universal msdmd parser — pure stdlib.
Implements the parser contract from ``msdmd/SKILL.md``: extracts every
@@ -14,6 +14,14 @@
parse_file(path, block_name) -> list[dict]
walk_tree(root, block_name, *, skip=None, extensions=None) -> tuple[annotated, untested]
+RATIOS is the one msdmd declaration that is *not* a fenced block — it is a
+single comment line carried on a file's first and last non-blank lines. The
+reader for it lives here too, as a sanctioned extension rather than a fork:
+
+ parse_ratios(text, marker="#") -> list[dict]
+ parse_ratios_file(path) -> list[dict]
+ ratios_placement(text, marker="#") -> tuple[first_ok, last_ok]
+
This module has zero non-stdlib dependencies and is safe to copy
verbatim into any project that wants msdmd support.
"""
@@ -143,4 +151,64 @@ def iter_source_files(path: Path) -> Iterable[Path]:
else:
untested.append(path)
return annotated, untested
-# 85:29 0:0 0:0
+
+
+# --- RATIOS single-line declaration (msdmd extension) --------------------
+# Unlike every other declaration, RATIOS is not fenced. It is a single
+# comment line carrying the three canonical ratios, placed on the file's
+# first and last non-blank lines:
+# ratios: loc_comments=N:M imports_exports=N:M calls_definitions=N:M
+RATIO_IDS = ("loc_comments", "imports_exports", "calls_definitions")
+_RATIOS_TOKEN_RE = re.compile(r"(?P[a-z_]+)=(?P\S+)")
+
+
+def _ratios_line_re(marker: str) -> re.Pattern[str]:
+ return re.compile(rf"^{re.escape(marker)}\s*ratios:\s*(?P.+?)\s*$")
+
+
+def parse_ratios(text: str, marker: str = "#") -> list[dict]:
+ """Read single-line RATIOS declarations from ``text``.
+
+ RATIOS is not a fenced block: it is one comment line of the form
+ `` ratios: loc_comments=N:M imports_exports=N:M calls_definitions=N:M``
+ placed on the file's first and last non-blank lines. Returns one flat
+ ``{"id", "value"}`` dict per (declaration line x ratio token) so a drift
+ gate can verify every occurrence.
+ """
+ line_re = _ratios_line_re(marker)
+ out: list[dict] = []
+ for raw in text.splitlines():
+ lm = line_re.match(raw.rstrip())
+ if not lm:
+ continue
+ for tm in _RATIOS_TOKEN_RE.finditer(lm.group("body")):
+ out.append({"id": tm.group("key"), "value": tm.group("val")})
+ return out
+
+
+def parse_ratios_file(path: Path) -> list[dict]:
+ """``parse_ratios`` for a file path (marker auto-detected); [] on error."""
+ marker = marker_for(path)
+ if marker is None:
+ return []
+ try:
+ return parse_ratios(path.read_text(encoding="utf-8"), marker)
+ except (OSError, UnicodeDecodeError):
+ return []
+
+
+def ratios_placement(text: str, marker: str = "#") -> tuple[bool, bool]:
+ """Return ``(first_line_has_ratios, last_non_blank_line_has_ratios)``."""
+ line_re = _ratios_line_re(marker)
+ lines = text.splitlines()
+ if not lines:
+ return (False, False)
+ first_ok = bool(line_re.match(lines[0].rstrip()))
+ last_ok = False
+ for raw in reversed(lines):
+ if raw.strip() == "":
+ continue
+ last_ok = bool(line_re.match(raw.rstrip()))
+ break
+ return (first_ok, last_ok)
+# ratios: loc_comments=128:49 imports_exports=4:7 calls_definitions=51:10
diff --git a/.agents/skills/msdmd/parsers/universal.ts b/.agents/skills/msdmd/parsers/universal.ts
index d889169..2db076f 100644
--- a/.agents/skills/msdmd/parsers/universal.ts
+++ b/.agents/skills/msdmd/parsers/universal.ts
@@ -1,4 +1,4 @@
-// 110:15 0:6 0:0
+// ratios: loc_comments=176:0 imports_exports=2:0 calls_definitions=53:0
/**
* Universal msdmd parser — pure Node stdlib (fs, path).
*
@@ -11,6 +11,11 @@
* itself is identical across languages; only the per-line marker
* changes.
*
+ * RATIOS is the one msdmd declaration that is not a fenced block — it is a
+ * single comment line on a file's first and last non-blank lines. Its reader
+ * (parseRatios / parseRatiosFile / ratiosPlacement) lives here too, as a
+ * sanctioned extension rather than a fork.
+ *
* Zero non-stdlib dependencies. Safe to copy verbatim into any
* Node/Deno/Bun project that wants msdmd support.
*/
@@ -137,4 +142,55 @@ export function walkTree(
visit(root);
return { annotated, untested };
}
-// 110:15 0:6 0:0
+
+// --- RATIOS single-line declaration (msdmd extension) --------------------
+// Unlike every other declaration, RATIOS is not fenced. It is a single
+// comment line carrying the three canonical ratios, placed on the file's
+// first and last non-blank lines:
+// ratios: loc_comments=N:M imports_exports=N:M calls_definitions=N:M
+export const RATIO_IDS = ["loc_comments", "imports_exports", "calls_definitions"] as const;
+
+function ratiosLineRe(marker: string): RegExp {
+ return new RegExp(`^${escapeRegex(marker)}\\s*ratios:\\s*(.+?)\\s*$`);
+}
+
+export function parseRatios(text: string, marker: string = "#"): Entry[] {
+ const lineRe = ratiosLineRe(marker);
+ const tokenRe = /([a-z_]+)=(\S+)/g;
+ const out: Entry[] = [];
+ for (const raw of text.split("\n")) {
+ const lm = lineRe.exec(raw.replace(/\s+$/, ""));
+ if (!lm) continue;
+ let tm: RegExpExecArray | null;
+ tokenRe.lastIndex = 0;
+ while ((tm = tokenRe.exec(lm[1])) !== null) {
+ out.push({ id: tm[1], value: tm[2] });
+ }
+ }
+ return out;
+}
+
+export function parseRatiosFile(path: string): Entry[] {
+ const marker = markerFor(path);
+ if (marker === null) return [];
+ try {
+ return parseRatios(readFileSync(path, "utf-8"), marker);
+ } catch {
+ return [];
+ }
+}
+
+export function ratiosPlacement(text: string, marker: string = "#"): [boolean, boolean] {
+ const lineRe = ratiosLineRe(marker);
+ const lines = text.split("\n");
+ if (lines.length === 0) return [false, false];
+ const firstOk = lineRe.test(lines[0].replace(/\s+$/, ""));
+ let lastOk = false;
+ for (let i = lines.length - 1; i >= 0; i--) {
+ if (lines[i].trim() === "") continue;
+ lastOk = lineRe.test(lines[i].replace(/\s+$/, ""));
+ break;
+ }
+ return [firstOk, lastOk];
+}
+// ratios: loc_comments=176:0 imports_exports=2:0 calls_definitions=53:0
diff --git a/.agents/skills/msdmd/visualize.py b/.agents/skills/msdmd/visualize.py
new file mode 100644
index 0000000..9dfa9d6
--- /dev/null
+++ b/.agents/skills/msdmd/visualize.py
@@ -0,0 +1,94 @@
+# ratios: loc_comments=63:8 imports_exports=5:3 calls_definitions=40:5
+"""Render an msdmd collection as a small Mermaid relationship graph.
+
+The input may be raw JSON or the generated TypeScript shape emitted by
+``msdmd.collect.render_typescript``. This helper is intentionally minimal:
+it visualizes the normalized ``edges`` array from a ``MsdmdCollection`` and
+adds gap nodes for visible coverage gaps.
+"""
+from __future__ import annotations
+
+import argparse
+import json
+import re
+from pathlib import Path
+
+_TS_COLLECTION_RE = re.compile(r"defineMsdmdCollection\((?P.*)\);\s*$", re.DOTALL)
+_SAFE_NODE_RE = re.compile(r"[^A-Za-z0-9_]")
+
+
+def load_collection(path: Path) -> dict:
+ """Load a collection from JSON or generated TypeScript."""
+ text = path.read_text(encoding="utf-8")
+ stripped = text.strip()
+ if stripped.startswith("{"):
+ return json.loads(stripped)
+
+ match = _TS_COLLECTION_RE.search(stripped)
+ if not match:
+ raise ValueError(f"{path} is not JSON or generated defineMsdmdCollection TypeScript")
+ return json.loads(match.group("payload"))
+
+
+def _node_id(value: str) -> str:
+ normalized = _SAFE_NODE_RE.sub("_", value).strip("_")
+ return normalized or "hmmm"
+
+
+def _label(value: str) -> str:
+ return value.replace('"', "'")
+
+
+def render_mermaid(collection: dict) -> str:
+ """Render ``collection`` as Mermaid flowchart text."""
+ lines = ["flowchart TD"]
+ repo = collection.get("repo", "repo")
+ lines.append(f' repo["{_label(str(repo))}"]')
+
+ emitted_nodes = {"repo"}
+ for declaration in collection.get("declarations", []):
+ node = _node_id(str(declaration["id"]))
+ label = f'{declaration["id"]}\\n{declaration["block"]}\\n{declaration["file"]}'
+ if node not in emitted_nodes:
+ lines.append(f' {node}["{_label(label)}"]')
+ lines.append(f" repo --> {node}")
+ emitted_nodes.add(node)
+
+ for edge in collection.get("edges", []):
+ source = _node_id(str(edge["from"]))
+ target = _node_id(str(edge["to"]))
+ if source not in emitted_nodes:
+ lines.append(f' {source}["{_label(str(edge["from"]))}"]')
+ emitted_nodes.add(source)
+ if target not in emitted_nodes:
+ lines.append(f' {target}["{_label(str(edge["to"]))}"]')
+ emitted_nodes.add(target)
+ lines.append(f' {source} -- "{_label(str(edge["kind"]))}" --> {target}')
+
+ for index, gap in enumerate(collection.get("gaps", []), start=1):
+ node = f"gap_{index}"
+ missing = ", ".join(gap.get("missing", []))
+ label = f'{gap.get("file", "hmmm")}\\nmissing: {missing or "hmmm"}'
+ lines.append(f' {node}[["{_label(label)}"]]')
+ lines.append(f" repo -. gap .-> {node}")
+
+ return "\n".join(lines) + "\n"
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("collection", type=Path, help="collection .json or generated .ts file")
+ parser.add_argument("--out", type=Path, help="output .mmd path; stdout when omitted")
+ args = parser.parse_args()
+
+ rendered = render_mermaid(load_collection(args.collection))
+ if args.out:
+ args.out.write_text(rendered, encoding="utf-8")
+ else:
+ print(rendered, end="")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
+# ratios: loc_comments=63:8 imports_exports=5:3 calls_definitions=40:5
diff --git a/.agents/skills/test-build/SKILL.md b/.agents/skills/test-build/SKILL.md
index b782621..f3d74e2 100644
--- a/.agents/skills/test-build/SKILL.md
+++ b/.agents/skills/test-build/SKILL.md
@@ -149,3 +149,7 @@ Field schema additions are non-breaking and don't bump the version.
Field renames or removals are breaking; bump the major version and
note the migration in the lib README. The `CONTRACTS` block name
itself is stable — never reuse it for a different purpose.
+
+hmmm
+- Whether a future test-build runner should standardize quarantine/flaky/slow contract states or leave them to consuming repos.
+- The exact boundary between an `ERROR` caused by infrastructure and a `FAIL` caused by violated behavior can get swampy; bring boots.