sensors-cli 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.
Files changed (70) hide show
  1. sensors_cli-0.1.0/PKG-INFO +159 -0
  2. sensors_cli-0.1.0/README.md +148 -0
  3. sensors_cli-0.1.0/pyproject.toml +80 -0
  4. sensors_cli-0.1.0/sensors/__init__.py +5 -0
  5. sensors_cli-0.1.0/sensors/cli.py +814 -0
  6. sensors_cli-0.1.0/sensors/config/__init__.py +29 -0
  7. sensors_cli-0.1.0/sensors/config/loader.py +282 -0
  8. sensors_cli-0.1.0/sensors/config/result_types.py +271 -0
  9. sensors_cli-0.1.0/sensors/config/schema.py +147 -0
  10. sensors_cli-0.1.0/sensors/events.py +16 -0
  11. sensors_cli-0.1.0/sensors/orchestration/__init__.py +0 -0
  12. sensors_cli-0.1.0/sensors/orchestration/control_server.py +272 -0
  13. sensors_cli-0.1.0/sensors/orchestration/orchestrator.py +390 -0
  14. sensors_cli-0.1.0/sensors/orchestration/sensors_processes.py +374 -0
  15. sensors_cli-0.1.0/sensors/persistence/__init__.py +21 -0
  16. sensors_cli-0.1.0/sensors/persistence/models.py +152 -0
  17. sensors_cli-0.1.0/sensors/persistence/state_manager.py +307 -0
  18. sensors_cli-0.1.0/sensors/runners/__init__.py +6 -0
  19. sensors_cli-0.1.0/sensors/runners/generic.py +493 -0
  20. sensors_cli-0.1.0/sensors/runners/parsers/__init__.py +86 -0
  21. sensors_cli-0.1.0/sensors/runners/parsers/base.py +41 -0
  22. sensors_cli-0.1.0/sensors/runners/parsers/default.py +168 -0
  23. sensors_cli-0.1.0/sensors/runners/parsers/depcruise.py +106 -0
  24. sensors_cli-0.1.0/sensors/runners/parsers/eslint.py +112 -0
  25. sensors_cli-0.1.0/sensors/runners/parsers/import_linter.py +119 -0
  26. sensors_cli-0.1.0/sensors/runners/parsers/pytest.py +132 -0
  27. sensors_cli-0.1.0/sensors/runners/parsers/pytest_cov.py +119 -0
  28. sensors_cli-0.1.0/sensors/runners/parsers/ruff.py +146 -0
  29. sensors_cli-0.1.0/sensors/runners/parsers/semgrep.py +111 -0
  30. sensors_cli-0.1.0/sensors/runners/parsers/stryker.py +168 -0
  31. sensors_cli-0.1.0/sensors/runners/parsers/stylelint.py +100 -0
  32. sensors_cli-0.1.0/sensors/runners/parsers/tsc.py +110 -0
  33. sensors_cli-0.1.0/sensors/runners/parsers/vitest.py +147 -0
  34. sensors_cli-0.1.0/sensors/runners/parsers/vitest_cov.py +149 -0
  35. sensors_cli-0.1.0/sensors/time_util.py +83 -0
  36. sensors_cli-0.1.0/sensors/tui/__init__.py +0 -0
  37. sensors_cli-0.1.0/sensors/tui/display.py +453 -0
  38. sensors_cli-0.1.0/sensors_cli.egg-info/PKG-INFO +159 -0
  39. sensors_cli-0.1.0/sensors_cli.egg-info/SOURCES.txt +68 -0
  40. sensors_cli-0.1.0/sensors_cli.egg-info/dependency_links.txt +1 -0
  41. sensors_cli-0.1.0/sensors_cli.egg-info/entry_points.txt +2 -0
  42. sensors_cli-0.1.0/sensors_cli.egg-info/requires.txt +4 -0
  43. sensors_cli-0.1.0/sensors_cli.egg-info/top_level.txt +1 -0
  44. sensors_cli-0.1.0/setup.cfg +4 -0
  45. sensors_cli-0.1.0/tests/test_cli_check.py +185 -0
  46. sensors_cli-0.1.0/tests/test_default_parser.py +174 -0
  47. sensors_cli-0.1.0/tests/test_depcruise.py +124 -0
  48. sensors_cli-0.1.0/tests/test_display.py +460 -0
  49. sensors_cli-0.1.0/tests/test_e2e_cli.py +480 -0
  50. sensors_cli-0.1.0/tests/test_e2e_runner_result_file.py +188 -0
  51. sensors_cli-0.1.0/tests/test_eslint.py +199 -0
  52. sensors_cli-0.1.0/tests/test_generic.py +214 -0
  53. sensors_cli-0.1.0/tests/test_generic_watch.py +193 -0
  54. sensors_cli-0.1.0/tests/test_import_linter.py +75 -0
  55. sensors_cli-0.1.0/tests/test_loader.py +58 -0
  56. sensors_cli-0.1.0/tests/test_orchestrator.py +149 -0
  57. sensors_cli-0.1.0/tests/test_pytest.py +65 -0
  58. sensors_cli-0.1.0/tests/test_pytest_cov.py +58 -0
  59. sensors_cli-0.1.0/tests/test_result_types.py +199 -0
  60. sensors_cli-0.1.0/tests/test_ruff.py +77 -0
  61. sensors_cli-0.1.0/tests/test_schema.py +138 -0
  62. sensors_cli-0.1.0/tests/test_semgrep.py +182 -0
  63. sensors_cli-0.1.0/tests/test_sensors_processes.py +190 -0
  64. sensors_cli-0.1.0/tests/test_state_manager.py +451 -0
  65. sensors_cli-0.1.0/tests/test_stryker.py +140 -0
  66. sensors_cli-0.1.0/tests/test_stylelint.py +155 -0
  67. sensors_cli-0.1.0/tests/test_time_util.py +78 -0
  68. sensors_cli-0.1.0/tests/test_tsc.py +116 -0
  69. sensors_cli-0.1.0/tests/test_vitest.py +70 -0
  70. sensors_cli-0.1.0/tests/test_vitest_cov.py +204 -0
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: sensors-cli
3
+ Version: 0.1.0
4
+ Summary: Continuous code quality monitoring sensors for coding agents
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pydantic>=2.0.0
8
+ Requires-Dist: pyyaml>=6.0
9
+ Requires-Dist: typer>=0.9.0
10
+ Requires-Dist: rich>=13.0.0
11
+
12
+ # Sensors Sidecar CLI
13
+
14
+ An **experimental** little "sidecar" system that can run a bunch of code quality sensors next to a coding agent. It can run linting, tests, and other checks on a schedule or in watch mode, persists structured state under `.sensors/` in the target codebase, and exposes a **`sensors`** CLI for running the service, checking the sensor status, or displaying the status in a human readable format.
15
+
16
+ Companion repository to this article: [Maintainability sensors for coding agents](https://martinfowler.com/articles/sensors-for-coding-agents.html)
17
+
18
+ Use `/_local-setup` skill to set it up on your machine (or use the `SKILL.md` file as documentation if you want to do it manually).
19
+
20
+ **Platform note:** The control plane uses **Unix domain sockets**, tested only on MacOS.
21
+
22
+ This tool was more or less vibe coded, though I did do regular refactorings with AI, and used the CLI to run sensors for this codebase ("eating my own dog food"). Check out [`.sensors/sensors-cli.sensors.yaml`](./.sensors/sensors-cli.sensors.yaml) to see the sensors used here. And look at [2026-06-15_modularity-review.md](./docs/reports/2026-06-15_modularity-review.md) for examples of why quick sensors like this can help with maintainability, but can only go so far when we don't spend much time on the larger code structure...
23
+
24
+ ## Commands
25
+
26
+ (CLI needs to be installed via `uv tool install`, see `/_local-setup` skill)
27
+
28
+ ```bash
29
+ # Is the sensors service running? (exit 0 = yes, 1 = no)
30
+ sensors status .
31
+
32
+ # All sensors start processes on this host (from /proc or ps)
33
+ sensors status --all
34
+
35
+ # Start the sensors
36
+ sensors start .
37
+
38
+ # Show the state
39
+ sensors show .
40
+
41
+ # Start the sensors and immediately jump into the display mode
42
+ sensors show --start .
43
+
44
+ # Agent-optimized runner results (failures included per runner); exit 0/1/2
45
+ sensors check .
46
+
47
+ sensors check . --runner eslint
48
+
49
+ # Save a score snapshot via RPC (needs a process to be running)
50
+ sensors snapshot .
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ The CLI looks for a `*.sensors.yaml` file under `.sensors/`.
56
+
57
+ There are some skills in this repo that document this setup more and that you can reuse:
58
+ - `.claude/skills/sensors_config-default` - a minimalist default setup that tries to determine one sensor example from your codebase. Use this to just get a taste
59
+ - `.claude/skills/sensors_config-typescript` - my full Typescript sensors setup
60
+ - `.claude/skills/sensors_config-python` - my full Python sensors setup
61
+
62
+ ## Parsers
63
+
64
+ The project comes with a bunch of output parsers for common tools, like `eslint` or `ruff`. If you want to use a tool as a sensor that is not yet supported, you either have to add a new parser to the code (and reinstall the CLI), or you can use the default parser.
65
+
66
+ ### Adding a new parser
67
+
68
+ This repo contains a skill that documents how to add a new parser [`.claude/skills/_new-parser/SKILL.md`](/.claude/skills/_new-parser/SKILL.md) in this repo for a guided template.
69
+
70
+ ## Default parser: Expected output format
71
+
72
+ Use `parser: default` in your runner config to connect any tool that can emit a JSON object in the specified schema. You have to build a script for your tool that turns the tool's output into this schema, and use that script in your sensor configuration.
73
+
74
+ This repo contains a skill that can help you write a wrapper script around your tool to transform your tool's data into the JSON schema [`.claude/skills/sensors_wrap-tool/SKILL.md`](/.claude/skills/sensors_wrap-tool/SKILL.md)
75
+
76
+ ### Schema
77
+
78
+ ```json
79
+ {
80
+ "findings": [
81
+ {
82
+ "message": "Unused variable 'x'",
83
+ "severity": "error",
84
+ "file": "src/foo.py",
85
+ "line": 42,
86
+ "column": 9,
87
+ "rule": "F841",
88
+ "context": "x is assigned but never used"
89
+ }
90
+ ],
91
+ "metrics": [
92
+ {
93
+ "key": "errorCount",
94
+ "label": "Errors",
95
+ "value": 1,
96
+ "direction": "less"
97
+ }
98
+ ],
99
+ "guidance": [
100
+ {
101
+ "rule": "F841",
102
+ "body": "Remove variable or use it."
103
+ }
104
+ ],
105
+ "score": {
106
+ "value": 1,
107
+ "direction": "less",
108
+ "description": "Issues reported by tool"
109
+ },
110
+ "success": false,
111
+ "summary": "1 issue",
112
+ "extra": {
113
+ "any": "parser-specific payload"
114
+ }
115
+ }
116
+ ```
117
+
118
+ This schema mirrors the `SensorReading` model used by built-in parsers. All fields are optional; missing values are derived as follows:
119
+
120
+ | Field | If absent or null |
121
+ |---|---|
122
+ | `findings` | treated as `[]` |
123
+ | `metrics` | treated as `[]` |
124
+ | `guidance` | treated as `[]` |
125
+ | `extra` | treated as `{}` |
126
+ | `success` | `true` when `findings` is empty, `false` otherwise |
127
+ | `summary` | `"N issue(s)"` / `"No issues"` derived from findings count |
128
+ | `score.value` | `len(findings)` |
129
+ | `score.direction` | `"less"` (lower is better) |
130
+ | `score.description` | `"Issues reported by tool"` |
131
+
132
+ `success`, `summary`, and `score` can be set explicitly and are used as-is. This allows tools that do not produce per-finding rows (for example, coverage checks) to report a score directly.
133
+
134
+
135
+ ### Example config
136
+
137
+ ```yaml
138
+ runners:
139
+ - name: my-custom-check
140
+ parser: default
141
+ enabled: true
142
+ mode: interval
143
+ command: some-tool | ./scripts/to-parser-default-format.sh
144
+ interval: 10000
145
+ ```
146
+
147
+ ### Minimal valid output
148
+
149
+ A tool that only reports a count without individual violations:
150
+
151
+ ```json
152
+ {"success": false, "summary": "Coverage 72% (threshold 80%)", "score": {"value": 72, "direction": "more"}}
153
+ ```
154
+
155
+ A tool with no issues:
156
+
157
+ ```json
158
+ {"findings": []}
159
+ ```
@@ -0,0 +1,148 @@
1
+ # Sensors Sidecar CLI
2
+
3
+ An **experimental** little "sidecar" system that can run a bunch of code quality sensors next to a coding agent. It can run linting, tests, and other checks on a schedule or in watch mode, persists structured state under `.sensors/` in the target codebase, and exposes a **`sensors`** CLI for running the service, checking the sensor status, or displaying the status in a human readable format.
4
+
5
+ Companion repository to this article: [Maintainability sensors for coding agents](https://martinfowler.com/articles/sensors-for-coding-agents.html)
6
+
7
+ Use `/_local-setup` skill to set it up on your machine (or use the `SKILL.md` file as documentation if you want to do it manually).
8
+
9
+ **Platform note:** The control plane uses **Unix domain sockets**, tested only on MacOS.
10
+
11
+ This tool was more or less vibe coded, though I did do regular refactorings with AI, and used the CLI to run sensors for this codebase ("eating my own dog food"). Check out [`.sensors/sensors-cli.sensors.yaml`](./.sensors/sensors-cli.sensors.yaml) to see the sensors used here. And look at [2026-06-15_modularity-review.md](./docs/reports/2026-06-15_modularity-review.md) for examples of why quick sensors like this can help with maintainability, but can only go so far when we don't spend much time on the larger code structure...
12
+
13
+ ## Commands
14
+
15
+ (CLI needs to be installed via `uv tool install`, see `/_local-setup` skill)
16
+
17
+ ```bash
18
+ # Is the sensors service running? (exit 0 = yes, 1 = no)
19
+ sensors status .
20
+
21
+ # All sensors start processes on this host (from /proc or ps)
22
+ sensors status --all
23
+
24
+ # Start the sensors
25
+ sensors start .
26
+
27
+ # Show the state
28
+ sensors show .
29
+
30
+ # Start the sensors and immediately jump into the display mode
31
+ sensors show --start .
32
+
33
+ # Agent-optimized runner results (failures included per runner); exit 0/1/2
34
+ sensors check .
35
+
36
+ sensors check . --runner eslint
37
+
38
+ # Save a score snapshot via RPC (needs a process to be running)
39
+ sensors snapshot .
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ The CLI looks for a `*.sensors.yaml` file under `.sensors/`.
45
+
46
+ There are some skills in this repo that document this setup more and that you can reuse:
47
+ - `.claude/skills/sensors_config-default` - a minimalist default setup that tries to determine one sensor example from your codebase. Use this to just get a taste
48
+ - `.claude/skills/sensors_config-typescript` - my full Typescript sensors setup
49
+ - `.claude/skills/sensors_config-python` - my full Python sensors setup
50
+
51
+ ## Parsers
52
+
53
+ The project comes with a bunch of output parsers for common tools, like `eslint` or `ruff`. If you want to use a tool as a sensor that is not yet supported, you either have to add a new parser to the code (and reinstall the CLI), or you can use the default parser.
54
+
55
+ ### Adding a new parser
56
+
57
+ This repo contains a skill that documents how to add a new parser [`.claude/skills/_new-parser/SKILL.md`](/.claude/skills/_new-parser/SKILL.md) in this repo for a guided template.
58
+
59
+ ## Default parser: Expected output format
60
+
61
+ Use `parser: default` in your runner config to connect any tool that can emit a JSON object in the specified schema. You have to build a script for your tool that turns the tool's output into this schema, and use that script in your sensor configuration.
62
+
63
+ This repo contains a skill that can help you write a wrapper script around your tool to transform your tool's data into the JSON schema [`.claude/skills/sensors_wrap-tool/SKILL.md`](/.claude/skills/sensors_wrap-tool/SKILL.md)
64
+
65
+ ### Schema
66
+
67
+ ```json
68
+ {
69
+ "findings": [
70
+ {
71
+ "message": "Unused variable 'x'",
72
+ "severity": "error",
73
+ "file": "src/foo.py",
74
+ "line": 42,
75
+ "column": 9,
76
+ "rule": "F841",
77
+ "context": "x is assigned but never used"
78
+ }
79
+ ],
80
+ "metrics": [
81
+ {
82
+ "key": "errorCount",
83
+ "label": "Errors",
84
+ "value": 1,
85
+ "direction": "less"
86
+ }
87
+ ],
88
+ "guidance": [
89
+ {
90
+ "rule": "F841",
91
+ "body": "Remove variable or use it."
92
+ }
93
+ ],
94
+ "score": {
95
+ "value": 1,
96
+ "direction": "less",
97
+ "description": "Issues reported by tool"
98
+ },
99
+ "success": false,
100
+ "summary": "1 issue",
101
+ "extra": {
102
+ "any": "parser-specific payload"
103
+ }
104
+ }
105
+ ```
106
+
107
+ This schema mirrors the `SensorReading` model used by built-in parsers. All fields are optional; missing values are derived as follows:
108
+
109
+ | Field | If absent or null |
110
+ |---|---|
111
+ | `findings` | treated as `[]` |
112
+ | `metrics` | treated as `[]` |
113
+ | `guidance` | treated as `[]` |
114
+ | `extra` | treated as `{}` |
115
+ | `success` | `true` when `findings` is empty, `false` otherwise |
116
+ | `summary` | `"N issue(s)"` / `"No issues"` derived from findings count |
117
+ | `score.value` | `len(findings)` |
118
+ | `score.direction` | `"less"` (lower is better) |
119
+ | `score.description` | `"Issues reported by tool"` |
120
+
121
+ `success`, `summary`, and `score` can be set explicitly and are used as-is. This allows tools that do not produce per-finding rows (for example, coverage checks) to report a score directly.
122
+
123
+
124
+ ### Example config
125
+
126
+ ```yaml
127
+ runners:
128
+ - name: my-custom-check
129
+ parser: default
130
+ enabled: true
131
+ mode: interval
132
+ command: some-tool | ./scripts/to-parser-default-format.sh
133
+ interval: 10000
134
+ ```
135
+
136
+ ### Minimal valid output
137
+
138
+ A tool that only reports a count without individual violations:
139
+
140
+ ```json
141
+ {"success": false, "summary": "Coverage 72% (threshold 80%)", "score": {"value": 72, "direction": "more"}}
142
+ ```
143
+
144
+ A tool with no issues:
145
+
146
+ ```json
147
+ {"findings": []}
148
+ ```
@@ -0,0 +1,80 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools.packages.find]
6
+ include = ["sensors*"]
7
+
8
+ [project]
9
+ name = "sensors-cli"
10
+ version = "0.1.0"
11
+ description = "Continuous code quality monitoring sensors for coding agents"
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ dependencies = [
15
+ "pydantic>=2.0.0",
16
+ "pyyaml>=6.0",
17
+ "typer>=0.9.0",
18
+ "rich>=13.0.0", # For better CLI output
19
+ ]
20
+
21
+ [project.scripts]
22
+ sensors = "sensors.cli:main"
23
+ [dependency-groups]
24
+ dev = [
25
+ "pytest>=9.0.3",
26
+ "pytest-asyncio>=0.21.0",
27
+ "pytest-cov>=7.0.0",
28
+ "black>=26.3.1",
29
+ "mypy>=1.0.0",
30
+ "ruff>=0.4.0",
31
+ "import-linter>=2.5.2",
32
+ "pyinstaller>=6.22.0",
33
+ ]
34
+
35
+ [tool.uv]
36
+ default-groups = ["dev"]
37
+
38
+ [tool.pytest.ini_options]
39
+ asyncio_mode = "auto"
40
+ testpaths = ["tests"]
41
+
42
+ [tool.black]
43
+ line-length = 100
44
+ target-version = ['py310']
45
+
46
+ [tool.ruff]
47
+ line-length = 100
48
+ target-version = "py310"
49
+
50
+ [tool.ruff.lint]
51
+ select = [
52
+ "E", # pycodestyle errors (indentation, spacing, etc.)
53
+ "F", # Pyflakes (unused imports, undefined names, redefinitions)
54
+ "I", # isort (import order and grouping)
55
+ "W", # pycodestyle warnings (style nits; see ignore list)
56
+ "UP", # pyupgrade (use modern Python syntax and builtins)
57
+ "B", # flake8-bugbear (likely bugs and questionable patterns)
58
+ "SIM", # flake8-simplify (redundant code that can be simplified)
59
+ "C901", # McCabe complexity (too many branches/paths in one function)
60
+ "PLR0913", # too many function/method parameters
61
+ "PLR0915", # too many statements in one function/method body
62
+ "PLR0917", # too many positional-only parameters
63
+ ]
64
+ ignore = [
65
+ "E501", # line too long (Black/format handles line length)
66
+ "W291", # trailing whitespace
67
+ ]
68
+
69
+ [tool.ruff.lint.mccabe]
70
+ max-complexity = 10 # threshold for C901 (default is 10)
71
+
72
+ [tool.ruff.lint.pylint]
73
+ max-args = 5 # threshold for PLR0913 (default is 5)
74
+ max-statements = 50 # threshold for PLR0915 (default is 50)
75
+
76
+ [tool.mypy]
77
+ python_version = "3.9"
78
+ warn_return_any = true
79
+ warn_unused_configs = true
80
+ disallow_untyped_defs = true
@@ -0,0 +1,5 @@
1
+ """
2
+ Sensors: Continuous code quality monitoring for coding agents.
3
+ """
4
+
5
+ __version__ = "0.1.0"