Skip to content

Commit 8829faf

Browse files
Jeffallanclaude
andcommitted
Add cross-reference validation, upgrade CoC to Contributor Covenant v2.1
- Add CrossRefChecker with bidirectional and orphan detection (--check crossrefs) - Upgrade Code of Conduct to Contributor Covenant v2.1 - Version bump v0.4.7 Closes #100 Closes #131 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e997ec0 commit 8829faf

9 files changed

Lines changed: 167 additions & 24 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
},
66
"metadata": {
77
"description": "Comprehensive skill pack for full-stack developers covering frameworks, workflows, and security",
8-
"version": "0.4.6"
8+
"version": "0.4.7"
99
},
1010
"plugins": [
1111
{
1212
"name": "fullstack-dev-skills",
1313
"source": "./",
1414
"description": "66 specialized skills for full-stack development: 12 language experts (Python, TypeScript, Go, Rust, C++, Swift, Kotlin, C#, PHP, Java, SQL, JavaScript), 10 backend frameworks, 6 frontend/mobile, plus infrastructure, DevOps, security, and testing skills. Includes 9 project workflow commands for epic planning, discovery, execution, and retrospectives.",
15-
"version": "0.4.6",
15+
"version": "0.4.7",
1616
"author": {
1717
"name": "jeffallan",
1818
"email": "github@jeffallan"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fullstack-dev-skills",
3-
"version": "0.4.6",
3+
"version": "0.4.7",
44
"description": "Comprehensive skill pack with 66 specialized skills for full-stack developers: 12 language experts (Python, TypeScript, Go, Rust, C++, Swift, Kotlin, C#, PHP, Java, SQL, JavaScript), 10 backend frameworks, 6 frontend/mobile, plus infrastructure, DevOps, security, and testing. Features progressive disclosure architecture for 50% faster loading.",
55
"author": {
66
"name": "jeffallan",

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.4.7] - 2026-02-08
9+
10+
### Added
11+
- Bidirectional cross-reference validation in `validate-skills.py` (closes #100)
12+
- `CrossRefChecker` class: warns when skill A references B but B doesn't reference A
13+
- Orphan detection: warns about skills with no incoming or outgoing references
14+
- New `--check crossrefs` CLI option for isolated cross-reference validation
15+
- All issues are WARNING severity (advisory, won't fail CI)
16+
17+
### Changed
18+
- Upgraded Code of Conduct to Contributor Covenant v2.1 (closes #131)
19+
- Refactored `CrossRefChecker._build_graph()` to reuse `BaseChecker._extract_frontmatter()`
20+
821
## [0.4.6] - 2026-02-08
922

1023
### Added
@@ -320,6 +333,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
320333
- Monitoring: Prometheus, Grafana, ELK, DataDog
321334
- Security: OWASP Top 10, SAST tools
322335

336+
[0.4.7]: https://github.com/jeffallan/claude-skills/compare/v0.4.6...v0.4.7
323337
[0.4.6]: https://github.com/jeffallan/claude-skills/compare/v0.4.5...v0.4.6
324338
[0.4.5]: https://github.com/jeffallan/claude-skills/compare/v0.4.4...v0.4.5
325339
[0.4.4]: https://github.com/jeffallan/claude-skills/compare/v0.4.3...v0.4.4

CONTRIBUTING.md

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -287,20 +287,37 @@ Before submitting:
287287

288288
## Code of Conduct
289289

290-
### Be Respectful
291-
- Be welcoming and inclusive
292-
- Respect differing viewpoints
293-
- Give and receive constructive feedback gracefully
294-
295-
### Be Collaborative
296-
- Help others learn and grow
297-
- Share knowledge generously
298-
- Acknowledge contributions
299-
300-
### Be Professional
301-
- Stay on topic
302-
- Assume good intent
303-
- Keep discussions productive
290+
This project follows the [Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
291+
292+
### Our Pledge
293+
294+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
295+
296+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
297+
298+
### Our Standards
299+
300+
Examples of behavior that contributes to a positive environment:
301+
302+
- Demonstrating empathy and kindness toward other people
303+
- Being respectful of differing opinions, viewpoints, and experiences
304+
- Giving and gracefully accepting constructive feedback
305+
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
306+
- Focusing on what is best not just for us as individuals, but for the overall community
307+
308+
Examples of unacceptable behavior:
309+
310+
- The use of sexualized language or imagery, and sexual attention or advances of any kind
311+
- Trolling, insulting or derogatory comments, and personal or political attacks
312+
- Public or private harassment
313+
- Publishing others' private information, such as a physical or email address, without their explicit permission
314+
- Other conduct which could reasonably be considered inappropriate in a professional setting
315+
316+
### Enforcement
317+
318+
Project maintainers are responsible for clarifying and enforcing standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
319+
320+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting the maintainers directly. All complaints will be reviewed and investigated promptly and fairly.
304321

305322
## Questions
306323

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
</p>
44

55
<p align="center">
6-
<a href="https://github.com/jeffallan/claude-skills"><img src="https://img.shields.io/badge/version-0.4.6-blue.svg?style=for-the-badge" alt="Version"/></a>
6+
<a href="https://github.com/jeffallan/claude-skills"><img src="https://img.shields.io/badge/version-0.4.7-blue.svg?style=for-the-badge" alt="Version"/></a>
77
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=for-the-badge" alt="License"/></a>
88
<a href="https://github.com/jeffallan/claude-skills"><img src="https://img.shields.io/badge/Claude_Code-Plugin-purple.svg?style=for-the-badge" alt="Claude Code"/></a>
99
<a href="https://github.com/jeffallan/claude-skills/stargazers"><img src="https://img.shields.io/github/stars/jeffallan/claude-skills?style=for-the-badge&color=yellow" alt="Stars"/></a>

ROADMAP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Current Status
44

5-
**Version:** v<!-- VERSION -->0.4.6<!-- /VERSION --> (Released January 2026)
5+
**Version:** v<!-- VERSION -->0.4.7<!-- /VERSION --> (Released January 2026)
66

77
- **<!-- SKILL_COUNT -->66<!-- /SKILL_COUNT --> Skills** across 12 domains
88
- **<!-- REFERENCE_COUNT -->365<!-- /REFERENCE_COUNT --> Reference Files** with progressive disclosure architecture
@@ -202,4 +202,4 @@ We welcome community input on the roadmap direction. Here's how you can contribu
202202

203203
---
204204

205-
*This roadmap is a living document and subject to change based on community feedback, technical constraints, and emerging priorities. Last updated: February 2026 (v0.4.6)*
205+
*This roadmap is a living document and subject to change based on community feedback, technical constraints, and emerging priorities. Last updated: February 2026 (v0.4.7)*

assets/social-preview.png

-1.89 KB
Loading

scripts/validate-skills.py

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
python scripts/validate-skills.py --check yaml # YAML-related checks only
1111
python scripts/validate-skills.py --check references # Reference checks only
1212
python scripts/validate-skills.py --check workflows # Workflow definition checks only
13+
python scripts/validate-skills.py --check crossrefs # Cross-reference validation only
1314
python scripts/validate-skills.py --skill react-expert # Single skill
1415
python scripts/validate-skills.py --format json # JSON for CI
1516
@@ -287,34 +288,39 @@ class ValidationReport:
287288
results: list[ValidationResult] = field(default_factory=list)
288289
count_issues: list[ValidationIssue] = field(default_factory=list)
289290
workflow_issues: list[ValidationIssue] = field(default_factory=list)
291+
crossref_issues: list[ValidationIssue] = field(default_factory=list)
290292

291293
@property
292294
def has_errors(self) -> bool:
293295
return (
294296
any(r.has_errors for r in self.results)
295297
or any(i.severity == Severity.ERROR for i in self.count_issues)
296298
or any(i.severity == Severity.ERROR for i in self.workflow_issues)
299+
or any(i.severity == Severity.ERROR for i in self.crossref_issues)
297300
)
298301

299302
@property
300303
def total_errors(self) -> int:
301304
count = sum(1 for r in self.results for i in r.issues if i.severity == Severity.ERROR)
302305
count += sum(1 for i in self.count_issues if i.severity == Severity.ERROR)
303306
count += sum(1 for i in self.workflow_issues if i.severity == Severity.ERROR)
307+
count += sum(1 for i in self.crossref_issues if i.severity == Severity.ERROR)
304308
return count
305309

306310
@property
307311
def total_warnings(self) -> int:
308312
count = sum(1 for r in self.results for i in r.issues if i.severity == Severity.WARNING)
309313
count += sum(1 for i in self.count_issues if i.severity == Severity.WARNING)
310314
count += sum(1 for i in self.workflow_issues if i.severity == Severity.WARNING)
315+
count += sum(1 for i in self.crossref_issues if i.severity == Severity.WARNING)
311316
return count
312317

313318
def to_dict(self) -> dict:
314319
return {
315320
"results": [r.to_dict() for r in self.results],
316321
"count_issues": [i.to_dict() for i in self.count_issues],
317322
"workflow_issues": [i.to_dict() for i in self.workflow_issues],
323+
"crossref_issues": [i.to_dict() for i in self.crossref_issues],
318324
"summary": {
319325
"total_skills": len(self.results),
320326
"total_errors": self.total_errors,
@@ -1533,6 +1539,97 @@ def check(self, skills_dir: Path) -> list[ValidationIssue]:
15331539
return issues
15341540

15351541

1542+
# =============================================================================
1543+
# Cross-Reference Checker
1544+
# =============================================================================
1545+
1546+
class CrossRefChecker:
1547+
"""Validates bidirectional related-skills references and detects orphan skills."""
1548+
1549+
name = "crossrefs"
1550+
1551+
def check(self, skills_dir: Path) -> list[ValidationIssue]:
1552+
issues = []
1553+
graph = self._build_graph(skills_dir)
1554+
issues.extend(self._check_bidirectional(graph))
1555+
issues.extend(self._check_orphans(graph, skills_dir))
1556+
return issues
1557+
1558+
def _build_graph(self, skills_dir: Path) -> dict[str, set[str]]:
1559+
"""Parse all skills' metadata.related-skills into a graph."""
1560+
graph: dict[str, set[str]] = {}
1561+
1562+
for skill_path in sorted(skills_dir.iterdir()):
1563+
if not skill_path.is_dir() or skill_path.name.startswith("."):
1564+
continue
1565+
1566+
result = BaseChecker._extract_frontmatter(skill_path)
1567+
if result is None:
1568+
if (skill_path / "SKILL.md").exists():
1569+
graph[skill_path.name] = set()
1570+
continue
1571+
1572+
metadata = result.frontmatter.get("metadata", {})
1573+
if not isinstance(metadata, dict):
1574+
graph[skill_path.name] = set()
1575+
continue
1576+
1577+
related = metadata.get("related-skills", "")
1578+
if isinstance(related, str) and related.strip():
1579+
refs = {r.strip() for r in related.split(",") if r.strip()}
1580+
else:
1581+
refs = set()
1582+
1583+
graph[skill_path.name] = refs
1584+
1585+
return graph
1586+
1587+
def _check_bidirectional(self, graph: dict[str, set[str]]) -> list[ValidationIssue]:
1588+
"""Warn when A references B but B does not reference A."""
1589+
issues = []
1590+
reported: set[tuple[str, str]] = set()
1591+
1592+
for skill, refs in sorted(graph.items()):
1593+
for ref in sorted(refs):
1594+
if ref not in graph:
1595+
continue # Non-existent skill; MetadataFieldsChecker handles this
1596+
pair = (min(skill, ref), max(skill, ref))
1597+
if pair in reported:
1598+
continue
1599+
if skill not in graph[ref]:
1600+
reported.add(pair)
1601+
issues.append(ValidationIssue(
1602+
skill="__crossrefs__",
1603+
check=self.name,
1604+
severity=Severity.WARNING,
1605+
message=f"'{skill}' references '{ref}' but '{ref}' does not reference '{skill}'",
1606+
))
1607+
1608+
return issues
1609+
1610+
def _check_orphans(
1611+
self, graph: dict[str, set[str]], skills_dir: Path
1612+
) -> list[ValidationIssue]:
1613+
"""Warn about skills with no incoming or outgoing references."""
1614+
issues = []
1615+
1616+
# Collect all skills that are referenced by at least one other skill
1617+
referenced: set[str] = set()
1618+
for refs in graph.values():
1619+
referenced.update(refs)
1620+
1621+
for skill, refs in sorted(graph.items()):
1622+
if not refs and skill not in referenced:
1623+
issues.append(ValidationIssue(
1624+
skill="__crossrefs__",
1625+
check=self.name,
1626+
severity=Severity.WARNING,
1627+
message=f"Orphan skill: '{skill}' has no related-skills and is not referenced by any other skill",
1628+
))
1629+
1630+
return issues
1631+
1632+
15361633
# =============================================================================
15371634
# Workflow Validator
15381635
# =============================================================================
@@ -1594,6 +1691,15 @@ def format(self, report: ValidationReport) -> str:
15941691
scope = f"[{issue.skill}] " if issue.skill else ""
15951692
lines.append(f" [{icon}] {scope}{issue.check}: {issue.message}{file_info}")
15961693

1694+
# Cross-reference issues
1695+
if report.crossref_issues:
1696+
lines.append("")
1697+
lines.append("CROSS-REFERENCE ISSUES:")
1698+
lines.append("-" * 80)
1699+
for issue in report.crossref_issues:
1700+
icon = "ERROR" if issue.severity == Severity.ERROR else "WARN "
1701+
lines.append(f" [{icon}] {issue.message}")
1702+
15971703
# Count issues
15981704
if report.count_issues:
15991705
lines.append("")
@@ -1733,12 +1839,13 @@ def main():
17331839
yaml - YAML frontmatter validation (parsing, required fields, format)
17341840
references - Reference file validation (directory, count, headers)
17351841
workflows - Workflow YAML definitions, manifest DAG, orphan detection
1842+
crossrefs - Bidirectional related-skills validation, orphan detection
17361843
""",
17371844
)
17381845

17391846
parser.add_argument(
17401847
"--check",
1741-
choices=["yaml", "references", "workflows"],
1848+
choices=["yaml", "references", "workflows", "crossrefs"],
17421849
help="Run only checks in the specified category",
17431850
)
17441851

@@ -1764,8 +1871,8 @@ def main():
17641871

17651872
report = ValidationReport()
17661873

1767-
# Run skill validation (unless --check workflows)
1768-
if args.check != "workflows":
1874+
# Run skill validation (unless --check workflows or --check crossrefs)
1875+
if args.check not in ("workflows", "crossrefs"):
17691876
validator = SkillValidator(
17701877
skills_dir=args.skills_dir,
17711878
check_category=args.check,
@@ -1779,6 +1886,11 @@ def main():
17791886
workflow_validator = WorkflowValidator(base_path)
17801887
report.workflow_issues = workflow_validator.validate()
17811888

1889+
# Run cross-reference validation (unless filtering to single skill or non-crossref category)
1890+
if args.check == "crossrefs" or (args.check is None and not args.skill):
1891+
crossref_checker = CrossRefChecker()
1892+
report.crossref_issues = crossref_checker.check(Path(args.skills_dir))
1893+
17821894
# Format and output
17831895
if args.format == "json":
17841896
formatter = JsonFormatter()

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.4.6",
2+
"version": "0.4.7",
33
"skillCount": 66,
44
"workflowCount": 9,
55
"referenceFileCount": 365

0 commit comments

Comments
 (0)