Files
Edward Cheng-I Wu 7ef93e0cb5 docs: re-derive data_access_level for academic-paper and academic-paper-reviewer (#773) (#793)
* docs: re-derive data_access_level for academic-paper and academic-paper-reviewer under the dirtiest-input rule (#773)

Applying the #756 derivation to the two carried-over pins the lint
docstring flagged as un-derived:

- academic-paper: redacted -> raw. Standalone modes ingest ungated user
  drafts and third-party reviewer comments, and literature_strategist's
  search-fills-gap flow ingests external-index search results inside the
  skill. The former value described only the post-Gate-2.5 pipeline path.
- academic-paper-reviewer: verified_only -> raw. The standalone
  /ars-reviewer entry (Routing Step 1 routes "review my paper" directly)
  legitimately consumes an ungated pasted manuscript; the rule
  quantifies over ALL entry paths. Pipeline positioning unchanged.
- deep-research: raw survives by a ceiling argument (no derivation can
  dirty the dirtiest value); recorded so no pin remains an un-derived
  carryover.

EXPECTED_LEVELS provenance note rewritten per-pin; ARCHITECTURE §4
diagram + rules now separate the per-skill intake annotation from the
per-stage output data level (§3 column, unchanged). Declarative only.

Closes #773

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZwcSFBwiJBZEtsSTcCxq

* review: address codex findings on #773 — precise gate-sequencing claims, deep-research derivation

- academic-paper's former 'redacted' is described as the orchestrated
  pipeline path (Stage-1 sanitized inputs), not "post-Gate-2.5" — Stage 2
  precedes Gate 2.5.
- academic-paper-reviewer's former 'verified_only' is stated as at best
  true for the initial Stage 3 dispatch; Stage 3' re-review consumes a
  freshly revised manuscript before Stage 4.5.
- deep-research's raw is re-affirmed on its actual inputs (raw queries +
  unverified search results); the ceiling argument becomes supplementary
  rather than the derivation itself.

Applied consistently across the lint docstring, ARCHITECTURE §4, and the
CHANGELOG entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZwcSFBwiJBZEtsSTcCxq

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 13:26:07 +08:00

132 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""Lint: every top-level SKILL.md declares the PINNED data_access_level.
Single pass per skill, one violation per problem: frontmatter must parse,
the skill must be registered in EXPECTED_LEVELS, and the declared value
must equal its pin. The governing rule (`ground_truth_isolation_pattern.md`
§ "Declare `data_access_level` truthfully") requires the annotation to
reflect the DIRTIEST input the skill may legitimately consume across all
its modes.
Pin provenance (honest-claim discipline) — all four pins are now
derivation-backed under the dirtiest-input rule:
- `academic-pipeline: raw` — #756-derived (Stage 1 accepts raw user
requests, mid-entry accepts raw papers; the gates run inside the
pipeline).
- `academic-paper: raw` — #773-derived. Standalone modes (revision,
revision-coach, rebuttal-audit, citation-check, ...) ingest ungated
user drafts and third-party reviewer comments, and
literature_strategist_agent's search-fills-gap flow ingests
external-index search results inside the skill — Layer-1 inputs both.
The prior `redacted` described the orchestrated pipeline path, where
Stage 2 inputs arrive as Stage-1 sanitized artifacts (Gate 2.5 runs
AFTER Stage 2, so "post-gate" would overstate even that path).
- `academic-paper-reviewer: raw` — #773-derived. The standalone
`/ars-reviewer` entry (Routing Discipline Step 1 routes "review my
paper" directly) legitimately consumes an ungated pasted manuscript;
`verified_only` was at best true for the pipeline's initial Stage 3
dispatch (post-Gate-2.5) — Stage 3' re-review consumes a freshly
revised manuscript before Stage 4.5 — and the rule quantifies over
ALL entry paths.
- `deep-research: raw` — #773 re-affirmed on its inputs: raw user
queries and unverified web/database search results are its core
intake. A ceiling argument additionally applies (`raw` is the
dirtiest value, so no re-derivation could move it further).
Changing any pin must be a deliberate, reviewed re-application of the rule,
and a new top-level skill must be registered here before it passes.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from _skill_lint import (
FrontmatterError,
iter_skill_files,
parse_frontmatter,
)
LEGAL_VALUES = frozenset({"raw", "redacted", "verified_only"})
# Dirtiest-input pins (#756, re-derived across all modes in #773).
# Keyed by skill directory name.
EXPECTED_LEVELS = {
"academic-paper": "raw",
"academic-paper-reviewer": "raw",
"academic-pipeline": "raw",
"deep-research": "raw",
}
# The pins themselves must stay inside the closed vocabulary.
assert set(EXPECTED_LEVELS.values()) <= LEGAL_VALUES
def run_all_checks(root: Path) -> list[str]:
violations: list[str] = []
seen: set[str] = set()
for skill_md in iter_skill_files(root):
name = skill_md.parent.name
seen.add(name)
try:
fm = parse_frontmatter(skill_md)
except FrontmatterError as exc:
violations.append(str(exc)) # message already carries the path
continue
if fm is None:
violations.append(f"{skill_md}: missing YAML frontmatter")
continue
if name not in EXPECTED_LEVELS:
violations.append(
f"{skill_md}: skill '{name}' is not registered in "
f"EXPECTED_LEVELS — apply the dirtiest-input rule and pin "
f"its level here"
)
continue
metadata = fm.get("metadata")
if not isinstance(metadata, dict):
violations.append(
f"{skill_md}: metadata must be a mapping/object, got "
f"{type(metadata).__name__}"
)
continue
declared = metadata.get("data_access_level")
if declared != EXPECTED_LEVELS[name]:
violations.append(
f"{skill_md}: data_access_level is {declared!r}, pinned "
f"value is {EXPECTED_LEVELS[name]!r} (change the pin "
f"deliberately or fix the declaration)"
)
for name in sorted(set(EXPECTED_LEVELS) - seen):
violations.append(
f"EXPECTED_LEVELS pins '{name}' but no top-level "
f"{name}/SKILL.md exists"
)
return violations
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--path",
type=Path,
default=Path(__file__).resolve().parent.parent,
)
args = parser.parse_args()
violations = run_all_checks(args.path)
if violations:
for v in violations:
print(f"ERROR: {v}")
print(f"\n{len(violations)} violation(s) found.", file=sys.stderr)
return 1
print(
"OK: all SKILL.md files declare a valid, pinned data_access_level."
)
return 0
if __name__ == "__main__":
sys.exit(main())