Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Initial CHANGELOG.md setup for tracking project history
- Documentation structure for future contributor updates
- Added .flake8 config file to enforce consistent 88-character line limit for all contributors
- DevPath Sentinel developer tool for repository health and dataset integrity validation (#1295)
- Dataset validator to detect duplicate project IDs, duplicate project titles, missing required fields, empty required fields, and missing starter code references

### Changed

Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,21 @@ pytest tests/ -v

If you add a new feature, add at least one corresponding test in `tests/test_basic.py`.

### Repository Validation

Before opening a pull request, contributors are encouraged to run DevPath Sentinel.

```bash
python -m tools.sentinel.cli

---

## Submitting a Pull Request

### Before opening a PR

- All 27 tests pass locally
- Run `python -m tools.sentinel.cli` to validate the project dataset before opening a pull request
- You have tested the running app in your browser
- Your branch is up to date with the upstream `main` branch
- Your code follows the style rules above
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ All tests passed

---

### Validate the Project Dataset

Run DevPath Sentinel to check the repository dataset for common integrity issues:

```bash
python -m tools.sentinel.cli
```

The current validator checks for:

- duplicate project IDs
- duplicate project titles
- missing required fields
- empty required fields
- missing starter code references

---

## Troubleshooting

<details>
Expand Down
260 changes: 260 additions & 0 deletions tests/test_sentinel_dataset_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
"""
Tests for the DevPath Sentinel dataset validator.
"""

from __future__ import annotations

import json

from tools.sentinel.validators.dataset_validator import run


def create_project(**overrides):
"""Create a valid project dictionary."""

project = {
"id": 1,
"title": "Expense Tracker",
"skills": ["Python"],
"level": "Beginner",
"interest": "Finance",
"time": "2 weeks",
"description": "Track expenses.",
"features": [
"Add expense",
"Delete expense",
],
"roadmap": [
"Planning",
"Implementation",
],
"resources": [
"https://python.org",
],
"starter_code": "starter_code/expense_tracker.py",
}

project.update(overrides)
return project


def write_dataset(tmp_path, projects):
"""Create a temporary projects.json file."""

data_dir = tmp_path / "data"
data_dir.mkdir()

dataset_path = data_dir / "projects.json"

dataset_path.write_text(
json.dumps(projects, indent=2),
encoding="utf-8",
)

return dataset_path


def create_starter_file(tmp_path, filename):
"""Create a starter code file."""

starter_dir = tmp_path / "starter_code"
starter_dir.mkdir(exist_ok=True)

(starter_dir / filename).write_text(
"# starter code",
encoding="utf-8",
)


def test_valid_dataset(tmp_path):
"""A valid dataset should pass validation."""

create_starter_file(
tmp_path,
"expense_tracker.py",
)

dataset = write_dataset(
tmp_path,
[create_project()],
)

result = run(dataset)

assert result.passed is True
assert result.errors == []
assert result.warnings == []


def test_duplicate_project_ids(tmp_path):
"""Duplicate IDs should produce an error."""

create_starter_file(
tmp_path,
"expense_tracker.py",
)

dataset = write_dataset(
tmp_path,
[
create_project(id=1),
create_project(
id=1,
title="Calculator",
),
],
)

result = run(dataset)

assert result.passed is False

assert any(
"Duplicate project ID"
in error
for error in result.errors
)


def test_duplicate_project_titles(tmp_path):
"""Duplicate titles should produce an error."""

create_starter_file(
tmp_path,
"expense_tracker.py",
)

dataset = write_dataset(
tmp_path,
[
create_project(id=1),
create_project(id=2),
],
)

result = run(dataset)

assert result.passed is False

assert any(
"Duplicate project title"
in error
for error in result.errors
)


def test_missing_required_field(tmp_path):
"""Missing required fields should fail validation."""

create_starter_file(
tmp_path,
"expense_tracker.py",
)

project = create_project()

del project["description"]

dataset = write_dataset(
tmp_path,
[project],
)

result = run(dataset)

assert result.passed is False

assert any(
"missing required fields"
in error
for error in result.errors
)


def test_empty_required_field(tmp_path):
"""Empty required string fields should fail validation."""

create_starter_file(
tmp_path,
"expense_tracker.py",
)

dataset = write_dataset(
tmp_path,
[
create_project(
title="",
),
],
)

result = run(dataset)

assert result.passed is False

assert any(
"empty 'title'"
in error
for error in result.errors
)


def test_missing_starter_code_warning(tmp_path):
"""Missing starter code should produce a warning."""

dataset = write_dataset(
tmp_path,
[create_project()],
)

result = run(dataset)

assert result.passed is True
assert result.errors == []
assert len(result.warnings) == 1

assert "[1]" in result.warnings[0]


def test_invalid_json(tmp_path):
"""Invalid JSON should fail validation."""

data_dir = tmp_path / "data"
data_dir.mkdir()

dataset = data_dir / "projects.json"

dataset.write_text(
"{ invalid json",
encoding="utf-8",
)

result = run(dataset)

assert result.passed is False

assert any(
"Invalid JSON"
in error
for error in result.errors
)


def test_missing_dataset_file(tmp_path):
"""Missing dataset file should fail validation."""

dataset = (
tmp_path
/ "data"
/ "projects.json"
)

result = run(dataset)

assert result.passed is False

assert any(
"Dataset not found"
in error
for error in result.errors
)
3 changes: 3 additions & 0 deletions tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Developer tools for DevPath.
"""
37 changes: 37 additions & 0 deletions tools/sentinel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# DevPath Sentinel

DevPath Sentinel is a lightweight developer utility for validating repository integrity.

It currently provides a dataset validator that checks the project dataset for common issues before changes are submitted.

## Features

The current validator detects:

- Duplicate project IDs
- Duplicate project titles
- Missing required fields
- Empty required fields
- Missing starter code references

## Usage

Run the validator from the project root:

```bash
python -m tools.sentinel.cli
```

The validator prints a summary of all checks, including any warnings or errors found in the dataset.

## Project Structure

```
tools/
└── sentinel/
├── cli.py
├── models.py
├── report.py
└── validators/
└── dataset_validator.py
```
6 changes: 6 additions & 0 deletions tools/sentinel/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
DevPath Sentinel

Repository health and integrity validation tool.
"""

26 changes: 26 additions & 0 deletions tools/sentinel/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Command-line entry point for DevPath Sentinel.
"""
import sys

from .report import print_banner, print_validation_result
from .validators.dataset_validator import run


def main() -> None:
"""Run DevPath Sentinel."""

print_banner()

result = run()

print_validation_result(result)

if result.errors:
sys.exit(1)

sys.exit(0)


if __name__ == "__main__":
main()
Loading