agentcheckup 0.4.0__tar.gz → 0.4.2__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.
Files changed (54) hide show
  1. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.github/workflows/publish.yml +2 -4
  2. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/CHANGELOG.md +43 -0
  3. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/PKG-INFO +6 -5
  4. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/README.md +5 -4
  5. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/README.zh-CN.md +2 -1
  6. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/docs/rules.md +4 -4
  7. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/pyproject.toml +1 -1
  8. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/conflicts.py +5 -3
  9. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/report.py +1 -1
  10. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/config_rules.py +2 -2
  11. agentcheckup-0.4.2/tests/test_registry.py +77 -0
  12. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/test_rules.py +1 -1
  13. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.gitattributes +0 -0
  14. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.github/ISSUE_TEMPLATE/rule_request.yml +0 -0
  17. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.github/pull_request_template.md +0 -0
  18. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.github/workflows/action-selftest.yml +0 -0
  19. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.github/workflows/ci.yml +0 -0
  20. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/.gitignore +0 -0
  21. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/CODE_OF_CONDUCT.md +0 -0
  22. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/CONTRIBUTING.md +0 -0
  23. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/LICENSE +0 -0
  24. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/Makefile +0 -0
  25. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/SECURITY.md +0 -0
  26. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/action.yml +0 -0
  27. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/conftest.py +0 -0
  28. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/docs/github-action.md +0 -0
  29. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/scripts/bootstrap.ps1 +0 -0
  30. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/scripts/bootstrap.sh +0 -0
  31. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/__init__.py +0 -0
  32. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/__main__.py +0 -0
  33. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/cli.py +0 -0
  34. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/discovery.py +0 -0
  35. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/engine.py +0 -0
  36. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/loader.py +0 -0
  37. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/models.py +0 -0
  38. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/__init__.py +0 -0
  39. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/base.py +0 -0
  40. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/docs.py +0 -0
  41. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/injection.py +0 -0
  42. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/naming.py +0 -0
  43. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/safety.py +0 -0
  44. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/rules/schema.py +0 -0
  45. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/src/agentcheckup/tokens.py +0 -0
  46. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/fixtures/bad_tools.json +0 -0
  47. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/fixtures/ci_config.json +0 -0
  48. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/fixtures/claude_desktop_config.json +0 -0
  49. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/fixtures/good_tools.json +0 -0
  50. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/fixtures/mock_server.py +0 -0
  51. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/test_cli.py +0 -0
  52. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/test_conflicts.py +0 -0
  53. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/test_discovery.py +0 -0
  54. {agentcheckup-0.4.0 → agentcheckup-0.4.2}/tests/test_scoring.py +0 -0
@@ -13,10 +13,8 @@ name: Publish
13
13
  # Environment : pypi
14
14
 
15
15
  on:
16
- # Re-enable the release trigger once the PyPI pending publisher exists:
17
- # on: release: types: [published]
18
- # Until then a release would run this and fail with "environment: MISSING",
19
- # which is noise on the repository home page.
16
+ release:
17
+ types: [published]
20
18
  workflow_dispatch:
21
19
 
22
20
  permissions:
@@ -9,6 +9,49 @@ new ids, and removing a rule is a breaking change.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.4.2] - 2026-11-07
13
+
14
+ Three claims the tool made that it had not earned. All were found by an external
15
+ review of the README and the report text, not by a bug report.
16
+
17
+ ### Fixed
18
+
19
+ - **`AGT201` no longer asserts what the client does.** The hint said "the client
20
+ keeps one and the other is unreachable". No client was ever tested; the
21
+ behaviour differs between clients and some namespace the tools instead. The
22
+ message now says the call can reach either one and that clients resolve it
23
+ differently, which is true without a test.
24
+ - **README no longer claims "No network calls, ever."** agentcheckup makes none
25
+ of its own, but `audit` starts every server in your config, and those processes
26
+ can do whatever they were written to do. The claim now names that boundary and
27
+ points at `--no-spawn`.
28
+ - **README said 27 config locations; there are 26.** Both languages.
29
+
30
+ ## [0.4.1] - 2026-11-07
31
+
32
+ Both fixes came out of an external fact-check of the project, not from a bug
33
+ report: the repository has no users yet, so nothing surfaced them in the field.
34
+
35
+ ### Fixed
36
+
37
+ - **`AGT105` was used by two unrelated rules.** "Server did not return a tool
38
+ list" (suite) and "Remote server uses plain HTTP" (config) shared the id, with
39
+ different severities. Rule ids are the tool's public API — people put them in
40
+ suppression lists — so two checks under one name is worse than either check
41
+ being wrong. The config rule is now `AGT108`; `AGT105` keeps the meaning every
42
+ document and the README already gave it.
43
+ - **README pointed the GitHub Action at `@v0.3.0`**, a tag that no longer exists.
44
+ Anyone copying the snippet got a resolution failure. Now `@v0.4.1`.
45
+ - `docs/rules.md` listed `AGT105` twice and described the suite rule as a
46
+ warning when it had been downgraded to info.
47
+
48
+ ### Added
49
+
50
+ - `tests/test_registry.py`: ids are unique across all three registries, well
51
+ formed, and — for every rule that constructs its own `Finding` — the severity
52
+ emitted matches the severity registered. That last check is the one that would
53
+ have caught the duplicate before it shipped.
54
+
12
55
  ## [0.4.0] - 2026-11-07
13
56
 
14
57
  Calibration release, driven by the first run against real servers rather than
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentcheckup
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Diagnose your agent setup: tool conflicts, risky config, context budget.
5
5
  Project-URL: Homepage, https://github.com/wanjiaqing/agentcheckup
6
6
  Project-URL: Issues, https://github.com/wanjiaqing/agentcheckup/issues
@@ -114,7 +114,7 @@ no network calls. It reads JSON, starts processes, prints text.
114
114
  agentcheckup
115
115
  ```
116
116
 
117
- Finds your configs automatically — 27 known locations across Claude Desktop,
117
+ Finds your configs automatically — 26 known locations across Claude Desktop,
118
118
  Claude Code (including its per-project `mcpServers`), Cursor, VS Code, Cline,
119
119
  Roo Code, Kilo Code, Windsurf, Zed, Codex CLI and Continue, plus project-local
120
120
  `.mcp.json` / `.cursor/mcp.json` / `.vscode/mcp.json` — starts each server,
@@ -149,7 +149,7 @@ The fastest way to keep a manifest from regressing, and the version you pin is
149
149
  the version you run — the action installs from its own checkout.
150
150
 
151
151
  ```yaml
152
- - uses: wanjiaqing/agentcheckup@v0.3.0
152
+ - uses: wanjiaqing/agentcheckup@v0.4.2
153
153
  with:
154
154
  config: .mcp.json
155
155
  fail-on: warning
@@ -238,8 +238,9 @@ rule. A report nobody can read is a report nobody acts on.
238
238
  to ignore the output.
239
239
  - **Degrade, don't die.** A server that will not start is one finding, not an
240
240
  aborted run.
241
- - **No network calls, ever.** Your config never leaves the machine. This is the
242
- one thing a commercial scanner structurally cannot promise.
241
+ - **No network calls of its own.** No telemetry, no API, no upload; your config
242
+ never leaves the machine. It does *start* the servers you configured, and those
243
+ can do whatever they were written to do — `--no-spawn` skips that entirely.
243
244
  - **Heuristics are labelled as heuristics.** `info` means "a human should
244
245
  decide". The score is a conversation starter, not a certification.
245
246
 
@@ -86,7 +86,7 @@ no network calls. It reads JSON, starts processes, prints text.
86
86
  agentcheckup
87
87
  ```
88
88
 
89
- Finds your configs automatically — 27 known locations across Claude Desktop,
89
+ Finds your configs automatically — 26 known locations across Claude Desktop,
90
90
  Claude Code (including its per-project `mcpServers`), Cursor, VS Code, Cline,
91
91
  Roo Code, Kilo Code, Windsurf, Zed, Codex CLI and Continue, plus project-local
92
92
  `.mcp.json` / `.cursor/mcp.json` / `.vscode/mcp.json` — starts each server,
@@ -121,7 +121,7 @@ The fastest way to keep a manifest from regressing, and the version you pin is
121
121
  the version you run — the action installs from its own checkout.
122
122
 
123
123
  ```yaml
124
- - uses: wanjiaqing/agentcheckup@v0.3.0
124
+ - uses: wanjiaqing/agentcheckup@v0.4.2
125
125
  with:
126
126
  config: .mcp.json
127
127
  fail-on: warning
@@ -210,8 +210,9 @@ rule. A report nobody can read is a report nobody acts on.
210
210
  to ignore the output.
211
211
  - **Degrade, don't die.** A server that will not start is one finding, not an
212
212
  aborted run.
213
- - **No network calls, ever.** Your config never leaves the machine. This is the
214
- one thing a commercial scanner structurally cannot promise.
213
+ - **No network calls of its own.** No telemetry, no API, no upload; your config
214
+ never leaves the machine. It does *start* the servers you configured, and those
215
+ can do whatever they were written to do — `--no-spawn` skips that entirely.
215
216
  - **Heuristics are labelled as heuristics.** `info` means "a human should
216
217
  decide". The score is a conversation starter, not a certification.
217
218
 
@@ -155,7 +155,8 @@ agentcheckup rules # 列出全部规则
155
155
  - **规则编号永久不变。** `AGT201` 的含义永远不变。
156
156
  - **每条发现都带修法。** 只说「这不好」的规则,训练用户忽略输出。
157
157
  - **降级而不是崩溃。** 起不来的 server 是一条发现,不是中断。
158
- - **永不联网。** 你的配置不会离开本机。这是商业扫描器结构上给不了的承诺。
158
+ - **自己不联网。** 没有遥测、没有 API、不上传任何东西,配置不会离开本机。
159
+ 但它会**启动你配置的 server**,那些进程会做什么取决于它们自己——`--no-spawn` 可完全跳过。
159
160
  - **启发式要标明是启发式。** `info` 的意思是「人来判断」。分数是讨论起点,不是认证。
160
161
 
161
162
  ## 这不是什么
@@ -163,9 +163,6 @@ also defeats the escaping the client already did.
163
163
  Granting the filesystem root gives the agent read-write access to everything. One
164
164
  prompt injection is then enough to reach SSH keys and browser profiles.
165
165
 
166
- ### AGT105 · warning · Remote server uses plain HTTP
167
- Tool calls carry your data, often your credentials.
168
-
169
166
  ### AGT106 · error · Config passes a permission-bypass flag
170
167
  `--dangerously-skip-permissions`, `--yolo`, `--allow-all`, `--no-sandbox`. These
171
168
  disable the only confirmation step between the model and your disk.
@@ -174,11 +171,14 @@ disable the only confirmation step between the model and your disk.
174
171
  Config files get committed, screenshotted and pasted into issues. Reference an
175
172
  env var instead.
176
173
 
174
+ ### AGT108 · warning · Remote server uses plain HTTP
175
+ Tool calls carry your data, often your credentials, over an unencrypted link.
176
+
177
177
  ### AGT105 · info · Server did not return a tool list
178
178
  The server failed to start, timed out, or is a remote `url` endpoint that cannot
179
179
  be introspected over stdio.
180
180
 
181
- This is a warning rather than an error on purpose: one broken server must not
181
+ This is info rather than an error on purpose: one broken server must not
182
182
  stop you auditing the other four. But an unreachable server is also an unaudited
183
183
  one — its tools were never checked for conflicts.
184
184
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentcheckup"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "Diagnose your agent setup: tool conflicts, risky config, context budget."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -187,7 +187,8 @@ def detect_conflicts(
187
187
  name_a = a.tool.name.lower()
188
188
  name_b = b.tool.name.lower()
189
189
 
190
- # Hard collision: identical names, different servers. One is unreachable.
190
+ # Hard collision: identical names, different servers. What the client
191
+ # does about it varies, so the message must not promise a specific outcome.
191
192
  if name_a == name_b:
192
193
  conflicts.append(
193
194
  Conflict(
@@ -201,8 +202,9 @@ def detect_conflicts(
201
202
  f"'{a.server}' and '{b.server}'."
202
203
  ),
203
204
  hint=(
204
- "Hard collision: the client keeps one and the other is "
205
- "unreachable. Rename one, or use a gateway that prefixes."
205
+ "Two servers expose the same name and clients resolve that "
206
+ "differently, so the same call can reach either one. Rename "
207
+ "one, or use a gateway that prefixes."
206
208
  ),
207
209
  )
208
210
  )
@@ -10,7 +10,7 @@ from . import rules as _rules
10
10
  from .engine import Report
11
11
  from .models import Finding, Severity
12
12
 
13
- __version__ = "0.4.0"
13
+ __version__ = "0.4.2"
14
14
 
15
15
  SARIF_LEVEL = {
16
16
  Severity.ERROR: "error",
@@ -132,12 +132,12 @@ def broad_fs_root(server: ServerConfig):
132
132
  return None
133
133
 
134
134
 
135
- @config_rule("AGT105", Severity.WARNING, "Remote server uses plain HTTP")
135
+ @config_rule("AGT108", Severity.WARNING, "Remote server uses plain HTTP")
136
136
  def insecure_url(server: ServerConfig):
137
137
  """Tool calls carry your data and often your credentials."""
138
138
  if server.url and server.url.lower().startswith("http://"):
139
139
  return Finding(
140
- rule="AGT105",
140
+ rule="AGT108",
141
141
  severity=Severity.WARNING,
142
142
  message=f"Server {server.name!r} uses an unencrypted http:// endpoint.",
143
143
  tool=server.name,
@@ -0,0 +1,77 @@
1
+ """Guards on the rule registry itself.
2
+
3
+ Rule ids are the public API of this tool: people put them in suppression lists,
4
+ CI thresholds and issue reports. A duplicate id silently gives two unrelated
5
+ checks the same name, which is worse than either check being wrong.
6
+
7
+ This file exists because that happened: `AGT105` was used for both "server did
8
+ not return a tool list" (suite) and "remote server uses plain HTTP" (config).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from pathlib import Path
15
+
16
+ import pytest
17
+
18
+ from agentcheckup.rules import CONFIG_RULES, SUITE_RULES, TOOL_RULES
19
+
20
+ SRC = Path(__file__).resolve().parents[1] / "src" / "agentcheckup"
21
+
22
+ ALL_RULES = [*TOOL_RULES, *SUITE_RULES, *CONFIG_RULES]
23
+
24
+
25
+ def test_rule_ids_are_unique():
26
+ ids = [rule.id for rule in ALL_RULES]
27
+ duplicates = {rule_id for rule_id in ids if ids.count(rule_id) > 1}
28
+ assert not duplicates, f"duplicate rule ids: {sorted(duplicates)}"
29
+
30
+
31
+ def test_rule_ids_are_well_formed():
32
+ for rule in ALL_RULES:
33
+ assert re.fullmatch(r"AGT\d{3}", rule.id), rule.id
34
+
35
+
36
+ def test_every_rule_has_a_title():
37
+ for rule in ALL_RULES:
38
+ assert rule.title.strip(), f"{rule.id} has no title"
39
+
40
+
41
+ def test_no_rule_is_registered_twice_across_groups():
42
+ """A tool rule and a config rule must not share an id either."""
43
+ groups = {
44
+ "tool": {r.id for r in TOOL_RULES},
45
+ "suite": {r.id for r in SUITE_RULES},
46
+ "config": {r.id for r in CONFIG_RULES},
47
+ }
48
+ for a, left in groups.items():
49
+ for b, right in groups.items():
50
+ if a >= b:
51
+ continue
52
+ overlap = left & right
53
+ assert not overlap, f"{a} and {b} share {sorted(overlap)}"
54
+
55
+
56
+ @pytest.mark.parametrize("rule", ALL_RULES, ids=lambda r: r.id)
57
+ def test_emitted_severity_matches_the_registry(rule):
58
+ """Catch a rule whose Finding says WARNING while the registry says INFO.
59
+
60
+ Without this, a severity change in one place silently disagrees with the
61
+ other, and the report and the score stop matching the docs.
62
+ """
63
+ pattern = re.compile(
64
+ r'rule="' + re.escape(rule.id) + r'",\s*severity=Severity\.(\w+)'
65
+ )
66
+ emitted: set[str] = set()
67
+ for path in SRC.rglob("*.py"):
68
+ for match in pattern.finditer(path.read_text(encoding="utf-8")):
69
+ emitted.add(match.group(1))
70
+
71
+ if not emitted:
72
+ pytest.skip("severity is passed indirectly for this rule")
73
+
74
+ expected = rule.severity.name
75
+ assert emitted == {expected}, (
76
+ f"{rule.id} is registered as {expected} but emits {sorted(emitted)}"
77
+ )
@@ -165,7 +165,7 @@ def test_invisible_characters_detected():
165
165
  def test_config_fixture_flags_expected_rules():
166
166
  report = lint_servers(servers_from_config(FIXTURES / "claude_desktop_config.json"))
167
167
  ids = rule_ids(report)
168
- for expected in ("AGT101", "AGT103", "AGT104", "AGT105", "AGT106", "AGT107"):
168
+ for expected in ("AGT101", "AGT103", "AGT104", "AGT106", "AGT107", "AGT108"):
169
169
  assert expected in ids, f"{expected} did not fire; got {sorted(ids)}"
170
170
 
171
171
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes