Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ type: software
authors:
- family-names: CodeSigils
license: MIT
version: "0.1.1"
date-released: "2026-07-25"
version: "0.1.2"
date-released: "2026-08-21"
repository-code: "https://github.com/CodeSigils/skill-discovery"
keywords:
- agent
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ Run the relevant checks before opening a pull request:
```bash
uv sync --locked --only-dev
uv run python scripts/validate-ci.py
uv run python scripts/validate-ci.py --self-test
uv run python scripts/check-version-consistency.py
uv run python scripts/check-readme-tree.py
uv run python scripts/cron-health.py
uv run ruff check .github/scripts/ scripts/
uv run python .github/scripts/test_validators.py
uv run python -m pytest .github/scripts/test_integration.py -v
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ skill-discovery/
│ ├── check-readme-tree.py # README layout vs disk check
│ ├── check-version-consistency.py # CITATION.cff ↔ pyproject.toml version
│ ├── cron-health.py # weekly link rot, reference integrity, budget
│ ├── test_common.py # tests for _common.py utilities
│ ├── test_validate_skill.py # tests for validate-skill
│ ├── validate-ci.py # CI workflow structural validator
│ ├── validate-evaluation-fixtures.py # offline discovery report-contract check
Expand Down Expand Up @@ -240,7 +241,7 @@ independently; use the verified local installation paths above.
| Payload, documentation, evaluation fixtures, and dependency validation | Every push and pull request | CI reports failures that must be fixed before merge. |
| External contract reachability and URL drift | Weekly schedule or manual dispatch | CI refreshes the evidence manifest through bounded checks and opens a PR when changes need review. |
| Research expiry and reference accuracy | Weekly schedule or manual review | A maintainer reviews expiring research and updates dated references or the affected guidance. |
| Internal link rot, reference integrity, SKILL.md budget | Weekly schedule or manual dispatch | Detect-only checks warn when markdown links break, reference files go missing, or the skill payload exceeds budget. |
| Internal link rot, reference integrity, SKILL.md budget | Weekly schedule or manual dispatch | Detect-only checks warn when markdown links break, reference files go missing, or the skill payload exceeds budget. Known warnings are suppressed by the advisory baseline. |

## Security

Expand Down
4 changes: 4 additions & 0 deletions advisory-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"reviewed": "2026-08-21",
"warnings": []
}
33 changes: 25 additions & 8 deletions proposals/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,9 @@ from known noise.
- Format validation — `validate-ci.py` covers structure


## Recommendation: Advisory Baseline
## Advisory Baseline ✅ IMPLEMENTED (P9)

**Priority:** Medium. Not blocking any user-facing feature.
**Effort:** ~2-3 hours.
**Value:** Reduces weekly CI noise by filtering known warnings.
**Files affected:** `scripts/cron-health.py`, new `advisory-baseline.json`.

Add as a future improvement when the weekly cron starts producing noise.
Not urgent — current weekly cron is functional and all warnings are valid.
Implemented 2026-08-21. See P9 section for details.


## Improvement Ideas ✅ COMPLETE
Expand Down Expand Up @@ -553,6 +547,29 @@ Code review identified 5 cleanup items. All fixed in PR #28.

Net: -14 lines. All 35 tests pass, ruff clean, LSP clean.

### P9 — Advisory baseline (2026-08-21) ✅ COMPLETE

Weekly health checks (`cron-health.py`) now diff current warnings against a
baseline snapshot. Only NEW warnings are reported; known warnings are
suppressed. Resolved warnings (in baseline but no longer produced) are
flagged for baseline update.

**Pattern source:** [awesome-agent-trust](https://github.com/CodeSigils/awesome-agent-trust)
advisory-baseline.json pattern, adapted for flat warning strings.

**Files affected:**
- `scripts/_common.py` — `load_advisory_baseline()`, `save_advisory_baseline()`, `diff_advisories()`
- `scripts/cron-health.py` — baseline diff in `main()`, `--update-baseline` flag
- `advisory-baseline.json` — new file, snapshots current warnings

**Usage:**
```bash
cron-health.py # run all checks, report vs baseline
cron-health.py --update-baseline # snapshot current warnings as new baseline
```

**Status:** Implemented 2026-08-21.

### What we're NOT doing (Phase 3)

- CODEOWNERS — solo project, no reviewers to assign
Expand Down
19 changes: 18 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
[project]
name = "skill-discovery"
version = "0.1.1"
version = "0.1.2"
description = "A methodology for finding and evaluating agent skills"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [
{ name = "CodeSigils" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Libraries",
]

[project.urls]
Homepage = "https://github.com/CodeSigils/skill-discovery"
Repository = "https://github.com/CodeSigils/skill-discovery"
Issues = "https://github.com/CodeSigils/skill-discovery/issues"

[dependency-groups]
dev = [
Expand Down
55 changes: 53 additions & 2 deletions scripts/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def parse_expiry_date(value: Any) -> date | None:


def check_fences(content: str, label: str) -> list[str]:
"""Require matched fences and a language tag on opening fences."""
"""Require matched code fences and a language tag on opening fences."""
errors: list[str] = []
opening: tuple[int, str] | None = None
for line_number, line in enumerate(content.splitlines(), start=1):
Expand Down Expand Up @@ -131,6 +131,57 @@ def fail(message: str, hint: str | None = None) -> None:
print(f" HINT: {hint}", file=sys.stderr)


# ── advisory baseline ─────────────────────────────────────────────────────

ADVISORY_BASELINE_PATH = ROOT / "advisory-baseline.json"


def load_advisory_baseline(path: Path | None = None) -> list[str]:
"""Load the advisory baseline file and return known warning strings.

Returns an empty list if the file doesn't exist or is empty.
"""
import json

path = path or ADVISORY_BASELINE_PATH
if not path.exists():
return []
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return []
return data.get("warnings", []) if isinstance(data, dict) else []


def save_advisory_baseline(warnings: list[str], path: Path | None = None) -> None:
"""Save current warnings as the new baseline."""
import json
from datetime import date

path = path or ADVISORY_BASELINE_PATH
data = {
"reviewed": date.today().isoformat(),
"warnings": sorted(warnings),
}
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")


def diff_advisories(
current: list[str], baseline: list[str]
) -> tuple[list[str], list[str]]:
"""Diff current warnings against baseline.

Returns (new, resolved) where:
- new: warnings in current but not in baseline (NEW issues)
- resolved: warnings in baseline but not in current (RESOLVED issues)
"""
current_set = set(current)
baseline_set = set(baseline)
new = sorted(current_set - baseline_set)
resolved = sorted(baseline_set - current_set)
return new, resolved


# ── unsafe probe detection ────────────────────────────────────────────────

UNSAFE_PROBE_PATTERNS: list[tuple[str, str]] = [
Expand Down Expand Up @@ -161,7 +212,7 @@ def check_unsafe_probes(content: str, label: str) -> list[str]:


def check_reference_sizes(skill_md: Path, root: Path) -> list[str]:
"""Verify referenced files are within size budgets."""
"""Verify referenced files are within size budgets (50B–50KB)."""
errors: list[str] = []
label = str(skill_md.relative_to(root))
content = skill_md.read_text(encoding="utf-8")
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-readme-tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

# Directories to exclude from reverse drift check
EXCLUDE_DIRS = {".git", "node_modules", ".omo", "__pycache__", ".ruff_cache"}
EXCLUDE_FILES = {".gitignore", "uv.lock", "CITATION.cff"}
EXCLUDE_FILES = {".gitignore", "uv.lock", "CITATION.cff", "advisory-baseline.json"}


def git_tracked_files() -> set[str]:
Expand Down
73 changes: 63 additions & 10 deletions scripts/cron-health.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,25 @@
3. skill-budget – SKILL.md line count against 350-line warning threshold

Exit code 0 = all checks passed, 1 = at least one issue found.

Usage:
cron-health.py Run all checks, report vs baseline
cron-health.py --check internal-link-rot Run single check
cron-health.py --update-baseline Snapshot current warnings as new baseline
"""
from __future__ import annotations

import re
import sys

from _common import ROOT, SKILL_REF_RE, find_markdown_files
from _common import (
ROOT,
SKILL_REF_RE,
diff_advisories,
find_markdown_files,
load_advisory_baseline,
save_advisory_baseline,
)

RELATIVE_LINK_RE = re.compile(r"\[.*?\]\(((?!https?://|mailto:|#)[^)]+)\)")

Expand Down Expand Up @@ -77,17 +89,53 @@ def check_skill_budget() -> list[str]:
}


def main(check: str | None = None) -> int:
def main(check: str | None = None, update_baseline: bool = False) -> int:
checks_to_run = {check: CHECKS[check]} if check else CHECKS
all_ok = True
all_warnings: list[str] = []
for name, fn in checks_to_run.items():
issues = fn()
if issues:
all_ok = False
for issue in issues:
print(f"⚠️ {name}: {issue}", file=sys.stderr)
else:
print(f"✅ {name}: OK")
for issue in issues:
all_warnings.append(f"{name}: {issue}")

if update_baseline:
save_advisory_baseline(all_warnings)
print(f"Baseline updated: {len(all_warnings)} warnings saved")
return 0

baseline = load_advisory_baseline()
new, resolved = diff_advisories(all_warnings, baseline)

if baseline:
known = len(all_warnings) - len(new)
if known > 0:
print(f"ℹ️ {known} known warning(s) suppressed by baseline")
for w in resolved:
print(f"✅ RESOLVED (baseline): {w}")
else:
new = all_warnings

all_ok = True
for w in new:
all_ok = False
print(f"⚠️ NEW: {w}", file=sys.stderr)
for w in all_warnings:
if w not in new:
print(f"✅ {w}")
if not all_warnings:
print("✅ All checks passed")
return 0

n_new = len(new)
n_known = len(all_warnings) - n_new
n_resolved = len(resolved)
parts = []
if n_new:
parts.append(f"{n_new} new")
if n_known:
parts.append(f"{n_known} known")
if n_resolved:
parts.append(f"{n_resolved} resolved")
print(f"Summary: {', '.join(parts)} warning(s)")
return 0 if all_ok else 1


Expand All @@ -101,5 +149,10 @@ def main(check: str | None = None) -> int:
default=None,
help="Run a single check (default: all)",
)
parser.add_argument(
"--update-baseline",
action="store_true",
help="Save current warnings as the new advisory baseline",
)
args = parser.parse_args()
sys.exit(main(check=args.check))
sys.exit(main(check=args.check, update_baseline=args.update_baseline))
Loading