fix(kyberforge): stop the provenance checker skipping check 8 on unparsed input
484357a taught the Contributing parser the bullet form, but a block it still
could not parse returned the same empty result as an explicit "(none)", so the
checker read "no contributing files" and skipped check 8 rather than reporting
that it could not tell. Checks 7 and 8 were consequently dead across the whole
git plugin without anything failing.
The parser now distinguishes "declared none" from "could not parse", which
wakes both checks. Because the parser is duplicated between the skill-audit and
agent-audit copies, it is fenced with BEGIN/END markers and a test hashes the
two regions so the copies cannot drift apart again silently.
Addresses #111.
This commit is contained in:
@@ -130,15 +130,55 @@ def parse_source_keys(fm):
|
||||
def parse_h2_slugs(content):
|
||||
return re.findall(r'^## (.+)$', content, re.MULTILINE)
|
||||
|
||||
# ===== BEGIN SHARED CONTRIBUTING-FILES PARSER =====
|
||||
# ONE parser, embedded VERBATIM in two scripts:
|
||||
# plugins/kyberforge/.apm/skills/skill-audit/scripts/validate-provenance.sh
|
||||
# plugins/kyberforge/.apm/skills/agent-audit/scripts/validate-provenance.sh
|
||||
# The block between these markers must stay byte-identical in both. It is
|
||||
# copied rather than imported because a cache-installed plugin's scripts cannot
|
||||
# read files outside their own plugin directory, so there is no single file both
|
||||
# can share — the same constraint that forces the ADR-0020 boundary resolver to
|
||||
# be duplicated across three scripts. Edit one copy, then paste it over the
|
||||
# other.
|
||||
#
|
||||
# tests/test-adr0020-contract.sh hashes both copies and fails on drift. Before
|
||||
# it did, the agent-audit copy's docstring merely ASSERTED the two were
|
||||
# "behaviourally identical" and nothing checked it — which is how the two
|
||||
# already-diverged spellings of the bullet loop went unnoticed.
|
||||
#
|
||||
# Requires: re (imported by the host script).
|
||||
|
||||
|
||||
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.
|
||||
Both authored forms are accepted, because both are in use across the
|
||||
corpus and only recognising the first silently skipped the contributing-
|
||||
file checks on every sources.md written the other way:
|
||||
|
||||
- **Contributing files:** SKILL.md, references/a.md
|
||||
|
||||
**Contributing files:**
|
||||
- SKILL.md (what this source contributed)
|
||||
- references/a.md (what this source contributed)
|
||||
|
||||
Returns a list of paths with any trailing parenthetical note stripped.
|
||||
Note the bullet form's notes may themselves contain commas, so the list
|
||||
is built per bullet rather than by splitting the joined value.
|
||||
|
||||
The three return values are NOT interchangeable, and callers depend on
|
||||
the distinction:
|
||||
|
||||
[path, ...] the entry names contributing files
|
||||
[] the entry EXPLICITLY records "(none)"
|
||||
None the entry says nothing this parser can read
|
||||
|
||||
Only an explicit "(none)" yields []. A "Contributing files:" heading
|
||||
followed by a numbered list, by `*` bullets, or by prose parses nothing
|
||||
and returns None, never [] — a caller reads [] as a deliberate "no
|
||||
contributing files" record and SKIPS its check on that basis, so a parse
|
||||
failure returning [] would silently disable the check instead of leaving
|
||||
the unreadable entry exposed to it.
|
||||
"""
|
||||
pattern = re.compile(
|
||||
r'^## ' + re.escape(slug) + r'\s*\n(.*?)(?=^## |\Z)',
|
||||
@@ -150,15 +190,19 @@ def parse_contributing_files(content, slug):
|
||||
block = m.group(1)
|
||||
|
||||
def strip_note(entry):
|
||||
# "references/a.md (why)" -> "references/a.md"
|
||||
return re.sub(r'\s*\(.*$', '', entry).strip()
|
||||
|
||||
# Inline form: value on the same line, comma-separated, no notes.
|
||||
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]
|
||||
return [p for p in (strip_note(x) for x in value.split(","))
|
||||
if p] or None
|
||||
|
||||
# Bullet form: heading on its own line, one file per following bullet.
|
||||
cf_m = re.search(r'^\*\*Contributing files:\*\*\s*$', block, re.MULTILINE)
|
||||
if not cf_m:
|
||||
return None
|
||||
@@ -177,7 +221,8 @@ def parse_contributing_files(content, slug):
|
||||
entry = strip_note(entry)
|
||||
if entry:
|
||||
files.append(entry)
|
||||
return files
|
||||
return files or None
|
||||
# ===== END SHARED CONTRIBUTING-FILES PARSER =====
|
||||
|
||||
def parse_research_doc(content, slug):
|
||||
pattern = re.compile(
|
||||
|
||||
Reference in New Issue
Block a user