GitHub's /contributors endpoint is heavily cached and can lag a merge by up
to a day. dakshverma23's commit from #129 was already linked to their account
- /commits reports it, and the commit API confirms the link - but they were
absent from the contributor wall because /contributors had not refreshed.
update-contributors.py now unions the two endpoints: /contributors for the
authoritative counts and ordering, /commits for anyone linked but not yet
surfaced. Commits authored with an unlinkable email still appear in neither,
which matches what GitHub's own contributor graph shows.
Wall goes from 13 to 14.
The index-freshness gate raced update-index.yml. Both trigger on a push to
main touching skills/**, so a merge that adds a skill runs the check against
the pre-merge index.json while update-index.yml is regenerating it. Merging
#129 turned main red for about fourteen minutes before the next push cleared
it, with nothing actually wrong.
The check still does its job where it matters - on pull requests, where the
contributor is the one who has to regenerate. On main, update-index.yml is
the mechanism that keeps it current, so verifying it in parallel only ever
produces a false red.
The Thanks-to-our-contributors block was hand-maintained and had gone stale:
it listed 8 people where the API reports 13, omitting valorisa (second by
contribution count with 18), Daytona39264, kevglynn, OctoBored and farhan6667.
tools/update-contributors.py regenerates the block between
<!-- contributors:start --> and <!-- contributors:end --> from the GitHub
contributors API, ordered by contribution count, bots excluded, with the
contribution count in each avatar's tooltip. It has a --check mode and
refuses to write an empty wall if the API returns nothing.
.github/workflows/update-contributors.yml runs it on push to main, weekly,
and on demand, committing only when the block actually changes. The weekly
pass exists because the contributors API is cached and can lag a merge by
about a day, so a push-triggered run alone would miss people.
Avatars come from github.com/<login>.png rather than a third-party
contributor-image service. A README image is fetched on every page view, so
an external host would be an uncontrolled dependency in the most-viewed file
in the repository - the same objection raised against the star-history
endpoint swap in #124.
Adds comprehensive GDPR compliance assessment skill covering Article 30 records,
lawful basis validation, data subject rights, DPIAs, breach notification,
international transfers, and technical/organizational measures.
Features:
- 295-line skill body (under 500-line cap)
- 1008-char description with negative triggers (under 1024 limit)
- 9 files total, all within skill directory
- 3 production scripts: article30_parser.py, article30_validator.py, generate_ropa_report.py
- Detailed workflow, templates, and references
Negative triggers direct users to:
- implementing-gdpr-data-protection-controls for Article 32 technical controls
- implementing-gdpr-data-subject-access-request for DSAR automation
Legal basis: EU Regulation 2016/679, UK GDPR as amended by Data Protection Act 2018
and Data (Use and Access) Act 2025. Effective date: August 2026.
Validation: validate-skill.py PASS, lint-descriptions.py PASS
The README advertised "29 security domains" and its table listed 29 rows
summing to 785 of 817 skills. Six domains were missing entirely and nine
carried stale counts, because alias subdomains fold into their canonical
form (security-operations into soc-operations, and so on).
The table is now generated from the subdomain field in each skill's
frontmatter: 34 domains, summing to 817. The six that were invisible are
Threat Detection (7), Blockchain Security (2), Wireless Security (2),
Privacy Compliance (2), Data Protection (1) and Purple Team (1).
AGENTS.md - short, imperative instructions for agents working in this
repository: the flat layout, which files load when, the validator
commands, and the rule against reintroducing regex frontmatter parsing.
Kept deliberately brief; long context files measurably degrade agent
performance.
SUPPORT.md - where to ask what, what actually moves fastest, and an
honest statement that there is no SLA and some PRs have waited months.
Several open PRs could not be decided because CONTRIBUTING.md had nothing to
point at: no scope, no self-promotion or affiliation rules, no PR-size
guidance. Declining on an unwritten rule is unfair to contributors, so the
rules go in first.
SCOPE.md (new)
What a skill is, and what this repository is not: runtimes, engines,
products and applications belong in their own repositories. Offensive and
dual-use content is explicitly in scope - the line is defensive framing and
authorization, not subject matter.
CONTRIBUTING.md
- one skill per pull request, and why batching stalls good work
- overlap: while the description backlog is worked down, a new skill that
overlaps an existing one will usually be asked to extend it instead
- self-promotion and vendor links: commercial tools are fine, including
ones with no free tier, but cost must be stated in Prerequisites and
links must go to documentation rather than signup funnels
- affiliation disclosure, with the consequence stated: the PR goes on hold
until it is disclosed, and nothing is closed over it
- AI-assisted contributions are allowed and must be disclosed; a human must
have run the commands and takes responsibility
- review and response, including a 14-day stale window that closes nothing
permanently
- subdomain list corrected from 24 entries to the 34 canonical values the
validator actually accepts, with the 12 accepted aliases named
README.md
Removed the claim that every PR is reviewed within 48 hours. The oldest
open PR has been waiting since April. Replaced the stale "most in need"
counts, which named domains with 2 and 5 skills that actually have 6 and 10.
tools/README.md
Corrected to match the code: eight required frontmatter fields, not five,
and PyYAML is now a dependency.
First regeneration through tools/generate-index.py. Restores the 591
descriptions that were previously truncated to their first line.
The 13 that still lack terminal punctuation are genuine source-level
omissions in the SKILL.md files, not parser damage.
GitHub restricted access to the stargazer API endpoints in July 2026, so
api.star-history.com now returns an error notice rather than a chart.
Verified by parsing both SVGs: the official endpoint returns a single
path with five text nodes reading "GitHub restricted access to star
data", while the replacement returns real axes, ticks and series.
Reported in #124.
All 33 container-security skills now carry what it does, an explicit
"Use when" trigger, keywords, and a negative trigger naming the nearest
neighbour. Six collision clusters resolved by differentiating scope
rather than merging, so no skill is removed:
- kube-bench: running the tool vs interpreting findings into an audit
- Calico: portable upstream NetworkPolicy vs Calico-as-CNI vs Calico-only
CRDs (GlobalNetworkPolicy, HostEndpoint, DNS egress)
- Falco: deploying and operating it vs authoring escape rules
- container escape: tool-agnostic runtime signals vs Falco rule syntax vs
static posture audit vs offensive breakout
- Trivy: all-target platform and operator vs single-image scan
- Docker: images and Dockerfiles vs daemon.json vs the CIS audit script
Also replaces the templated "When to Use" boilerplate in these files,
including bullets that only restated the skill's own name.
Worst pair (Pod Security Standards vs Pod Security Admission) drops from
0.77 cosine to below the 0.45 threshold. Repo-wide: colliding pairs
60 -> 56, skills involved 105 -> 94.
The description is the only signal an agent sees at discovery time, so
overlapping descriptions cause misrouting. Nothing in CI checked for that.
- tools/lint-descriptions.py enforces name==folder, description <=1024
chars, terminal punctuation (a truncation canary), a trigger clause, a
negative trigger, and a 500-line body cap. Pre-existing failures are
grandfathered in tools/lint-baseline.json so this blocks new debt only;
the baseline may shrink and never grow.
- tools/detect-collisions.py scores every description pair by TF-IDF
cosine and ratchets the count of unreviewed near-duplicates. It strips
negative-trigger clauses before vectorizing: those name the sibling
skill on purpose, so scoring them would make correct disambiguation
raise a pair's similarity.
- wire both into validate-skills.yml, along with agentskills conformance,
an index.json freshness check, and a guard that fails the build if a
regex frontmatter parser is reintroduced.
- broaden the path filters from tools/validate-skill.py to tools/**, as
noted when #105 merged.
All five gates verified to fail on deliberately broken input.
index.json shipped 604 of 817 descriptions truncated to their first line.
The cause was the inline regex parser in update-index.yml: it reconstructed
multi-line descriptions only for the YAML block-scalar styles ('>' and '|')
and silently dropped continuation lines for every other style.
A census of the corpus explains the blast radius:
block scalar (description: >-) 43
single-quoted multiline 278
plain unquoted multiline 496
single-line 0
So 774 of 817 skills (94.7%) used a style the parser mishandled. Commit
d56fc0a7 had fixed only the 43 block-scalar files, and CONTRIBUTING.md
recommends that one working style, which is why it stayed hidden.
- add tools/skill_frontmatter.py, the single PyYAML-backed loader
- add tools/generate-index.py so generation is testable outside CI, with
a --check mode for use as a gate
- delete the hand-rolled parsers from validate-skill.py (98 lines) and
validate-agentskills.py, routing both through the shared loader
- implement the reserved-word check that agentskills-skill.schema.json
names validate-agentskills.py as the enforcement point for
Verified by a differential harness against yaml.safe_load ground truth:
index-vs-source mismatches 606 -> 0.
The star history chart in both READMEs no longer renders due to GitHub stargazer API restrictions. Switch the chart to a working mirror so the stargazer history displays again.
The generator's `^description:\s*(.+)$` regex captured the block-scalar
indicator (">-") instead of the wrapped text, corrupting 43 descriptions in
index.json. Parse `>`/`|` block scalars properly and regenerate (0 broken).
Also refresh the count-update comment examples 754 -> 817.
The standard requires only name+description; additional top-level keys are
permitted metadata. Fixes:
- validator no longer counts extra top-level keys as violations (reports them
as informational only).
- angle-bracket check now ignores YAML block-scalar indicators (`>`, `>-`,
`|`), which were false-positiving on multi-line descriptions; no skill has
genuine angle-bracket content.
- schema additionalProperties false -> true to match.
Audit result: 817/817 compliant.
- tools/agentskills-skill.schema.json: strict JSON Schema for the
agentskills.io SKILL.md frontmatter standard (name+description required;
optional license/compatibility/metadata/allowed-tools; no other top-level
keys).
- tools/validate-agentskills.py: read-only compliance validator (also checks
name==directory and the no-angle-brackets rule).
Each rewritten description now states both what the skill does (concrete
capability, named tools/artifacts) and an explicit when-to-use trigger,
improving agent discovery/activation. Grounded in each skill's own body;
changes confined to the `description` field only (bodies and all other
frontmatter untouched). Produced by a gated audit->rewrite->recheck loop
(548 -> 0 flagged) with a sampled anti-invention check (0 ungrounded).
Schema: 817/817 pass. Framework-ID gate: 0 defects.
Deterministic audit against vendored MITRE/NIST oracles (ATT&CK v19.1,
ATLAS 2026.07, NIST CSF 2.0, D3FEND v1.4.0) found and fixed:
- 27 wrong-framework leaks on 12 AI-security skills: ATLAS AML.* IDs were
under `mitre_attack` (-> `atlas_techniques`) and AI-RMF GOVERN/MEASURE IDs
under `nist_csf` (-> `nist_ai_rmf`).
- RS.AN-01 -> RS.AN-03 on 37 forensics/incident-analysis skills (CSF 1.1 ID
retired in CSF 2.0; RS.AN-03 is the incident-analysis successor).
- PR.DS-06 -> PR.DS-01 on the SLSA/Sigstore provenance skill (CSF 1.1 ID
absorbed into PR.DS-01 in CSF 2.0; body prose updated too).
- AML.T0104 -> AML.T0010 on 3 software-supply-chain skills (T0104 is
"Publish Poisoned AI Agent Tool" -- wrong topic; T0010 "AI Supply Chain
Compromise" is correct).
CSF/ATLAS replacements verified against NIST CSWP.29, the official CSF
1.1->2.0 transition workbook, and mitre-atlas/atlas-data.
Framework-ID gate: 0 defects. Schema: 817/817 pass.
Address PR review threads: valid YAML subdomain example, separate
D3FEND list items, NIST CSF ID convention, document tools/validate-skill.py,
and fix Quick Reference backticks.
generate_ioc_report()'s "generated" field used:
datetime.utcnow().isoformat() if "datetime" in dir() else ""
"datetime" is never imported anywhere in this file, and dir() with
no arguments only inspects local scope names -- so this guard is
always False, and every generated report had "generated": "" instead
of a real timestamp.
Fixed by importing datetime/timezone at the top and calling
datetime.now(timezone.utc).isoformat() directly (the non-deprecated
replacement for utcnow(), since Python 3.12 deprecates utcnow()).
Tested: python3 -m py_compile, --help works, and:
python3 agent.py report --file <any file>
now produces a real ISO 8601 UTC timestamp
(e.g. "2026-07-17T14:46:03.178652+00:00") instead of an empty string.
Add AV false-positive guidance for issue #100, move inline YARA rule to
references/yara-fileless-powershell.yar, and replace literal encoded
PowerShell payload with a placeholder in the sample report.
Add tools/validate-skill.py and the workflow file to the push/pull_request
path filters so edits to the validator or workflow re-run validation, and
add workflow_dispatch for manual runs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add hardware-firmware-security as a canonical subdomain (folding in the
firmware-analysis/firmware-security aliases). The 4 new hardware/firmware
skills failed validation because the subdomain was not in the allowed set.
- Skip skills/*.bak/ backup directories in --all mode; they have no SKILL.md
and were producing 21 false failures.
- Result: validate-skill.py --all now reports 817/817 passing, exit 0.