-
Notifications
You must be signed in to change notification settings - Fork 0
chore(skills): re-sync vendored .agents/skills to skill-lib@d0f6209 #113
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| a7100dd7895270498a1562aa9f4efc485e05b14e25c9fe0421a5bf3cb3b77408 generate.py | ||
| 06cec293ebd7b9a04d84fbfcd4cee55ebb058b6f265e758b6acadd0efd86595d generate.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", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For BOUNDARIES blocks that declare the documented risk fields such as 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When 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 | ||
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a consuming repo with this skill vendored under
.agents/skills, running this documented command from the repo root fails becausemsdmdis not on Python's module search path; if the user instead changes into.agents/skillsto makepython -mwork,--root .scans the skills directory rather than the repo. The command needs to setPYTHONPATH=.agents/skillsor use another invocation that works from the intended root.Useful? React with 👍 / 👎.