Skip to content
This repository was archived by the owner on Jul 29, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
23 changes: 23 additions & 0 deletions .agents/skills/manifest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ Flags: `--root <dir>` (default `.`), `--file <doc>` (default `CLAUDE.md`),
`--pyproject <path>` (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
Expand Down Expand Up @@ -109,3 +124,11 @@ Flags: `--root <dir>` (default `.`), `--file <doc>` (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`.
3 changes: 2 additions & 1 deletion .agents/skills/manifest/generate.py
Original file line number Diff line number Diff line change
@@ -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 —
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion .agents/skills/manifest/generate.py.sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
a7100dd7895270498a1562aa9f4efc485e05b14e25c9fe0421a5bf3cb3b77408 generate.py
06cec293ebd7b9a04d84fbfcd4cee55ebb058b6f265e758b6acadd0efd86595d generate.py
72 changes: 70 additions & 2 deletions .agents/skills/msdmd/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
`<reponame>_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: "<reponame>",
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 `<reponame>_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 <reponame> --out <reponame>_msdmd.ts

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the collection command runnable from repo root

In a consuming repo with this skill vendored under .agents/skills, running this documented command from the repo root fails because msdmd is not on Python's module search path; if the user instead changes into .agents/skills to make python -m work, --root . scans the skills directory rather than the repo. The command needs to set PYTHONPATH=.agents/skills or use another invocation that works from the intended root.

Useful? React with 👍 / 👎.

```

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 <reponame>_msdmd.ts --out <reponame>_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:
Expand Down Expand Up @@ -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

Expand Down
175 changes: 175 additions & 0 deletions .agents/skills/msdmd/collect.py
Original file line number Diff line number Diff line change
@@ -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
`<reponame>_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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Map actual BOUNDARIES fields to risk edges

For BOUNDARIES blocks that declare the documented risk fields such as auth_boundary, storage_boundary, network_boundary, and user_data_boundary, _edges_for() never emits a risk edge because it only checks for a literal field named boundaries. Collections generated from the existing backend BOUNDARIES block therefore omit the boundary-risk relationships that the new visualizer guidance says should be rendered.

Useful? React with 👍 / 👎.

}


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)
Comment on lines +91 to +92

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse RATIOS with its single-line reader

When block is RATIOS (included in DEFAULT_BLOCK_NAMES and selectable with --block RATIOS), this path still uses walk_tree, which only looks for fenced # === RATIOS === blocks. The new RATIOS support in parsers/universal.py is explicitly single-line via parse_ratios_file, so generated collections silently omit existing RATIOS declarations and --expected-block RATIOS reports files as missing even when they have the ratio lines.

Useful? React with 👍 / 👎.

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 `<reponame>_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
74 changes: 74 additions & 0 deletions .agents/skills/msdmd/collection.ts
Original file line number Diff line number Diff line change
@@ -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 `<reponame>_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<string, string>;

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
Loading
Loading