hermead 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+
9
+ # Virtual environments
10
+ venv/
11
+ .venv/
12
+
13
+ # IDEs
14
+ .vscode/
15
+ .idea/
16
+
17
+ # OS
18
+ .DS_Store
19
+ Thumbs.db
20
+
21
+ # Hermes
22
+ .hermes/
23
+
24
+ # Test coverage
25
+ htmlcov/
26
+ .coverage
27
+ .coverage.*
hermead-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tony Simons
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hermead-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermead
3
+ Version: 0.1.0
4
+ Summary: Hermes Agent plugin for project-level linting, type-checking, formatting, and security scanning
5
+ Author: Tony Simons
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: pyyaml>=6.0
10
+ Provides-Extra: dashboard
11
+ Requires-Dist: fastapi; extra == 'dashboard'
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest; extra == 'dev'
14
+ Requires-Dist: pytest-timeout; extra == 'dev'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # HermeAd
18
+
19
+ [![PyPI version](https://img.shields.io/pypi/v/hermead?color=blue)](https://pypi.org/project/hermead/)
20
+ [![License](https://img.shields.io/pypi/l/hermead?color=green)](LICENSE)
21
+ [![Python](https://img.shields.io/pypi/pyversions/hermead?color=blueviolet)](https://pypi.org/project/hermead/)
22
+
23
+ **HermeAd** is a Hermes Agent plugin that automatically runs linters, type checkers, formatters, and security scanners on project files after every `write_file` or `patch` tool call.
24
+
25
+ No manual commands. No context switching. Files get checked the moment you write them.
26
+
27
+ ## How It Works
28
+
29
+ HermeAd registers a single `post_tool_call` hook. After each file modification:
30
+
31
+ 1. Detects the file type from extension or filename
32
+ 2. Walks up the tree to find the project root (`.git`, `.hg`, or `.hermes` directory)
33
+ 3. Loads config: built-in defaults → global `~/.hermes/hermead.yaml` → per-project `.hermes/hermead.yaml`
34
+ 4. Auto-detects tooling from `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, `.shellcheckrc`
35
+ 5. Routes to the right runner for each check category (lint, type check, format, security)
36
+
37
+ Results appear inline in the session. Issues surface at the moment of introduction, not after a CI run.
38
+
39
+ ## Quick Install
40
+
41
+ ```bash
42
+ pip install hermead
43
+ ```
44
+
45
+ From source:
46
+
47
+ ```bash
48
+ git clone https://github.com/asimons81/hermead.git
49
+ cd hermead
50
+ pip install -e ".[dev]"
51
+ ```
52
+
53
+ Verify it loaded:
54
+
55
+ ```bash
56
+ hermes plugin list
57
+ # → hermead should appear
58
+ ```
59
+
60
+ ## Features
61
+
62
+ - **Zero-config for common projects.** Works out of the box with ruff + mypy for Python, eslint + prettier + tsc for JS/TS, golangci-lint + go vet + gofmt for Go, clippy + rustfmt for Rust, shellcheck for shell scripts.
63
+ - **Auto-detection.** Reads `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, and `.shellcheckrc` to pick the right tools. Auto-detected tools take priority over config.
64
+ - **Per-project config.** `.hermes/hermead.yaml` overrides global `~/.hermes/hermead.yaml`, which overrides built-in defaults.
65
+ - **Threshold system.** Set findings to `block`, `warn`, or `ignore` per severity level (lint warnings, type errors, security findings).
66
+ - **6 language runners.** Python, JavaScript/TypeScript, Go, Rust, Shell, and a Generic runner for config/script files (semgrep).
67
+ - **Graceful degredation.** Missing tools produce empty results. No crashes, no spurious errors. Use `which <tool>` to check availability.
68
+ - **Ignore patterns.** Skip `node_modules/`, `venv/`, build output dirs, and anything else you add.
69
+ - **Extra CLI args.** Pass custom flags per tool (e.g. `ruff: ["--line-length", "100"]`).
70
+
71
+ ## Configuration
72
+
73
+ ### Quick Start
74
+
75
+ Create `.hermes/hermead.yaml` at your project root:
76
+
77
+ ```yaml
78
+ python:
79
+ lint: ruff
80
+ type_check: mypy
81
+ formatter: ruff
82
+ security: semgrep
83
+
84
+ thresholds:
85
+ lint_warnings: warn
86
+ type_errors: block
87
+ security_high: block
88
+
89
+ ignore_paths:
90
+ - node_modules/**
91
+ - venv/**
92
+ - __pycache__/**
93
+ ```
94
+
95
+ ### Full Reference
96
+
97
+ ```yaml
98
+ # Per-language tool assignments (null = skip that check category)
99
+ python:
100
+ lint: ruff
101
+ type_check: mypy
102
+ formatter: ruff
103
+ security: semgrep
104
+
105
+ javascript:
106
+ lint: eslint
107
+ type_check: tsc
108
+ formatter: prettier
109
+ security: semgrep
110
+
111
+ go:
112
+ lint: golangci-lint
113
+ type_check: go vet
114
+ formatter: gofmt
115
+ security: gosec
116
+
117
+ rust:
118
+ lint: clippy
119
+ type_check: cargo check
120
+ formatter: rustfmt
121
+ security: cargo audit
122
+
123
+ shell:
124
+ lint: shellcheck
125
+ formatter: shfmt
126
+
127
+ generic:
128
+ lint: semgrep
129
+ security: semgrep
130
+
131
+ thresholds:
132
+ lint_warnings: warn # warn | block | ignore
133
+ type_errors: block
134
+ security_high: block
135
+ security_medium: warn
136
+ security_low: info
137
+
138
+ ignore_paths:
139
+ - node_modules/**
140
+ - venv/**
141
+ - .venv/**
142
+ - __pycache__/**
143
+ - .git/**
144
+ - dist/**
145
+ - build/**
146
+ - target/**
147
+ - .hermes/**
148
+
149
+ extra_args:
150
+ ruff: ["--line-length", "100"]
151
+ mypy: ["--strict"]
152
+ golangci-lint: ["--timeout", "5m"]
153
+ ```
154
+
155
+ ## Supported Languages
156
+
157
+ | Language | Extensions | Lint | Type Check | Format | Security |
158
+ |----------|-----------|------|------------|--------|----------|
159
+ | Python | `.py` | ruff | mypy | ruff / black | bandit / semgrep |
160
+ | JavaScript / TypeScript | `.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.cjs` | eslint | tsc | prettier | semgrep |
161
+ | Go | `.go` | golangci-lint | go vet | gofmt | gosec |
162
+ | Rust | `.rs` | clippy | cargo check | rustfmt | cargo audit |
163
+ | Shell | `.sh`, `.bash`, `.zsh`, `.fish` | shellcheck | — | shfmt | — |
164
+ | Generic | Dockerfile, Makefile, `.gitignore`, etc. | semgrep | — | — | semgrep |
165
+
166
+ ## Inline Reports
167
+
168
+ When HermeAd runs, findings appear in your session as structured results:
169
+
170
+ ```
171
+ ╔══════════════════════════════════════════════════════════╗
172
+ ║ HermeAd — lint results for src/main.py ║
173
+ ╠══════════════════════════════════════════════════════════╣
174
+ ║ tool: ruff severity: error line: 42 col: 5 ║
175
+ ║ F841 — local variable 'x' is assigned to but never ║
176
+ ║ used ║
177
+ ╠══════════════════════════════════════════════════════════╣
178
+ ║ 2 findings (1 error, 1 warning) — 0 blocked, 2 warned ║
179
+ ╚══════════════════════════════════════════════════════════╝
180
+ ```
181
+
182
+ Each result shows the tool, severity, source location, message, and rule code. Empty output means the file passed or the tool wasn't available.
183
+
184
+ ## Contributing
185
+
186
+ PRs welcome. HermeAd is a young project — the runner interface, result format, and hooks API are still finding their shape.
187
+
188
+ - Runners live in `hermead/runners/`. Each module self-registers via `register_runner(language, action, func)`.
189
+ - The hook dispatcher lives in `hermead/hooks.py`.
190
+ - Config loading lives in `hermead/config.py`.
191
+ - The registry lives in `hermead/runners/__init__.py`.
192
+
193
+ Before opening a PR, run the test suite:
194
+
195
+ ```bash
196
+ pip install -e ".[dev]"
197
+ pytest -v
198
+ ```
199
+
200
+ ## Plugin Architecture
201
+
202
+ HermeAd is a Hermes Agent plugin — no MCP server, no web server, no external API. It registers a single hook entry point in `pyproject.toml`:
203
+
204
+ ```toml
205
+ [project.entry-points."hermes_agent.plugins"]
206
+ hermead = "hermead:register"
207
+ ```
208
+
209
+ For more on building Hermes plugins, see the [Hermes Agent Plugin Docs](https://hermes-agent.nousresearch.com/docs/plugins).
210
+
211
+ ## License
212
+
213
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,197 @@
1
+ # HermeAd
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/hermead?color=blue)](https://pypi.org/project/hermead/)
4
+ [![License](https://img.shields.io/pypi/l/hermead?color=green)](LICENSE)
5
+ [![Python](https://img.shields.io/pypi/pyversions/hermead?color=blueviolet)](https://pypi.org/project/hermead/)
6
+
7
+ **HermeAd** is a Hermes Agent plugin that automatically runs linters, type checkers, formatters, and security scanners on project files after every `write_file` or `patch` tool call.
8
+
9
+ No manual commands. No context switching. Files get checked the moment you write them.
10
+
11
+ ## How It Works
12
+
13
+ HermeAd registers a single `post_tool_call` hook. After each file modification:
14
+
15
+ 1. Detects the file type from extension or filename
16
+ 2. Walks up the tree to find the project root (`.git`, `.hg`, or `.hermes` directory)
17
+ 3. Loads config: built-in defaults → global `~/.hermes/hermead.yaml` → per-project `.hermes/hermead.yaml`
18
+ 4. Auto-detects tooling from `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, `.shellcheckrc`
19
+ 5. Routes to the right runner for each check category (lint, type check, format, security)
20
+
21
+ Results appear inline in the session. Issues surface at the moment of introduction, not after a CI run.
22
+
23
+ ## Quick Install
24
+
25
+ ```bash
26
+ pip install hermead
27
+ ```
28
+
29
+ From source:
30
+
31
+ ```bash
32
+ git clone https://github.com/asimons81/hermead.git
33
+ cd hermead
34
+ pip install -e ".[dev]"
35
+ ```
36
+
37
+ Verify it loaded:
38
+
39
+ ```bash
40
+ hermes plugin list
41
+ # → hermead should appear
42
+ ```
43
+
44
+ ## Features
45
+
46
+ - **Zero-config for common projects.** Works out of the box with ruff + mypy for Python, eslint + prettier + tsc for JS/TS, golangci-lint + go vet + gofmt for Go, clippy + rustfmt for Rust, shellcheck for shell scripts.
47
+ - **Auto-detection.** Reads `pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, and `.shellcheckrc` to pick the right tools. Auto-detected tools take priority over config.
48
+ - **Per-project config.** `.hermes/hermead.yaml` overrides global `~/.hermes/hermead.yaml`, which overrides built-in defaults.
49
+ - **Threshold system.** Set findings to `block`, `warn`, or `ignore` per severity level (lint warnings, type errors, security findings).
50
+ - **6 language runners.** Python, JavaScript/TypeScript, Go, Rust, Shell, and a Generic runner for config/script files (semgrep).
51
+ - **Graceful degredation.** Missing tools produce empty results. No crashes, no spurious errors. Use `which <tool>` to check availability.
52
+ - **Ignore patterns.** Skip `node_modules/`, `venv/`, build output dirs, and anything else you add.
53
+ - **Extra CLI args.** Pass custom flags per tool (e.g. `ruff: ["--line-length", "100"]`).
54
+
55
+ ## Configuration
56
+
57
+ ### Quick Start
58
+
59
+ Create `.hermes/hermead.yaml` at your project root:
60
+
61
+ ```yaml
62
+ python:
63
+ lint: ruff
64
+ type_check: mypy
65
+ formatter: ruff
66
+ security: semgrep
67
+
68
+ thresholds:
69
+ lint_warnings: warn
70
+ type_errors: block
71
+ security_high: block
72
+
73
+ ignore_paths:
74
+ - node_modules/**
75
+ - venv/**
76
+ - __pycache__/**
77
+ ```
78
+
79
+ ### Full Reference
80
+
81
+ ```yaml
82
+ # Per-language tool assignments (null = skip that check category)
83
+ python:
84
+ lint: ruff
85
+ type_check: mypy
86
+ formatter: ruff
87
+ security: semgrep
88
+
89
+ javascript:
90
+ lint: eslint
91
+ type_check: tsc
92
+ formatter: prettier
93
+ security: semgrep
94
+
95
+ go:
96
+ lint: golangci-lint
97
+ type_check: go vet
98
+ formatter: gofmt
99
+ security: gosec
100
+
101
+ rust:
102
+ lint: clippy
103
+ type_check: cargo check
104
+ formatter: rustfmt
105
+ security: cargo audit
106
+
107
+ shell:
108
+ lint: shellcheck
109
+ formatter: shfmt
110
+
111
+ generic:
112
+ lint: semgrep
113
+ security: semgrep
114
+
115
+ thresholds:
116
+ lint_warnings: warn # warn | block | ignore
117
+ type_errors: block
118
+ security_high: block
119
+ security_medium: warn
120
+ security_low: info
121
+
122
+ ignore_paths:
123
+ - node_modules/**
124
+ - venv/**
125
+ - .venv/**
126
+ - __pycache__/**
127
+ - .git/**
128
+ - dist/**
129
+ - build/**
130
+ - target/**
131
+ - .hermes/**
132
+
133
+ extra_args:
134
+ ruff: ["--line-length", "100"]
135
+ mypy: ["--strict"]
136
+ golangci-lint: ["--timeout", "5m"]
137
+ ```
138
+
139
+ ## Supported Languages
140
+
141
+ | Language | Extensions | Lint | Type Check | Format | Security |
142
+ |----------|-----------|------|------------|--------|----------|
143
+ | Python | `.py` | ruff | mypy | ruff / black | bandit / semgrep |
144
+ | JavaScript / TypeScript | `.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.cjs` | eslint | tsc | prettier | semgrep |
145
+ | Go | `.go` | golangci-lint | go vet | gofmt | gosec |
146
+ | Rust | `.rs` | clippy | cargo check | rustfmt | cargo audit |
147
+ | Shell | `.sh`, `.bash`, `.zsh`, `.fish` | shellcheck | — | shfmt | — |
148
+ | Generic | Dockerfile, Makefile, `.gitignore`, etc. | semgrep | — | — | semgrep |
149
+
150
+ ## Inline Reports
151
+
152
+ When HermeAd runs, findings appear in your session as structured results:
153
+
154
+ ```
155
+ ╔══════════════════════════════════════════════════════════╗
156
+ ║ HermeAd — lint results for src/main.py ║
157
+ ╠══════════════════════════════════════════════════════════╣
158
+ ║ tool: ruff severity: error line: 42 col: 5 ║
159
+ ║ F841 — local variable 'x' is assigned to but never ║
160
+ ║ used ║
161
+ ╠══════════════════════════════════════════════════════════╣
162
+ ║ 2 findings (1 error, 1 warning) — 0 blocked, 2 warned ║
163
+ ╚══════════════════════════════════════════════════════════╝
164
+ ```
165
+
166
+ Each result shows the tool, severity, source location, message, and rule code. Empty output means the file passed or the tool wasn't available.
167
+
168
+ ## Contributing
169
+
170
+ PRs welcome. HermeAd is a young project — the runner interface, result format, and hooks API are still finding their shape.
171
+
172
+ - Runners live in `hermead/runners/`. Each module self-registers via `register_runner(language, action, func)`.
173
+ - The hook dispatcher lives in `hermead/hooks.py`.
174
+ - Config loading lives in `hermead/config.py`.
175
+ - The registry lives in `hermead/runners/__init__.py`.
176
+
177
+ Before opening a PR, run the test suite:
178
+
179
+ ```bash
180
+ pip install -e ".[dev]"
181
+ pytest -v
182
+ ```
183
+
184
+ ## Plugin Architecture
185
+
186
+ HermeAd is a Hermes Agent plugin — no MCP server, no web server, no external API. It registers a single hook entry point in `pyproject.toml`:
187
+
188
+ ```toml
189
+ [project.entry-points."hermes_agent.plugins"]
190
+ hermead = "hermead:register"
191
+ ```
192
+
193
+ For more on building Hermes plugins, see the [Hermes Agent Plugin Docs](https://hermes-agent.nousresearch.com/docs/plugins).
194
+
195
+ ## License
196
+
197
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,29 @@
1
+ """HermeAd: Hermes Agent plugin for project-level linting, type-checking, formatting, and security scanning.
2
+
3
+ Runs entirely through Hermes hook system. No external API, no MCP server, no web server.
4
+ """
5
+
6
+ from hermead.hooks import post_tool_call
7
+
8
+ __version__ = "0.1.0"
9
+
10
+
11
+ def register():
12
+ """Register HermeAd hooks with the Hermes Agent plugin system.
13
+
14
+ Returns the hook registration dict expected by Hermes Agent.
15
+ """
16
+ return {
17
+ "hooks": {
18
+ "post_tool_call": post_tool_call,
19
+ },
20
+ "metadata": {
21
+ "name": "hermead",
22
+ "version": __version__,
23
+ "description": (
24
+ "Post-tool-call hook that detects write_file/patch on project "
25
+ "files and auto-runs linting, type-checking, formatting, and "
26
+ "security checks."
27
+ ),
28
+ },
29
+ }
@@ -0,0 +1,121 @@
1
+ """Configuration loader for HermeAd.
2
+
3
+ Loads .hermes/hermead.yaml with per-project first, then global fallback.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import yaml
13
+
14
+ DEFAULT_CONFIG: dict[str, Any] = {
15
+ "python": {
16
+ "lint": "ruff",
17
+ "type_check": "mypy",
18
+ "formatter": "ruff",
19
+ "security": "semgrep",
20
+ },
21
+ "javascript": {
22
+ "lint": "eslint",
23
+ "type_check": "tsc",
24
+ "formatter": "prettier",
25
+ "security": "semgrep",
26
+ },
27
+ "go": {
28
+ "lint": "golangci-lint",
29
+ "type_check": "go vet",
30
+ "formatter": "gofmt",
31
+ "security": "gosec",
32
+ },
33
+ "rust": {
34
+ "lint": "clippy",
35
+ "type_check": "rustc",
36
+ "formatter": "rustfmt",
37
+ "security": "cargo audit",
38
+ },
39
+ "shell": {
40
+ "lint": "shellcheck",
41
+ "type_check": None,
42
+ "formatter": "shfmt",
43
+ "security": None,
44
+ },
45
+ "generic": {
46
+ "lint": "semgrep",
47
+ "type_check": None,
48
+ "formatter": None,
49
+ "security": "semgrep",
50
+ },
51
+ "thresholds": {
52
+ "lint_warnings": "warn",
53
+ "type_errors": "block",
54
+ "security_high": "block",
55
+ "security_medium": "warn",
56
+ },
57
+ "ignore_paths": [
58
+ "node_modules/**",
59
+ "venv/**",
60
+ ".venv/**",
61
+ "__pycache__/**",
62
+ ".git/**",
63
+ "dist/**",
64
+ "build/**",
65
+ "target/**",
66
+ ".hermes/**",
67
+ ],
68
+ "extra_args": {},
69
+ }
70
+
71
+
72
+ def find_project_root(path: str | Path | None = None) -> Path | None:
73
+ """Walk up from *path* (default: cwd) looking for a .git, .hg, or .hermes directory."""
74
+ start = Path(path or os.getcwd()).resolve()
75
+ for ancestor in [start] + list(start.parents):
76
+ if (ancestor / ".git").is_dir():
77
+ return ancestor
78
+ if (ancestor / ".hg").is_dir():
79
+ return ancestor
80
+ if (ancestor / ".hermes").is_dir():
81
+ return ancestor
82
+ return None
83
+
84
+
85
+ def load_hermead_config(path: str | Path | None = None) -> dict[str, Any]:
86
+ """Load the effective HermeAd config, merging global → per-project overrides.
87
+
88
+ 1. Start with DEFAULT_CONFIG.
89
+ 2. Overlay ``~/.hermes/hermead.yaml`` if it exists (global).
90
+ 3. Overlay ``<project-root>/.hermes/hermead.yaml`` if it exists (per-project).
91
+ """
92
+ config: dict[str, Any] = _deep_merge({}, DEFAULT_CONFIG)
93
+
94
+ # Global fallback
95
+ global_path = Path.home() / ".hermes" / "hermead.yaml"
96
+ if global_path.is_file():
97
+ with open(global_path, encoding="utf-8") as fh:
98
+ global_cfg: dict[str, Any] = yaml.safe_load(fh) or {}
99
+ config = _deep_merge(config, global_cfg)
100
+
101
+ # Per-project override
102
+ project_root = find_project_root(path)
103
+ if project_root is not None:
104
+ local_path = project_root / ".hermes" / "hermead.yaml"
105
+ if local_path.is_file():
106
+ with open(local_path, encoding="utf-8") as fh:
107
+ local_cfg: dict[str, Any] = yaml.safe_load(fh) or {}
108
+ config = _deep_merge(config, local_cfg)
109
+
110
+ return config
111
+
112
+
113
+ def _deep_merge(base: dict, overlay: dict) -> dict:
114
+ """Recursively merge *overlay* into *base* (keys in overlay win)."""
115
+ merged = dict(base)
116
+ for key, value in overlay.items():
117
+ if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
118
+ merged[key] = _deep_merge(merged[key], value)
119
+ else:
120
+ merged[key] = value
121
+ return merged