fix(agents): relocate provenance sources.md outside agents/ dir

`claude plugin validate --strict` auto-discovers every .md under a
plugin's agents/ directory as an agent requiring frontmatter, so the
provenance file there always needs fake agent frontmatter to pass
validation. Confirmed empirically that an explicit `agents` manifest
array can't suppress this discovery. Move the file to <plugin-root>/
sources.md instead, and update agent-author/agent-audit accordingly.

Adds ADR-0010, partially superseding ADR-0005's `agents/sources.md`
convention. Fixes #63.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 09:01:06 +00:00
parent 5deed07a95
commit 996d9be428
16 changed files with 125 additions and 68 deletions

View File

@@ -16,10 +16,10 @@ Exit codes:
1 One or more checks failed
Checks performed:
0 source_keys present in agent pair but agents/sources.md absent
1 FILL IN: placeholders in agents/sources.md
2 source_keys in agent files → slug exists in agents/sources.md
3 Contributing files listed in agents/sources.md exist on disk (plugin-root relative)
0 source_keys present in agent pair but sources.md absent
1 FILL IN: placeholders in sources.md
2 source_keys in agent files → slug exists in sources.md
3 Contributing files listed in sources.md exist on disk (plugin-root relative)
4 Contributing files back-reference the parent slug in their source_keys
5 Research doc field present and not placeholder
EOF
@@ -78,7 +78,7 @@ if provider == 'copilot':
else:
counterpart = os.path.join(agent_dir, name_stem + '.agent.md')
sources_md_path = os.path.join(plugin_root, 'agents', 'sources.md')
sources_md_path = os.path.join(plugin_root, 'sources.md')
# --- Helpers ---
PLACEHOLDER_RE = re.compile(r'(?<!`)FILL IN:[^`\n]')
@@ -188,30 +188,30 @@ if sources_md_exists:
sources_content = f.read()
sources_slugs = set(parse_h2_slugs(sources_content))
# --- Check 0: source_keys present but agents/sources.md absent ---
# --- Check 0: source_keys present but sources.md absent ---
if not sources_md_exists and all_source_keys:
rel_given = os.path.relpath(agent_file, plugin_root)
emit_fail(
"source_keys declared but agents/sources.md is absent",
"source_keys declared but sources.md is absent",
rel_given,
"source_keys references research provenance that has no sources index to validate against.",
"Create agents/sources.md with an H2 entry for each slug referenced by source_keys."
"Create sources.md with an H2 entry for each slug referenced by source_keys."
)
print_findings()
sys.exit(1)
# --- Check 1: FILL IN: placeholders in agents/sources.md ---
# --- Check 1: FILL IN: placeholders in sources.md ---
for line in sources_content.splitlines():
if PLACEHOLDER_RE.search(line):
emit_fail(
"Unfilled FILL IN: placeholder",
"agents/sources.md",
"agents/sources.md contains an unfilled placeholder, meaning provenance is incomplete.",
"Replace all 'FILL IN:' values in agents/sources.md with real content."
"sources.md",
"sources.md contains an unfilled placeholder, meaning provenance is incomplete.",
"Replace all 'FILL IN:' values in sources.md with real content."
)
break
# --- Check 2: source_keys in agent files → slug exists in agents/sources.md ---
# --- Check 2: source_keys in agent files → slug exists in sources.md ---
for fpath, keys in [(agent_file, given_keys), (counterpart, counterpart_keys)]:
if not keys:
continue
@@ -219,13 +219,13 @@ for fpath, keys in [(agent_file, given_keys), (counterpart, counterpart_keys)]:
for slug in keys:
if slug not in sources_slugs:
emit_fail(
f"source_keys slug '{slug}' not found in agents/sources.md",
f"source_keys slug '{slug}' not found in sources.md",
rel,
f"'{rel}' declares '{slug}' as a source but there is no '## {slug}' heading in agents/sources.md.",
f"Add '## {slug}' entry to agents/sources.md or remove '{slug}' from {rel} source_keys."
f"'{rel}' declares '{slug}' as a source but there is no '## {slug}' heading in sources.md.",
f"Add '## {slug}' entry to sources.md or remove '{slug}' from {rel} source_keys."
)
# --- Checks 3, 4, 5: Per-slug checks in agents/sources.md ---
# --- Checks 3, 4, 5: Per-slug checks in sources.md ---
for slug in parse_h2_slugs(sources_content):
# Check 3: Contributing files exist (paths relative to plugin root)
cf_value = parse_contributing_files(sources_content, slug)
@@ -236,9 +236,9 @@ for slug in parse_h2_slugs(sources_content):
if not os.path.isfile(cf_abs):
emit_fail(
f"Contributing file '{cf_rel}' does not exist",
f"agents/sources.md (## {slug})",
f"agents/sources.md claims '{cf_rel}' was contributed to by slug '{slug}' but the file does not exist.",
f"Create '{cf_rel}' relative to the plugin root, or correct the path in agents/sources.md."
f"sources.md (## {slug})",
f"sources.md claims '{cf_rel}' was contributed to by slug '{slug}' but the file does not exist.",
f"Create '{cf_rel}' relative to the plugin root, or correct the path in sources.md."
)
else:
# Check 4: Bidirectional — file should list slug in its source_keys
@@ -249,8 +249,8 @@ for slug in parse_h2_slugs(sources_content):
if slug not in cf_keys:
emit_fail(
f"Contributing file '{cf_rel}' does not list '{slug}' in its source_keys",
f"agents/sources.md (## {slug})",
f"agents/sources.md says '{cf_rel}' was informed by '{slug}', but '{cf_rel}' does not declare '{slug}' in its top-level source_keys.",
f"sources.md (## {slug})",
f"sources.md says '{cf_rel}' was informed by '{slug}', but '{cf_rel}' does not declare '{slug}' in its top-level source_keys.",
f"Add '{slug}' to the top-level source_keys frontmatter in '{cf_rel}'."
)
@@ -259,14 +259,14 @@ for slug in parse_h2_slugs(sources_content):
if rd_value is None:
emit_fail(
"Research doc field missing",
f"agents/sources.md (## {slug})",
f"The '## {slug}' entry in agents/sources.md has no '- **Research doc:**' line.",
f"Add '- **Research doc:** <path-or-(none)>' to the '## {slug}' entry in agents/sources.md."
f"sources.md (## {slug})",
f"The '## {slug}' entry in sources.md has no '- **Research doc:**' line.",
f"Add '- **Research doc:** <path-or-(none)>' to the '## {slug}' entry in sources.md."
)
elif rd_value == "" or PLACEHOLDER_RE.search(rd_value):
emit_fail(
"Research doc field is empty or placeholder",
f"agents/sources.md (## {slug})",
f"sources.md (## {slug})",
f"The '## {slug}' entry has an unfilled Research doc value.",
"Set '- **Research doc:**' to a real path relative to repo root, or '(none)' if not applicable."
)