fix(kyberforge): tell an unparsable Contributing-files block from an explicit (none)

parse_contributing_files documented that callers depend on None vs [], because a
parse failure returning [] would silently disable the check. Only check 8
honoured it; checks 4/5 (skill-audit) and 3/4 (agent-audit) used a truthiness
test, so an unreadable block disabled them without a word.

Two live corpus entries were skipping this way. A sweep of all 32 sources.md
found 134 entries, exactly 2 parsing to None, both in gitea-files: one heading
carried an inline parenthetical that defeated both regexes, and one (none) was
written without its leading bullet. Also pins EMPTY_SOURCE_KEYS_RE to the two
indents parse_source_keys actually reads.

agent-audit had no INFO tier at all, so it gains one rather than reporting a
check that could not run as a FAIL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJJrm5YmacbwMdzZpXcoti
This commit is contained in:
2026-08-31 19:46:05 +00:00
parent c232e69645
commit 00daf285ec
8 changed files with 418 additions and 32 deletions

View File

@@ -2,13 +2,13 @@
## gitea-mcp-repo ## gitea-mcp-repo
**Description:** Official gitea-mcp repository (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags. **Description:** Official gitea-mcp repository (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags. Tool parameters and SHA/concurrency behavior were cross-checked live against the deployed MCP tool schemas via `ToolSearch`, per this repo's process for resolving schema-vs-docs drift, rather than copied from the derived research doc.
**Source:** https://gitea.com/gitea/gitea-mcp **Source:** https://gitea.com/gitea/gitea-mcp
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
**Contributing files:** (tool parameters and SHA/concurrency behavior cross-checked live against the deployed MCP tool schemas via ToolSearch) **Contributing files:**
- SKILL.md (Gotchas — cross-flow parameter and encoding traps; Dispatch) - SKILL.md (Gotchas — cross-flow parameter and encoding traps; Dispatch)
- references/reading.md (read-tool parameters, `ref`/`tree_sha` selection, tree pagination) - references/reading.md (read-tool parameters, `ref`/`tree_sha` selection, tree pagination)
- references/writing.md (write-tool parameters, SHA/concurrency behavior, canonical call sequences, failed-write triage) - references/writing.md (write-tool parameters, SHA/concurrency behavior, canonical call sequences, failed-write triage)
@@ -45,4 +45,4 @@
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
**Contributing files:** (none) - **Contributing files:** (none)

View File

@@ -2,13 +2,13 @@
## gitea-mcp-repo ## gitea-mcp-repo
**Description:** Official gitea-mcp repository (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags. **Description:** Official gitea-mcp repository (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags. Tool parameters and SHA/concurrency behavior were cross-checked live against the deployed MCP tool schemas via `ToolSearch`, per this repo's process for resolving schema-vs-docs drift, rather than copied from the derived research doc.
**Source:** https://gitea.com/gitea/gitea-mcp **Source:** https://gitea.com/gitea/gitea-mcp
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
**Contributing files:** (tool parameters and SHA/concurrency behavior cross-checked live against the deployed MCP tool schemas via ToolSearch) **Contributing files:**
- SKILL.md (Gotchas — cross-flow parameter and encoding traps; Dispatch) - SKILL.md (Gotchas — cross-flow parameter and encoding traps; Dispatch)
- references/reading.md (read-tool parameters, `ref`/`tree_sha` selection, tree pagination) - references/reading.md (read-tool parameters, `ref`/`tree_sha` selection, tree pagination)
- references/writing.md (write-tool parameters, SHA/concurrency behavior, canonical call sequences, failed-write triage) - references/writing.md (write-tool parameters, SHA/concurrency behavior, canonical call sequences, failed-write triage)
@@ -45,4 +45,4 @@
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
**Contributing files:** (none) - **Contributing files:** (none)

View File

@@ -22,7 +22,10 @@ Checks performed:
0 source_keys present in agent pair but sources.md absent 0 source_keys present in agent pair but sources.md absent
1 FILL IN: placeholders in sources.md 1 FILL IN: placeholders in sources.md
2 source_keys in agent files → slug exists 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) 3 Contributing files listed in sources.md exist on disk (plugin-root
relative). An explicit '(none)' skips silently; a Contributing files block
this parser cannot read is reported as an INFO saying checks 3 and 4 did
not run, never skipped silently.
4 Contributing files back-reference the parent slug in their source_keys 4 Contributing files back-reference the parent slug in their source_keys
5 Research doc field present and not placeholder 5 Research doc field present and not placeholder
EOF EOF
@@ -244,14 +247,31 @@ has_fail = False
def emit_fail(desc, fpath, why, fix): def emit_fail(desc, fpath, why, fix):
global has_fail global has_fail
has_fail = True has_fail = True
findings.append(("FAIL", desc, fpath, why, fix)) findings.append(("FAIL", desc, fpath, why, fix, None))
# INFO does not set has_fail and does not change the exit code. It is for a
# check that could not RUN — an unverified entry, not a broken one — and it
# exists so that "did not run" is never spelled the same way as "passed".
def emit_info(desc, fpath, note):
findings.append(("INFO", desc, fpath, None, None, note))
def print_findings(): def print_findings():
for kind, desc, fpath, why, fix in findings: for entry in findings:
kind = entry[0]
desc = entry[1]
fpath = entry[2]
why = entry[3]
fix = entry[4]
note = entry[5]
if kind == "FAIL":
print(f"FAIL {desc} — {fpath}") print(f"FAIL {desc} — {fpath}")
print(f" Why: {why}") print(f" Why: {why}")
print(f" Fix: {fix}") print(f" Fix: {fix}")
print() print()
else:
print(f"INFO {desc} — {fpath}")
print(f" Note: {note}")
print()
# --- Collect source_keys from agent pair --- # --- Collect source_keys from agent pair ---
def get_source_keys_from_file(fpath): def get_source_keys_from_file(fpath):
@@ -321,9 +341,24 @@ for fpath, keys in [(agent_file, given_keys)]:
# --- Checks 3, 4, 5: Per-slug checks in sources.md --- # --- Checks 3, 4, 5: Per-slug checks in sources.md ---
for slug in parse_h2_slugs(sources_content): for slug in parse_h2_slugs(sources_content):
# Check 3: Contributing files exist (paths relative to plugin root) # Checks 3 and 4: Contributing files exist (paths relative to plugin root),
# and back-reference the slug. `[]` and None are NOT the same answer here.
# `[]` is the author writing "(none)" — there is nothing to check and the
# skip is correct. None is a Contributing-files block this parser cannot
# read, and skipping THAT silently disables both checks on the one entry
# least likely to be right, which is the failure mode
# parse_contributing_files' own docstring warns about. Say so out loud.
cf_files = parse_contributing_files(sources_content, slug) cf_files = parse_contributing_files(sources_content, slug)
if cf_files: if cf_files is None:
emit_info(
f"Contributing-file checks skipped for '{slug}' — the Contributing files block could not be parsed",
f"sources.md (## {slug})",
f"The '## {slug}' entry has no Contributing files list this parser can read — a missing field, a bare heading, '*' bullets, a numbered list, or prose all read as unparsable rather than as an empty declaration. "
f"Checks 3 and 4 did not run for this slug, so nothing verified that its contributing files exist or name it back. "
f"Write the value as '- **Contributing files:** <comma-separated paths>', or as a '**Contributing files:**' heading followed by '- ' bullets — "
f"or record '(none)' if this source contributed no files."
)
elif cf_files:
for cf_rel in cf_files: for cf_rel in cf_files:
cf_abs = os.path.join(plugin_root, cf_rel) cf_abs = os.path.join(plugin_root, cf_rel)
if not os.path.isfile(cf_abs): if not os.path.isfile(cf_abs):

View File

@@ -433,3 +433,140 @@ EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md" run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success assert_success
} }
# ---------------------------------------------------------------------------
# Checks 3 and 4: None ("could not parse") is NOT [] ("explicitly (none)")
#
# parse_contributing_files returns three distinguishable answers and checks 3
# and 4 have to honour all three. `[]` is the author writing "(none)" — the
# skip is correct and silent. None is a Contributing files block the parser
# cannot read, and skipping THAT silently disables both checks on the one entry
# least likely to be right, which is the failure mode the parser's own
# docstring warns about. The assertions below are therefore about the INFO
# appearing; a silent exit 0 is exactly the bug.
# ---------------------------------------------------------------------------
@test "INFO: an unparsable Contributing files block names the slug instead of skipping checks 3 and 4 silently" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
**Contributing files:**
* .apm/agents/ghost.agent.md (asterisk bullets are not the bullet form)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "INFO"
assert_output --partial "Contributing-file checks skipped for 'my-source' — the Contributing files block could not be parsed"
}
@test "INFO: an entry with no Contributing files field at all is reported, not skipped silently" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "INFO"
assert_output --partial "Contributing-file checks skipped for 'my-source' — the Contributing files block could not be parsed"
}
@test "checks 3 and 4 skipped silently: an explicit '(none)' emits no INFO" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
make_sources_md "$root" "my-source" "(none — not used directly)"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output ""
}
@test "checks 3 and 4 still run: a parseable Contributing files list is not diverted to the INFO" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
make_sources_md "$root" "my-source" ".apm/agents/nonexistent.agent.md"
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "FAIL"
assert_output --partial "Contributing file '.apm/agents/nonexistent.agent.md' does not exist"
refute_output --partial "could not be parsed"
}
# ---------------------------------------------------------------------------
# The INFO tier itself: kind-aware printing and a kind-aware exit code
#
# INFO is new here — before it, findings was a 5-tuple and print_findings
# stamped every entry FAIL. The two cases below pin the tier rather than any
# one check: an INFO must print under the INFO prefix and leave the exit code
# at 0, and a real FAIL must keep printing under the FAIL prefix and still exit
# non-zero even when an INFO is sitting in the same findings list.
# ---------------------------------------------------------------------------
@test "INFO tier: an INFO alone prints as INFO with a Note and does not set a failing exit code" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_success
assert_output --partial "INFO Contributing-file checks skipped for 'my-source'"
assert_output --partial "Note:"
refute_output --partial "FAIL"
}
@test "FAIL tier: a genuine FAIL alongside an INFO still prints as FAIL and exits non-zero" {
local root="$TMPDIR/package"
make_package "$root"
make_agent_with_source_keys "$root"
cat > "$root/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Contributing files:** .apm/agents/nonexistent.agent.md
- **Research doc:** (none)
- **Status:** \`extracted\`
## ghost-source
- **URL:** https://example.com/ghost-source
- **Description:** Another test source.
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$root/.apm/agents/my-agent.agent.md"
assert_failure
assert_output --partial "FAIL Contributing file '.apm/agents/nonexistent.agent.md' does not exist"
assert_output --partial "Why:"
assert_output --partial "INFO Contributing-file checks skipped for 'ghost-source'"
assert_output --partial "Note:"
}

View File

@@ -21,7 +21,10 @@ Checks performed:
3 source_keys in references/*.md → slug exists in sources.md (INFO if no 3 source_keys in references/*.md → slug exists in sources.md (INFO if no
source_keys; an explicit 'source_keys: []' declares the file house-authored source_keys; an explicit 'source_keys: []' declares the file house-authored
and passes silently) and passes silently)
4 Contributing files listed in sources.md exist on disk 4 Contributing files listed in sources.md exist on disk. An explicit
'(none)' skips silently; a Contributing files block this parser cannot
read is reported as an INFO saying checks 4 and 5 did not run, never
skipped silently.
5 Contributing files back-reference the parent slug in their source_keys 5 Contributing files back-reference the parent slug in their source_keys
6 Research doc field present and not placeholder 6 Research doc field present and not placeholder
7 Slug in sources.md present in upstream research doc (INFO only). A section 7 Slug in sources.md present in upstream research doc (INFO only). A section
@@ -104,7 +107,13 @@ def parse_source_keys(fm):
# claims that then have to be maintained in sources.md as well. A bare # claims that then have to be maintained in sources.md as well. A bare
# `source_keys:` with nothing after it is NOT accepted here — that reads as a # `source_keys:` with nothing after it is NOT accepted here — that reads as a
# truncated or half-written entry, not a decision. # truncated or half-written entry, not a decision.
EMPTY_SOURCE_KEYS_RE = re.compile(r'^\s*source_keys:\s*\[\s*\]\s*$') #
# The indent is pinned to the two positions parse_source_keys() actually reads
# — column 0, or two spaces under `metadata:`. A permissive `^\s*` matched a
# `source_keys: []` nested at ANY depth under an unrelated key, which
# parse_source_keys() never reads, so a stray nested key silenced the check-3
# INFO for a file that had declared nothing.
EMPTY_SOURCE_KEYS_RE = re.compile(r'^(?: )?source_keys:\s*\[\s*\]\s*$')
def declares_empty_source_keys(fm): def declares_empty_source_keys(fm):
"""True when frontmatter carries an explicit, empty `source_keys: []`.""" """True when frontmatter carries an explicit, empty `source_keys: []`."""
@@ -436,9 +445,25 @@ repo_root = find_repo_root(skill_dir)
research_docs_seen = {} # abs_path → set of slugs in sources.md that reference it research_docs_seen = {} # abs_path → set of slugs in sources.md that reference it
for slug in parse_h2_slugs(sources_content): for slug in parse_h2_slugs(sources_content):
# Check 4: Contributing files exist # Checks 4 and 5: Contributing files exist, and back-reference the slug.
# `[]` and None are NOT the same answer here. `[]` is the author writing
# "(none)" — there is nothing to check and the skip is correct. None is a
# Contributing-files block this parser cannot read, and skipping THAT
# silently disables both checks on the one entry least likely to be right,
# which is the failure mode parse_contributing_files' own docstring warns
# about. Say so out loud instead, the same way an unresolvable Research doc
# value does.
cf_files = parse_contributing_files(sources_content, slug) cf_files = parse_contributing_files(sources_content, slug)
if cf_files: if cf_files is None:
emit_info(
f"Contributing-file checks skipped for '{slug}' — the Contributing files block could not be parsed",
f"references/sources.md (## {slug})",
f"The '## {slug}' entry has no Contributing files list this parser can read — a missing field, a bare heading, '*' bullets, a numbered list, or prose all read as unparsable rather than as an empty declaration. "
f"Checks 4 and 5 did not run for this slug, so nothing verified that its contributing files exist or name it back. "
f"Write the value as '- **Contributing files:** <comma-separated paths>', or as a '**Contributing files:**' heading followed by '- ' bullets — "
f"or record '(none)' if this source contributed no files."
)
elif cf_files:
for cf_rel in cf_files: for cf_rel in cf_files:
cf_abs = os.path.join(skill_dir, cf_rel) cf_abs = os.path.join(skill_dir, cf_rel)
if not os.path.isfile(cf_abs): if not os.path.isfile(cf_abs):

View File

@@ -985,3 +985,132 @@ EOF
assert_output --partial "INFO" assert_output --partial "INFO"
assert_output --partial "Upstream checks skipped for 'my-source' — no repo root above the skill directory" assert_output --partial "Upstream checks skipped for 'my-source' — no repo root above the skill directory"
} }
# ---------------------------------------------------------------------------
# Cycle 16 — Checks 4 and 5: None ("could not parse") is NOT [] ("explicitly
# (none)"), on the sources.md side this time
#
# Cycle 13 pinned the distinction for check 8, which reads the parser's output
# against a RESEARCH doc. Checks 4 and 5 read it against the skill's own
# sources.md and honoured neither half: a truthiness test collapsed None into
# [], so an unreadable Contributing files block disabled both checks and the
# script still exited 0 with no output — the failure mode
# parse_contributing_files' docstring names in as many words. The assertions
# below are therefore about the INFO appearing; a silent exit 0 is exactly the
# bug.
# ---------------------------------------------------------------------------
@test "INFO: an unparsable Contributing files block names the slug instead of skipping checks 4 and 5 silently" {
local skill="$TMPDIR/my-skill"
make_skill_with_source_keys "$skill"
mkdir -p "$skill/references"
cat > "$skill/references/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
**Contributing files:**
* references/ghost.md (asterisk bullets are not the bullet form)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$skill"
assert_success
assert_output --partial "INFO"
assert_output --partial "Contributing-file checks skipped for 'my-source' — the Contributing files block could not be parsed"
}
@test "INFO: an entry with no Contributing files field at all is reported, not skipped silently" {
local skill="$TMPDIR/my-skill"
make_skill_with_source_keys "$skill"
mkdir -p "$skill/references"
cat > "$skill/references/sources.md" <<EOF
# Sources
## my-source
- **URL:** https://example.com/my-source
- **Description:** A test source.
- **Research doc:** (none)
- **Status:** \`extracted\`
EOF
run bash "$SCRIPT" "$skill"
assert_success
assert_output --partial "INFO"
assert_output --partial "Contributing-file checks skipped for 'my-source' — the Contributing files block could not be parsed"
}
@test "checks 4 and 5 skipped silently: an explicit '(none)' emits no INFO" {
local skill="$TMPDIR/my-skill"
make_skill_with_source_keys "$skill"
make_sources_md "$skill" "my-source" "(none — not used directly)"
run bash "$SCRIPT" "$skill"
assert_success
assert_output ""
}
@test "checks 4 and 5 still run: a parseable Contributing files list is not diverted to the INFO" {
local skill="$TMPDIR/my-skill"
make_skill_with_source_keys "$skill"
make_sources_md "$skill" "my-source" "references/nonexistent.md"
run bash "$SCRIPT" "$skill"
assert_failure
assert_output --partial "FAIL"
assert_output --partial "Contributing file 'references/nonexistent.md' does not exist"
refute_output --partial "could not be parsed"
}
# ---------------------------------------------------------------------------
# Cycle 17 — Check 3 (#111): 'source_keys: []' only declares anything in the
# two positions parse_source_keys() actually reads
#
# The declaration and the parse have to agree on WHERE the key lives. They did
# not: the declaration regex accepted any indent, so a `source_keys: []` buried
# under an unrelated key — a position parse_source_keys() never reads — passed
# as a house-authored declaration and silenced the INFO for a file that had
# declared nothing.
# ---------------------------------------------------------------------------
@test "INFO: 'source_keys: []' nested under an unrelated key is not a declaration" {
local skill="$TMPDIR/my-skill"
make_skill_with_source_keys "$skill"
make_sources_md "$skill"
cat > "$skill/references/extra.md" <<EOF
---
title: Extra Reference
unrelated:
nested:
source_keys: []
---
# Extra Reference
The empty list is nested where parse_source_keys never looks.
EOF
run bash "$SCRIPT" "$skill"
assert_success
assert_output --partial "INFO"
assert_output --partial "No source_keys frontmatter"
}
@test "INFO: a four-space-indented 'source_keys: []' is not a declaration either" {
local skill="$TMPDIR/my-skill"
make_skill_with_source_keys "$skill"
make_sources_md "$skill"
cat > "$skill/references/extra.md" <<EOF
---
metadata:
source_keys: []
---
# Extra Reference
Two spaces is the position parse_source_keys reads under metadata:, not four.
EOF
run bash "$SCRIPT" "$skill"
assert_success
assert_output --partial "INFO"
assert_output --partial "No source_keys frontmatter"
}

View File

@@ -22,7 +22,10 @@ Checks performed:
0 source_keys present in agent pair but sources.md absent 0 source_keys present in agent pair but sources.md absent
1 FILL IN: placeholders in sources.md 1 FILL IN: placeholders in sources.md
2 source_keys in agent files → slug exists 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) 3 Contributing files listed in sources.md exist on disk (plugin-root
relative). An explicit '(none)' skips silently; a Contributing files block
this parser cannot read is reported as an INFO saying checks 3 and 4 did
not run, never skipped silently.
4 Contributing files back-reference the parent slug in their source_keys 4 Contributing files back-reference the parent slug in their source_keys
5 Research doc field present and not placeholder 5 Research doc field present and not placeholder
EOF EOF
@@ -244,14 +247,31 @@ has_fail = False
def emit_fail(desc, fpath, why, fix): def emit_fail(desc, fpath, why, fix):
global has_fail global has_fail
has_fail = True has_fail = True
findings.append(("FAIL", desc, fpath, why, fix)) findings.append(("FAIL", desc, fpath, why, fix, None))
# INFO does not set has_fail and does not change the exit code. It is for a
# check that could not RUN — an unverified entry, not a broken one — and it
# exists so that "did not run" is never spelled the same way as "passed".
def emit_info(desc, fpath, note):
findings.append(("INFO", desc, fpath, None, None, note))
def print_findings(): def print_findings():
for kind, desc, fpath, why, fix in findings: for entry in findings:
kind = entry[0]
desc = entry[1]
fpath = entry[2]
why = entry[3]
fix = entry[4]
note = entry[5]
if kind == "FAIL":
print(f"FAIL {desc} — {fpath}") print(f"FAIL {desc} — {fpath}")
print(f" Why: {why}") print(f" Why: {why}")
print(f" Fix: {fix}") print(f" Fix: {fix}")
print() print()
else:
print(f"INFO {desc} — {fpath}")
print(f" Note: {note}")
print()
# --- Collect source_keys from agent pair --- # --- Collect source_keys from agent pair ---
def get_source_keys_from_file(fpath): def get_source_keys_from_file(fpath):
@@ -321,9 +341,24 @@ for fpath, keys in [(agent_file, given_keys)]:
# --- Checks 3, 4, 5: Per-slug checks in sources.md --- # --- Checks 3, 4, 5: Per-slug checks in sources.md ---
for slug in parse_h2_slugs(sources_content): for slug in parse_h2_slugs(sources_content):
# Check 3: Contributing files exist (paths relative to plugin root) # Checks 3 and 4: Contributing files exist (paths relative to plugin root),
# and back-reference the slug. `[]` and None are NOT the same answer here.
# `[]` is the author writing "(none)" — there is nothing to check and the
# skip is correct. None is a Contributing-files block this parser cannot
# read, and skipping THAT silently disables both checks on the one entry
# least likely to be right, which is the failure mode
# parse_contributing_files' own docstring warns about. Say so out loud.
cf_files = parse_contributing_files(sources_content, slug) cf_files = parse_contributing_files(sources_content, slug)
if cf_files: if cf_files is None:
emit_info(
f"Contributing-file checks skipped for '{slug}' — the Contributing files block could not be parsed",
f"sources.md (## {slug})",
f"The '## {slug}' entry has no Contributing files list this parser can read — a missing field, a bare heading, '*' bullets, a numbered list, or prose all read as unparsable rather than as an empty declaration. "
f"Checks 3 and 4 did not run for this slug, so nothing verified that its contributing files exist or name it back. "
f"Write the value as '- **Contributing files:** <comma-separated paths>', or as a '**Contributing files:**' heading followed by '- ' bullets — "
f"or record '(none)' if this source contributed no files."
)
elif cf_files:
for cf_rel in cf_files: for cf_rel in cf_files:
cf_abs = os.path.join(plugin_root, cf_rel) cf_abs = os.path.join(plugin_root, cf_rel)
if not os.path.isfile(cf_abs): if not os.path.isfile(cf_abs):

View File

@@ -21,7 +21,10 @@ Checks performed:
3 source_keys in references/*.md → slug exists in sources.md (INFO if no 3 source_keys in references/*.md → slug exists in sources.md (INFO if no
source_keys; an explicit 'source_keys: []' declares the file house-authored source_keys; an explicit 'source_keys: []' declares the file house-authored
and passes silently) and passes silently)
4 Contributing files listed in sources.md exist on disk 4 Contributing files listed in sources.md exist on disk. An explicit
'(none)' skips silently; a Contributing files block this parser cannot
read is reported as an INFO saying checks 4 and 5 did not run, never
skipped silently.
5 Contributing files back-reference the parent slug in their source_keys 5 Contributing files back-reference the parent slug in their source_keys
6 Research doc field present and not placeholder 6 Research doc field present and not placeholder
7 Slug in sources.md present in upstream research doc (INFO only). A section 7 Slug in sources.md present in upstream research doc (INFO only). A section
@@ -104,7 +107,13 @@ def parse_source_keys(fm):
# claims that then have to be maintained in sources.md as well. A bare # claims that then have to be maintained in sources.md as well. A bare
# `source_keys:` with nothing after it is NOT accepted here — that reads as a # `source_keys:` with nothing after it is NOT accepted here — that reads as a
# truncated or half-written entry, not a decision. # truncated or half-written entry, not a decision.
EMPTY_SOURCE_KEYS_RE = re.compile(r'^\s*source_keys:\s*\[\s*\]\s*$') #
# The indent is pinned to the two positions parse_source_keys() actually reads
# — column 0, or two spaces under `metadata:`. A permissive `^\s*` matched a
# `source_keys: []` nested at ANY depth under an unrelated key, which
# parse_source_keys() never reads, so a stray nested key silenced the check-3
# INFO for a file that had declared nothing.
EMPTY_SOURCE_KEYS_RE = re.compile(r'^(?: )?source_keys:\s*\[\s*\]\s*$')
def declares_empty_source_keys(fm): def declares_empty_source_keys(fm):
"""True when frontmatter carries an explicit, empty `source_keys: []`.""" """True when frontmatter carries an explicit, empty `source_keys: []`."""
@@ -436,9 +445,25 @@ repo_root = find_repo_root(skill_dir)
research_docs_seen = {} # abs_path → set of slugs in sources.md that reference it research_docs_seen = {} # abs_path → set of slugs in sources.md that reference it
for slug in parse_h2_slugs(sources_content): for slug in parse_h2_slugs(sources_content):
# Check 4: Contributing files exist # Checks 4 and 5: Contributing files exist, and back-reference the slug.
# `[]` and None are NOT the same answer here. `[]` is the author writing
# "(none)" — there is nothing to check and the skip is correct. None is a
# Contributing-files block this parser cannot read, and skipping THAT
# silently disables both checks on the one entry least likely to be right,
# which is the failure mode parse_contributing_files' own docstring warns
# about. Say so out loud instead, the same way an unresolvable Research doc
# value does.
cf_files = parse_contributing_files(sources_content, slug) cf_files = parse_contributing_files(sources_content, slug)
if cf_files: if cf_files is None:
emit_info(
f"Contributing-file checks skipped for '{slug}' — the Contributing files block could not be parsed",
f"references/sources.md (## {slug})",
f"The '## {slug}' entry has no Contributing files list this parser can read — a missing field, a bare heading, '*' bullets, a numbered list, or prose all read as unparsable rather than as an empty declaration. "
f"Checks 4 and 5 did not run for this slug, so nothing verified that its contributing files exist or name it back. "
f"Write the value as '- **Contributing files:** <comma-separated paths>', or as a '**Contributing files:**' heading followed by '- ' bullets — "
f"or record '(none)' if this source contributed no files."
)
elif cf_files:
for cf_rel in cf_files: for cf_rel in cf_files:
cf_abs = os.path.join(skill_dir, cf_rel) cf_abs = os.path.join(skill_dir, cf_rel)
if not os.path.isfile(cf_abs): if not os.path.isfile(cf_abs):