fix(gates): parse the bullet form of Contributing files
validate-provenance.sh matched Contributing files only as a single inline line beginning "- **Contributing files:**". Seven skills write it as a bare "**Contributing files:**" heading above a bullet list, so parse_contributing_files returned None and checks 4 (contributing file exists) and 5 (bidirectional source_keys) silently verified nothing on git-branches, git-remotes, git-submodules, git-workflow, git-worktrees, gitea-files and gitea-releases. Those are among the skills this branch changed most — git-branches alone gained five reference files — and the retrofit's mandatory sources.md collateral went in unchecked. Demonstrated rather than argued: planting a nonexistent contributing path in git-remotes yields 0 findings under the old parser and 1 FAIL under the new one. Both forms are now accepted. The bullet form is parsed per bullet rather than by splitting a joined value, because its per-file notes contain commas that would otherwise be read as path separators. The return type becomes a list of note-stripped paths, with "(none)" as an empty list and an absent entry as None, so the two callers no longer re-split a string. Applied to agent-audit's copy as well. No agent ships a sources.md today, so it is latent there, but it is the same defect. This is a third gate blind spot alongside #117 and #118, and was unfiled. One real defect surfaced immediately and is fixed separately. Refs #99
This commit is contained in:
@@ -131,6 +131,15 @@ def parse_h2_slugs(content):
|
||||
return re.findall(r'^## (.+)$', content, re.MULTILINE)
|
||||
|
||||
def parse_contributing_files(content, slug):
|
||||
"""Find the Contributing files for a given slug H2 in content.
|
||||
|
||||
Accepts the inline form and the bullet form; recognising only the
|
||||
inline one silently skips the contributing-file checks on every
|
||||
sources.md written the other way. Returns a list of paths with any
|
||||
trailing parenthetical note stripped; "(none)" returns an empty list
|
||||
and a slug with no entry returns None. Kept behaviourally identical to
|
||||
skill-audit's copy, which is where the bug was found.
|
||||
"""
|
||||
pattern = re.compile(
|
||||
r'^## ' + re.escape(slug) + r'\s*\n(.*?)(?=^## |\Z)',
|
||||
re.MULTILINE | re.DOTALL
|
||||
@@ -139,10 +148,36 @@ def parse_contributing_files(content, slug):
|
||||
if not m:
|
||||
return None
|
||||
block = m.group(1)
|
||||
|
||||
def strip_note(entry):
|
||||
return re.sub(r'\s*\(.*$', '', entry).strip()
|
||||
|
||||
cf_m = re.search(r'^\- \*\*Contributing files:\*\* (.+)$', block, re.MULTILINE)
|
||||
if cf_m:
|
||||
value = cf_m.group(1).strip()
|
||||
if value.startswith("(none"):
|
||||
return []
|
||||
return [p for p in (strip_note(x) for x in value.split(",")) if p]
|
||||
|
||||
cf_m = re.search(r'^\*\*Contributing files:\*\*\s*$', block, re.MULTILINE)
|
||||
if not cf_m:
|
||||
return None
|
||||
return cf_m.group(1).strip()
|
||||
files = []
|
||||
for line in block[cf_m.end():].splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
if files:
|
||||
break
|
||||
continue
|
||||
if not line.startswith("- "):
|
||||
break
|
||||
entry = line[2:].strip()
|
||||
if entry.startswith("(none"):
|
||||
return []
|
||||
entry = strip_note(entry)
|
||||
if entry:
|
||||
files.append(entry)
|
||||
return files
|
||||
|
||||
def parse_research_doc(content, slug):
|
||||
pattern = re.compile(
|
||||
@@ -242,9 +277,8 @@ for fpath, keys in [(agent_file, given_keys)]:
|
||||
# --- 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)
|
||||
if cf_value and not cf_value.startswith("(none"):
|
||||
cf_files = [p.strip() for p in cf_value.split(",") if p.strip()]
|
||||
cf_files = parse_contributing_files(sources_content, slug)
|
||||
if cf_files:
|
||||
for cf_rel in cf_files:
|
||||
cf_abs = os.path.join(plugin_root, cf_rel)
|
||||
if not os.path.isfile(cf_abs):
|
||||
|
||||
Reference in New Issue
Block a user