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:
@@ -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."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user