shockharness 0.1.0a1__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 (127) hide show
  1. shockharness-0.1.0a1/.gitignore +20 -0
  2. shockharness-0.1.0a1/.shockharness.toml +120 -0
  3. shockharness-0.1.0a1/CHANGELOG.md +69 -0
  4. shockharness-0.1.0a1/LICENSE +21 -0
  5. shockharness-0.1.0a1/PKG-INFO +197 -0
  6. shockharness-0.1.0a1/README.md +163 -0
  7. shockharness-0.1.0a1/STATUS.md +494 -0
  8. shockharness-0.1.0a1/docs/architecture.md +112 -0
  9. shockharness-0.1.0a1/docs/configuration.md +182 -0
  10. shockharness-0.1.0a1/docs/limitations.md +127 -0
  11. shockharness-0.1.0a1/docs/rules.md +313 -0
  12. shockharness-0.1.0a1/docs/shockharness_distribution_integration.md +168 -0
  13. shockharness-0.1.0a1/pyproject.toml +85 -0
  14. shockharness-0.1.0a1/schemas/report-v1.schema.json +127 -0
  15. shockharness-0.1.0a1/src/shockharness/__init__.py +13 -0
  16. shockharness-0.1.0a1/src/shockharness/__main__.py +5 -0
  17. shockharness-0.1.0a1/src/shockharness/analysis.py +262 -0
  18. shockharness-0.1.0a1/src/shockharness/api.py +68 -0
  19. shockharness-0.1.0a1/src/shockharness/cli.py +197 -0
  20. shockharness-0.1.0a1/src/shockharness/config.py +331 -0
  21. shockharness-0.1.0a1/src/shockharness/constants.py +53 -0
  22. shockharness-0.1.0a1/src/shockharness/defaults.toml +73 -0
  23. shockharness-0.1.0a1/src/shockharness/discovery.py +143 -0
  24. shockharness-0.1.0a1/src/shockharness/errors.py +42 -0
  25. shockharness-0.1.0a1/src/shockharness/findings.py +70 -0
  26. shockharness-0.1.0a1/src/shockharness/gate.py +102 -0
  27. shockharness-0.1.0a1/src/shockharness/globs.py +142 -0
  28. shockharness-0.1.0a1/src/shockharness/ir/__init__.py +1 -0
  29. shockharness-0.1.0a1/src/shockharness/ir/javascript.py +176 -0
  30. shockharness-0.1.0a1/src/shockharness/ir/javascript_catches.py +570 -0
  31. shockharness-0.1.0a1/src/shockharness/ir/javascript_defaults.py +874 -0
  32. shockharness-0.1.0a1/src/shockharness/ir/model.py +236 -0
  33. shockharness-0.1.0a1/src/shockharness/ir/python.py +192 -0
  34. shockharness-0.1.0a1/src/shockharness/ir/python_catches.py +441 -0
  35. shockharness-0.1.0a1/src/shockharness/ir/python_defaults.py +917 -0
  36. shockharness-0.1.0a1/src/shockharness/output/__init__.py +1 -0
  37. shockharness-0.1.0a1/src/shockharness/output/json.py +10 -0
  38. shockharness-0.1.0a1/src/shockharness/output/text.py +108 -0
  39. shockharness-0.1.0a1/src/shockharness/report.py +150 -0
  40. shockharness-0.1.0a1/src/shockharness/rules/__init__.py +1 -0
  41. shockharness-0.1.0a1/src/shockharness/rules/defaults.py +279 -0
  42. shockharness-0.1.0a1/src/shockharness/rules/exceptions.py +304 -0
  43. shockharness-0.1.0a1/src/shockharness/rules/magic_numbers.py +55 -0
  44. shockharness-0.1.0a1/src/shockharness/rules/registry.py +736 -0
  45. shockharness-0.1.0a1/src/shockharness/schema.py +246 -0
  46. shockharness-0.1.0a1/src/shockharness/source.py +52 -0
  47. shockharness-0.1.0a1/src/shockharness/suppressions.py +191 -0
  48. shockharness-0.1.0a1/src/shockharness/version.py +3 -0
  49. shockharness-0.1.0a1/tests/__init__.py +0 -0
  50. shockharness-0.1.0a1/tests/cli/__init__.py +0 -0
  51. shockharness-0.1.0a1/tests/cli/test_cli.py +296 -0
  52. shockharness-0.1.0a1/tests/conftest.py +98 -0
  53. shockharness-0.1.0a1/tests/fixtures/expected.json +3046 -0
  54. shockharness-0.1.0a1/tests/fixtures/javascript/clean.js +3 -0
  55. shockharness-0.1.0a1/tests/fixtures/javascript/common.cjs +1 -0
  56. shockharness-0.1.0a1/tests/fixtures/javascript/def_destructuring.js +21 -0
  57. shockharness-0.1.0a1/tests/fixtures/javascript/def_exception_default.js +74 -0
  58. shockharness-0.1.0a1/tests/fixtures/javascript/def_logical_or.js +43 -0
  59. shockharness-0.1.0a1/tests/fixtures/javascript/def_nullish.js +40 -0
  60. shockharness-0.1.0a1/tests/fixtures/javascript/def_parameters.js +38 -0
  61. shockharness-0.1.0a1/tests/fixtures/javascript/def_process_env.js +15 -0
  62. shockharness-0.1.0a1/tests/fixtures/javascript/exc_async_await.js +35 -0
  63. shockharness-0.1.0a1/tests/fixtures/javascript/exc_boundary_catch.js +87 -0
  64. shockharness-0.1.0a1/tests/fixtures/javascript/exc_nested_catches.js +28 -0
  65. shockharness-0.1.0a1/tests/fixtures/javascript/exc_promise_catch.js +32 -0
  66. shockharness-0.1.0a1/tests/fixtures/javascript/exc_rethrow.js +40 -0
  67. shockharness-0.1.0a1/tests/fixtures/javascript/exc_return_fallback.js +58 -0
  68. shockharness-0.1.0a1/tests/fixtures/javascript/exc_untyped_catch.js +32 -0
  69. shockharness-0.1.0a1/tests/fixtures/javascript/jsx.js +2 -0
  70. shockharness-0.1.0a1/tests/fixtures/javascript/magic_basic.js +7 -0
  71. shockharness-0.1.0a1/tests/fixtures/javascript/magic_encodings.js +3 -0
  72. shockharness-0.1.0a1/tests/fixtures/javascript/magic_negatives.js +10 -0
  73. shockharness-0.1.0a1/tests/fixtures/javascript/module.mjs +2 -0
  74. shockharness-0.1.0a1/tests/fixtures/javascript/scopes.js +9 -0
  75. shockharness-0.1.0a1/tests/fixtures/javascript/strings_regex.js +6 -0
  76. shockharness-0.1.0a1/tests/fixtures/javascript/suppressions.js +4 -0
  77. shockharness-0.1.0a1/tests/fixtures/javascript/syntax_error.js +3 -0
  78. shockharness-0.1.0a1/tests/fixtures/javascript/unicode.js +2 -0
  79. shockharness-0.1.0a1/tests/fixtures/python/clean.py +15 -0
  80. shockharness-0.1.0a1/tests/fixtures/python/def_dataclasses.py +30 -0
  81. shockharness-0.1.0a1/tests/fixtures/python/def_environment.py +23 -0
  82. shockharness-0.1.0a1/tests/fixtures/python/def_exception_default.py +78 -0
  83. shockharness-0.1.0a1/tests/fixtures/python/def_mapping_get.py +55 -0
  84. shockharness-0.1.0a1/tests/fixtures/python/def_nullish.py +28 -0
  85. shockharness-0.1.0a1/tests/fixtures/python/def_parameters.py +59 -0
  86. shockharness-0.1.0a1/tests/fixtures/python/def_truthiness.py +42 -0
  87. shockharness-0.1.0a1/tests/fixtures/python/exc_bare.py +12 -0
  88. shockharness-0.1.0a1/tests/fixtures/python/exc_broad.py +26 -0
  89. shockharness-0.1.0a1/tests/fixtures/python/exc_except_star.py +10 -0
  90. shockharness-0.1.0a1/tests/fixtures/python/exc_logging_only.py +49 -0
  91. shockharness-0.1.0a1/tests/fixtures/python/exc_multiple_operations.py +60 -0
  92. shockharness-0.1.0a1/tests/fixtures/python/exc_nested.py +29 -0
  93. shockharness-0.1.0a1/tests/fixtures/python/exc_rethrow.py +44 -0
  94. shockharness-0.1.0a1/tests/fixtures/python/exc_rethrow_transparent.py +30 -0
  95. shockharness-0.1.0a1/tests/fixtures/python/exc_return_fallback.py +62 -0
  96. shockharness-0.1.0a1/tests/fixtures/python/exc_scoped_timeout.py +55 -0
  97. shockharness-0.1.0a1/tests/fixtures/python/exc_tuple.py +22 -0
  98. shockharness-0.1.0a1/tests/fixtures/python/exc_unresolved.py +91 -0
  99. shockharness-0.1.0a1/tests/fixtures/python/magic_annotations.py +8 -0
  100. shockharness-0.1.0a1/tests/fixtures/python/magic_basic.py +13 -0
  101. shockharness-0.1.0a1/tests/fixtures/python/magic_booleans.py +5 -0
  102. shockharness-0.1.0a1/tests/fixtures/python/magic_encodings.py +3 -0
  103. shockharness-0.1.0a1/tests/fixtures/python/magic_negatives.py +9 -0
  104. shockharness-0.1.0a1/tests/fixtures/python/magic_scopes.py +32 -0
  105. shockharness-0.1.0a1/tests/fixtures/python/magic_strings_not_numbers.py +5 -0
  106. shockharness-0.1.0a1/tests/fixtures/python/suppressions.py +9 -0
  107. shockharness-0.1.0a1/tests/fixtures/python/syntax_error.py +2 -0
  108. shockharness-0.1.0a1/tests/fixtures/python/unicode.py +3 -0
  109. shockharness-0.1.0a1/tests/property/__init__.py +0 -0
  110. shockharness-0.1.0a1/tests/property/test_properties.py +159 -0
  111. shockharness-0.1.0a1/tests/test_self_compliance.py +21 -0
  112. shockharness-0.1.0a1/tests/unit/__init__.py +0 -0
  113. shockharness-0.1.0a1/tests/unit/test_analysis.py +127 -0
  114. shockharness-0.1.0a1/tests/unit/test_config.py +221 -0
  115. shockharness-0.1.0a1/tests/unit/test_defaults.py +626 -0
  116. shockharness-0.1.0a1/tests/unit/test_discovery.py +110 -0
  117. shockharness-0.1.0a1/tests/unit/test_exceptions.py +473 -0
  118. shockharness-0.1.0a1/tests/unit/test_gate_and_report.py +222 -0
  119. shockharness-0.1.0a1/tests/unit/test_globs.py +76 -0
  120. shockharness-0.1.0a1/tests/unit/test_golden_fixtures.py +49 -0
  121. shockharness-0.1.0a1/tests/unit/test_javascript_adapter.py +311 -0
  122. shockharness-0.1.0a1/tests/unit/test_magic001.py +109 -0
  123. shockharness-0.1.0a1/tests/unit/test_python_adapter.py +280 -0
  124. shockharness-0.1.0a1/tests/unit/test_registry.py +69 -0
  125. shockharness-0.1.0a1/tests/unit/test_schema.py +74 -0
  126. shockharness-0.1.0a1/tests/unit/test_source.py +38 -0
  127. shockharness-0.1.0a1/tests/unit/test_suppressions.py +99 -0
@@ -0,0 +1,20 @@
1
+ # Environments
2
+ .venv/
3
+ venv/
4
+
5
+ # IDE
6
+ .idea/
7
+ .vscode/
8
+
9
+ # Python
10
+ __pycache__/
11
+ *.py[cod]
12
+ *.egg-info/
13
+ dist/
14
+ build/
15
+ .pytest_cache/
16
+ .hypothesis/
17
+ .ruff_cache/
18
+
19
+ # macOS
20
+ .DS_Store
@@ -0,0 +1,120 @@
1
+ # ShockHarness analyzing itself. Every exemption below is deliberate and reported.
2
+ # See agent_code_integrity_project_spec.md section 15.5 for the bootstrapping policy.
3
+
4
+ [tool.shockharness]
5
+ profile = "strict"
6
+ include = ["src/**/*.py", "tests/**/*.py"]
7
+ exclude = ["**/.venv/**", "**/node_modules/**", "**/dist/**", "**/build/**", "tests/fixtures/**"]
8
+ parse_errors = "fail"
9
+
10
+ [tool.shockharness.rules.magic_numbers]
11
+ enabled = true
12
+ allowed_values = [0, 1]
13
+ allow_unary_negative_one = false
14
+ exclude_configuration_files = false
15
+
16
+ [tool.shockharness.rules.exceptions]
17
+ enabled = true
18
+ mode = "boundary_whitelist"
19
+
20
+ # Declared boundaries where the tool meets the outside world. Each converts the
21
+ # named failure into a typed analyzer diagnostic; none may swallow it.
22
+ [[tool.shockharness.rules.exceptions.boundaries]]
23
+ path = "src/shockharness/config.py"
24
+ language = "python"
25
+ exception = "OSError"
26
+ reason = "Reading a configuration file is I/O; failure becomes a ConfigError (exit 2)"
27
+
28
+ [[tool.shockharness.rules.exceptions.boundaries]]
29
+ path = "src/shockharness/config.py"
30
+ language = "python"
31
+ exception = "tomllib.TOMLDecodeError"
32
+ reason = "Config file content is external input; decode failure becomes a ConfigError (exit 2)"
33
+
34
+ [[tool.shockharness.rules.exceptions.boundaries]]
35
+ path = "src/shockharness/config.py"
36
+ language = "python"
37
+ exception = "UnicodeDecodeError"
38
+ reason = "Config file bytes are external input; non-UTF-8 becomes a ConfigError (exit 2)"
39
+
40
+ [[tool.shockharness.rules.exceptions.boundaries]]
41
+ path = "src/shockharness/analysis.py"
42
+ language = "python"
43
+ exception = "OSError"
44
+ reason = "Reading analyzed files is I/O; failure becomes a per-file read_error status, never a pass"
45
+
46
+ [[tool.shockharness.rules.exceptions.boundaries]]
47
+ path = "src/shockharness/analysis.py"
48
+ language = "python"
49
+ exception = "UnicodeDecodeError"
50
+ reason = "Analyzed source is external bytes; undecodable input becomes a decode_error file status"
51
+
52
+ [[tool.shockharness.rules.exceptions.boundaries]]
53
+ path = "src/shockharness/ir/python.py"
54
+ language = "python"
55
+ exception = "SyntaxError"
56
+ reason = "ast.parse and tokenize on external source; failure becomes a ParseFailure fact feeding the parse_errors gate"
57
+
58
+ [[tool.shockharness.rules.exceptions.boundaries]]
59
+ path = "src/shockharness/ir/python.py"
60
+ language = "python"
61
+ exception = "ValueError"
62
+ reason = "ast.parse raises ValueError for NUL bytes on 3.11 and tokenize raises UnicodeDecodeError (a ValueError) on some CR sequences; both become a ParseFailure"
63
+
64
+ [[tool.shockharness.rules.exceptions.boundaries]]
65
+ path = "src/shockharness/ir/python.py"
66
+ language = "python"
67
+ exception = "tokenize.TokenError"
68
+ reason = "Comment extraction tokenizes external source; failure becomes a ParseFailure fact"
69
+
70
+ [[tool.shockharness.rules.exceptions.boundaries]]
71
+ path = "src/shockharness/cli.py"
72
+ language = "python"
73
+ exception = "OSError"
74
+ reason = "Writing the report or init file to a user path is I/O; failure becomes a UsageError (exit 2)"
75
+
76
+ [[tool.shockharness.rules.exceptions.boundaries]]
77
+ path = "src/shockharness/cli.py"
78
+ language = "python"
79
+ exception = "shockharness.errors.ShockHarnessError"
80
+ reason = "Process boundary: typed configuration and usage errors print one line and exit 2"
81
+
82
+ [[tool.shockharness.rules.exceptions.boundaries]]
83
+ path = "src/shockharness/cli.py"
84
+ language = "python"
85
+ exception = "Exception"
86
+ reason = "Process boundary: an unexpected internal failure prints its full traceback and exits 3 instead of Python's default 1; nothing is hidden"
87
+
88
+ [tool.shockharness.rules.defaults]
89
+ enabled = true
90
+ function_parameters = "forbid"
91
+ dataclass_fields = "forbid"
92
+ missing_value_fallbacks = "forbid"
93
+ truthiness_fallbacks = "review"
94
+ nullish_fallbacks = "forbid"
95
+ configuration_fallbacks = "forbid"
96
+
97
+ [tool.shockharness.rules.magic_strings]
98
+ enabled = true
99
+ mode = "review"
100
+
101
+ [tool.shockharness.complexity]
102
+ enabled = true
103
+ cognitive_warn = 15
104
+ cyclomatic_warn = 15
105
+ max_nesting_warn = 4
106
+
107
+ [tool.shockharness.gates]
108
+ new_hard_violations = "fail"
109
+ legacy = "baseline"
110
+ complexity_regressions = "warn"
111
+
112
+ [[tool.shockharness.overrides]]
113
+ paths = ["src/shockharness/constants.py"]
114
+ disable = ["MAGIC001"]
115
+ reason = "Single designated module holding every numeric constant the tool needs (exit codes, schema version, caps)"
116
+
117
+ [[tool.shockharness.overrides]]
118
+ paths = ["tests/**"]
119
+ disable = ["MAGIC001", "DEF001"]
120
+ reason = "Test code encodes literal expected outputs and pytest fixture signatures"
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ All notable changes to ShockHarness. Format follows Keep a Changelog; versions
4
+ follow Semantic Versioning. The report `schema_version` is tracked separately.
5
+
6
+ ## [Unreleased]
7
+
8
+ ### Added
9
+ - Milestone A: package skeleton, MIT license, configuration schema with full
10
+ shape and semantic validation, precedence (`--config` > `.shockharness.toml` >
11
+ `pyproject.toml` > bundled defaults), rule registry with every specified rule
12
+ ID, diagnostic and report models, `scan`, `config validate`, `config effective`,
13
+ `rules list`, `rules explain`, `init`, exit-code contract, GitHub Actions matrix.
14
+ - Milestone B: Python (`ast`) and JavaScript (tree-sitter) adapters with
15
+ byte-to-character column mapping, explicit parse-failure propagation,
16
+ `MAGIC001` (value-based 0/1, folded unary sign, annotations flagged),
17
+ `SUPP001`/`SUPP002` inline suppression checks, per-path overrides,
18
+ configuration-file exemption, JSON report (`schema_version` 1) and text output.
19
+ - Milestone C: catch facts in the IR (exception identities with
20
+ exact/approximate/unresolved confidence, handler body classification,
21
+ try-body operation attribution) for Python `except`/`except*` and JavaScript
22
+ `catch`, `.catch(handler)`, `.then(ok, handler)`; `EXC001` forbidden catch
23
+ (boundary whitelist, fail-closed on bare/unresolved identities, JavaScript
24
+ instanceof-guard convention), `EXC002` suppressed or converted exception,
25
+ `EXC003` broad try scope; `rules.exceptions.transparent_rethrow` (default
26
+ false); boundary `exception` names validated as dotted class names; every
27
+ boundary and `transparent_rethrow` reported under `config.deviations`.
28
+ - Milestone D: default facts in the IR (`DefaultFact`: family, kind, subject,
29
+ fallback, constant-shapedness, syntactic context, confidence) extracted by
30
+ `ir/python_defaults.py` and `ir/javascript_defaults.py`; `DEF001` parameter
31
+ and dataclass-field defaults, `DEF002` mapping/attribute/property fallbacks
32
+ (plus the implicit `None` of `.get(key)` as a review finding), `DEF003`
33
+ truthiness fallbacks (review by default; Boolean logic excluded), `DEF004`
34
+ nullish fallbacks, `DEF005` exception-to-default (literal returns and
35
+ one-step static traces to module constants and one-return helpers), `DEF006`
36
+ configuration fallbacks (`os.getenv`/`os.environ`/`process.env` in every
37
+ spelling); new `rules.defaults` keys `dataclass_fields`, `nullish_fallbacks`,
38
+ `configuration_fallbacks` (all `forbid`); loosened modes reported under
39
+ `config.deviations`; 13 golden fixtures and 25 unit tests; `safe_autofix` is
40
+ false for every finding.
41
+
42
+ ### Changed
43
+ - `EXC002` no longer reports the `return_fallback` classification while `DEF005`
44
+ is active: the handler is reported once, as `DEF005`, with the EXC002
45
+ classification named in the message. Disabling `DEF005` restores the EXC002
46
+ finding. Golden expectations for the eleven affected handlers changed rule ID
47
+ only.
48
+ - The tool's own code was brought under `DEF002`/`DEF004`: three `setdefault`
49
+ accumulators became explicit membership checks, the report's
50
+ `discovery`/`explicit_targets_excluded` fields are built with an explicit
51
+ branch, and nineteen `x if x is not None else v` expressions on parser fields
52
+ became either explicit branches (genuinely optional values: import aliases,
53
+ `from . import`, source segments) or assertions of grammar invariants (fields
54
+ tree-sitter and `ast` always populate). A Python `SyntaxError` position is now
55
+ recorded only when the interpreter reports both a line and a column; before,
56
+ a missing column was silently replaced by column 1.
57
+ - The arities that distinguish `getattr(o, a, v)` and `m.get(k, v)` from plain
58
+ lookups are named in `constants.py`.
59
+ - The tool's own boundaries were narrowed to satisfy `EXC003`: config decoding
60
+ and TOML parsing are two `try` blocks, analyzed-file decoding is bound before
61
+ the result is returned, and comment tokenization materializes the token list
62
+ in the one protected call. Behaviour is unchanged.
63
+
64
+ ### Dependencies
65
+ - `tree-sitter` 0.25.x (MIT) and `tree-sitter-javascript` 0.25.x (MIT). Pinned
66
+ as a tested pair; language ABI 15. `tree-sitter` 0.26.0 is excluded: with
67
+ CPython 3.14 it segfaults when walking trees nested deeper than about 200
68
+ levels (CPython 3.13 with 0.26.0 and CPython 3.14 with 0.25.2 are both fine).
69
+ The 300-deep nesting test in `tests/unit/test_javascript_adapter.py` guards this.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Lahore
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.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.5
2
+ Name: shockharness
3
+ Version: 0.1.0a1
4
+ Summary: Strict static-analysis quality gate for Python and JavaScript: no silent failures, no implicit defaults, no magic numbers.
5
+ Project-URL: Homepage, https://github.com/evolvingstuff/ShockHarness
6
+ Project-URL: Repository, https://github.com/evolvingstuff/ShockHarness
7
+ Project-URL: Issues, https://github.com/evolvingstuff/ShockHarness/issues
8
+ Project-URL: Changelog, https://github.com/evolvingstuff/ShockHarness/blob/main/CHANGELOG.md
9
+ Author: Thomas Lahore
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ci,exceptions,linter,magic-numbers,quality-gate,static-analysis
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: JavaScript
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: tree-sitter-javascript<0.26,>=0.25
27
+ Requires-Dist: tree-sitter<0.26,>=0.25.1
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == 'dev'
30
+ Requires-Dist: hypothesis>=6; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Requires-Dist: ruff>=0.6; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # ShockHarness
36
+
37
+ A strict, deterministic static-analysis quality gate for Python and JavaScript.
38
+ Its one idea: unexpected failures, missing information, and unexplained
39
+ decisions must not silently become apparently valid program behavior.
40
+
41
+ Three default-on hard policies:
42
+
43
+ 1. **Exception transparency.** No catch except at narrow, explicitly approved
44
+ external boundaries. No catch-and-continue, no success-shaped fallbacks.
45
+ 2. **No implicit defaults.** No fallback values for missing required data, no
46
+ function-parameter defaults in strict mode.
47
+ 3. **No magic numbers.** Every numeric literal in executable code must be
48
+ exactly `0` or `1`. `range(2)` is a violation. The tool invents no exemptions.
49
+
50
+ Plus a separate, advisory complexity family (cyclomatic, cognitive, nesting,
51
+ NPath, call graph, churn). Hard violations are never averaged into a score.
52
+
53
+ ShockHarness complements Ruff and ESLint; it is not a formatter or a general
54
+ linter. It runs offline, needs no LLM, and never uploads source.
55
+
56
+ ## Status
57
+
58
+ Pre-release. Milestones A (skeleton and contracts), B (parsers and
59
+ `MAGIC001`), C (exception rules `EXC001`-`EXC003`) and D (default and fallback
60
+ rules `DEF001`-`DEF006`) are implemented. `MAGIC002`, complexity, baselines,
61
+ SARIF, and agent skills are registered but **not yet implemented**; every
62
+ report lists them under `rules_not_implemented` so a clean run never implies
63
+ coverage the tool does not have. See `STATUS.md` for exact evidence.
64
+
65
+ Implemented rules:
66
+
67
+ | ID | What it reports | Default |
68
+ |---|---|---|
69
+ | `MAGIC001` | numeric literal other than `0`/`1` in executable code | error |
70
+ | `EXC001` | catch outside an approved boundary (bare, broad, unresolved, untyped) | error |
71
+ | `EXC002` | handler that swallows, only logs, or replaces the exception without its cause | error |
72
+ | `EXC003` | approved `try` wrapping more than the one sanctioned operation | error |
73
+ | `DEF001` | parameter default, dataclass field default / `default_factory` | error |
74
+ | `DEF002` | `m.get(k, v)`, `getattr(o, a, v)`, `o?.x ?? v`, `const {x = v} = o`; `.get(k)` implicit `None` as review | error |
75
+ | `DEF003` | `x or v`, `x || v` outside Boolean logic | review |
76
+ | `DEF004` | `x ?? v`, `x if x is not None else v` | error |
77
+ | `DEF005` | handler that returns a default (literal, module constant, one-return helper) | error |
78
+ | `DEF006` | `os.getenv("X", v)`, `process.env.X || v`, `const { X = v } = process.env` | error |
79
+ | `SUPP001` / `SUPP002` | malformed / unused suppression comment | error / warning |
80
+
81
+ ## Install
82
+
83
+ Requires Python 3.11+. Runtime dependencies are `tree-sitter` and
84
+ `tree-sitter-javascript` only.
85
+
86
+ ```bash
87
+ pip install shockharness
88
+ ```
89
+
90
+ From a checkout:
91
+
92
+ ```bash
93
+ uv pip install -e ".[dev]"
94
+ ```
95
+
96
+ ## Use
97
+
98
+ ```bash
99
+ shockharness init # writes .shockharness.toml (strict profile)
100
+ shockharness config validate # exit 0 if the configuration is valid
101
+ shockharness config effective # merged config with per-key origins
102
+ shockharness scan src/ # human-readable report, exit = gate status
103
+ shockharness scan src/ --format json --output report.json
104
+ shockharness rules list
105
+ shockharness rules explain MAGIC001
106
+ ```
107
+
108
+ Exit codes: `0` gates pass, `1` policy violations, `2` invalid configuration or
109
+ invocation, `3` internal error or (with `parse_errors = "fail"`) a file that
110
+ could not be parsed. A report is still produced when the gate fails.
111
+
112
+ Example:
113
+
114
+ ```text
115
+ src/client.py:12:11: MAGIC001 error: Numeric literal 30 has no permitted justification in executable code [timeout = 30]
116
+ src/client.py:15:24: DEF001 error: parameter 'port' of connect() has a default value: 5432 [def connect(host, port=5432):]
117
+ src/client.py:18:15: DEF006 error: environment variable "RETRIES" falls back to "3"; os.getenv(...) substitutes a fallback when environment variable "RETRIES" is unset (getenv) [retries = os.getenv("RETRIES", "3")]
118
+ src/client.py:24:5: DEF005 error: except handler converts the exception into a fallback value: return None (EXC002 classification return_fallback; the handler is reported once, here) [except OSError:]
119
+ src/client.py:24:5: EXC001 error: except handler of 'OSError' is forbidden: catch of 'OSError' is not approved by any boundary for this file [except OSError:]
120
+
121
+ shockharness 0.1.0: 1 file(s), 1 analyzed, 0 failed; 5 finding(s), 0 suppressed
122
+ by severity: error=5
123
+ rules applied: MAGIC001, EXC001, EXC002, EXC003, DEF001, DEF002, DEF003, DEF004, DEF005, DEF006, SUPP001, SUPP002
124
+ rules not implemented (not analyzed): MAGIC002, CPLX001, CPLX002, CPLX003, CPLX004, CPLX005
125
+ gate: FAIL (exit 1)
126
+ 5 hard violation(s) and gates.new_hard_violations = "fail"
127
+ ```
128
+
129
+ ## Configuration
130
+
131
+ Either `.shockharness.toml` or a `[tool.shockharness]` table in
132
+ `pyproject.toml` (the dedicated file wins and the shadowed table is reported).
133
+ Unknown keys, bad types, unknown rule IDs, and invalid globs are errors, never
134
+ ignored. Every rule can be disabled; every disable is listed in the report.
135
+
136
+ ```toml
137
+ [tool.shockharness]
138
+ profile = "strict"
139
+ include = ["src/**/*.py", "src/**/*.js"]
140
+ exclude = ["**/node_modules/**", "**/.venv/**"]
141
+ parse_errors = "fail"
142
+
143
+ [tool.shockharness.rules.magic_numbers]
144
+ enabled = true
145
+ allowed_values = [0, 1]
146
+ allow_unary_negative_one = false
147
+
148
+ [[tool.shockharness.overrides]]
149
+ paths = ["tests/**"]
150
+ disable = ["MAGIC001"]
151
+ reason = "Fixtures encode literal expected outputs"
152
+ ```
153
+
154
+ Inline suppressions need an exact rule ID and a reason, on the same line:
155
+
156
+ ```python
157
+ status = 404 # shockharness: ignore[MAGIC001] reason="RFC 9110 section 15.5.5"
158
+ ```
159
+
160
+ A malformed suppression is a `SUPP001` error; one that silences nothing is a
161
+ `SUPP002` warning. `--no-inline-suppressions` disables them for centrally
162
+ governed policies.
163
+
164
+ See `docs/` for configuration, rules, architecture, and limitations.
165
+
166
+ ## Library
167
+
168
+ ```python
169
+ from pathlib import Path
170
+ from shockharness import analyze_paths
171
+ from shockharness.analysis import AnalysisOptions
172
+ from shockharness.config import load_config
173
+
174
+ config = load_config(Path("."), None)
175
+ report = analyze_paths([Path("src")], Path("."), config, AnalysisOptions(inline_suppressions=True))
176
+ print(report.gate.status, len(report.findings))
177
+ ```
178
+
179
+ ## Development
180
+
181
+ ```bash
182
+ uv pip install -e ".[dev]"
183
+ pytest -q
184
+ ruff check src tests && ruff format --check src tests
185
+ shockharness scan src tests # the tool gates its own source
186
+ ```
187
+
188
+ ShockHarness runs on itself as a hard CI gate. Its one `MAGIC001` exemption is
189
+ `src/shockharness/constants.py`; its declared exception boundaries are listed in
190
+ `.shockharness.toml` with reasons and enforced by `EXC001`-`EXC003`: every
191
+ `try` in the tool wraps exactly one external call and converts the failure into
192
+ a typed diagnostic. `DEF001`-`DEF006` hold too: the tool has no parameter
193
+ defaults outside tests, no mapping fallbacks, and no `or`/`is None` defaults.
194
+
195
+ ## License
196
+
197
+ MIT. See `LICENSE`.
@@ -0,0 +1,163 @@
1
+ # ShockHarness
2
+
3
+ A strict, deterministic static-analysis quality gate for Python and JavaScript.
4
+ Its one idea: unexpected failures, missing information, and unexplained
5
+ decisions must not silently become apparently valid program behavior.
6
+
7
+ Three default-on hard policies:
8
+
9
+ 1. **Exception transparency.** No catch except at narrow, explicitly approved
10
+ external boundaries. No catch-and-continue, no success-shaped fallbacks.
11
+ 2. **No implicit defaults.** No fallback values for missing required data, no
12
+ function-parameter defaults in strict mode.
13
+ 3. **No magic numbers.** Every numeric literal in executable code must be
14
+ exactly `0` or `1`. `range(2)` is a violation. The tool invents no exemptions.
15
+
16
+ Plus a separate, advisory complexity family (cyclomatic, cognitive, nesting,
17
+ NPath, call graph, churn). Hard violations are never averaged into a score.
18
+
19
+ ShockHarness complements Ruff and ESLint; it is not a formatter or a general
20
+ linter. It runs offline, needs no LLM, and never uploads source.
21
+
22
+ ## Status
23
+
24
+ Pre-release. Milestones A (skeleton and contracts), B (parsers and
25
+ `MAGIC001`), C (exception rules `EXC001`-`EXC003`) and D (default and fallback
26
+ rules `DEF001`-`DEF006`) are implemented. `MAGIC002`, complexity, baselines,
27
+ SARIF, and agent skills are registered but **not yet implemented**; every
28
+ report lists them under `rules_not_implemented` so a clean run never implies
29
+ coverage the tool does not have. See `STATUS.md` for exact evidence.
30
+
31
+ Implemented rules:
32
+
33
+ | ID | What it reports | Default |
34
+ |---|---|---|
35
+ | `MAGIC001` | numeric literal other than `0`/`1` in executable code | error |
36
+ | `EXC001` | catch outside an approved boundary (bare, broad, unresolved, untyped) | error |
37
+ | `EXC002` | handler that swallows, only logs, or replaces the exception without its cause | error |
38
+ | `EXC003` | approved `try` wrapping more than the one sanctioned operation | error |
39
+ | `DEF001` | parameter default, dataclass field default / `default_factory` | error |
40
+ | `DEF002` | `m.get(k, v)`, `getattr(o, a, v)`, `o?.x ?? v`, `const {x = v} = o`; `.get(k)` implicit `None` as review | error |
41
+ | `DEF003` | `x or v`, `x || v` outside Boolean logic | review |
42
+ | `DEF004` | `x ?? v`, `x if x is not None else v` | error |
43
+ | `DEF005` | handler that returns a default (literal, module constant, one-return helper) | error |
44
+ | `DEF006` | `os.getenv("X", v)`, `process.env.X || v`, `const { X = v } = process.env` | error |
45
+ | `SUPP001` / `SUPP002` | malformed / unused suppression comment | error / warning |
46
+
47
+ ## Install
48
+
49
+ Requires Python 3.11+. Runtime dependencies are `tree-sitter` and
50
+ `tree-sitter-javascript` only.
51
+
52
+ ```bash
53
+ pip install shockharness
54
+ ```
55
+
56
+ From a checkout:
57
+
58
+ ```bash
59
+ uv pip install -e ".[dev]"
60
+ ```
61
+
62
+ ## Use
63
+
64
+ ```bash
65
+ shockharness init # writes .shockharness.toml (strict profile)
66
+ shockharness config validate # exit 0 if the configuration is valid
67
+ shockharness config effective # merged config with per-key origins
68
+ shockharness scan src/ # human-readable report, exit = gate status
69
+ shockharness scan src/ --format json --output report.json
70
+ shockharness rules list
71
+ shockharness rules explain MAGIC001
72
+ ```
73
+
74
+ Exit codes: `0` gates pass, `1` policy violations, `2` invalid configuration or
75
+ invocation, `3` internal error or (with `parse_errors = "fail"`) a file that
76
+ could not be parsed. A report is still produced when the gate fails.
77
+
78
+ Example:
79
+
80
+ ```text
81
+ src/client.py:12:11: MAGIC001 error: Numeric literal 30 has no permitted justification in executable code [timeout = 30]
82
+ src/client.py:15:24: DEF001 error: parameter 'port' of connect() has a default value: 5432 [def connect(host, port=5432):]
83
+ src/client.py:18:15: DEF006 error: environment variable "RETRIES" falls back to "3"; os.getenv(...) substitutes a fallback when environment variable "RETRIES" is unset (getenv) [retries = os.getenv("RETRIES", "3")]
84
+ src/client.py:24:5: DEF005 error: except handler converts the exception into a fallback value: return None (EXC002 classification return_fallback; the handler is reported once, here) [except OSError:]
85
+ src/client.py:24:5: EXC001 error: except handler of 'OSError' is forbidden: catch of 'OSError' is not approved by any boundary for this file [except OSError:]
86
+
87
+ shockharness 0.1.0: 1 file(s), 1 analyzed, 0 failed; 5 finding(s), 0 suppressed
88
+ by severity: error=5
89
+ rules applied: MAGIC001, EXC001, EXC002, EXC003, DEF001, DEF002, DEF003, DEF004, DEF005, DEF006, SUPP001, SUPP002
90
+ rules not implemented (not analyzed): MAGIC002, CPLX001, CPLX002, CPLX003, CPLX004, CPLX005
91
+ gate: FAIL (exit 1)
92
+ 5 hard violation(s) and gates.new_hard_violations = "fail"
93
+ ```
94
+
95
+ ## Configuration
96
+
97
+ Either `.shockharness.toml` or a `[tool.shockharness]` table in
98
+ `pyproject.toml` (the dedicated file wins and the shadowed table is reported).
99
+ Unknown keys, bad types, unknown rule IDs, and invalid globs are errors, never
100
+ ignored. Every rule can be disabled; every disable is listed in the report.
101
+
102
+ ```toml
103
+ [tool.shockharness]
104
+ profile = "strict"
105
+ include = ["src/**/*.py", "src/**/*.js"]
106
+ exclude = ["**/node_modules/**", "**/.venv/**"]
107
+ parse_errors = "fail"
108
+
109
+ [tool.shockharness.rules.magic_numbers]
110
+ enabled = true
111
+ allowed_values = [0, 1]
112
+ allow_unary_negative_one = false
113
+
114
+ [[tool.shockharness.overrides]]
115
+ paths = ["tests/**"]
116
+ disable = ["MAGIC001"]
117
+ reason = "Fixtures encode literal expected outputs"
118
+ ```
119
+
120
+ Inline suppressions need an exact rule ID and a reason, on the same line:
121
+
122
+ ```python
123
+ status = 404 # shockharness: ignore[MAGIC001] reason="RFC 9110 section 15.5.5"
124
+ ```
125
+
126
+ A malformed suppression is a `SUPP001` error; one that silences nothing is a
127
+ `SUPP002` warning. `--no-inline-suppressions` disables them for centrally
128
+ governed policies.
129
+
130
+ See `docs/` for configuration, rules, architecture, and limitations.
131
+
132
+ ## Library
133
+
134
+ ```python
135
+ from pathlib import Path
136
+ from shockharness import analyze_paths
137
+ from shockharness.analysis import AnalysisOptions
138
+ from shockharness.config import load_config
139
+
140
+ config = load_config(Path("."), None)
141
+ report = analyze_paths([Path("src")], Path("."), config, AnalysisOptions(inline_suppressions=True))
142
+ print(report.gate.status, len(report.findings))
143
+ ```
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ uv pip install -e ".[dev]"
149
+ pytest -q
150
+ ruff check src tests && ruff format --check src tests
151
+ shockharness scan src tests # the tool gates its own source
152
+ ```
153
+
154
+ ShockHarness runs on itself as a hard CI gate. Its one `MAGIC001` exemption is
155
+ `src/shockharness/constants.py`; its declared exception boundaries are listed in
156
+ `.shockharness.toml` with reasons and enforced by `EXC001`-`EXC003`: every
157
+ `try` in the tool wraps exactly one external call and converts the failure into
158
+ a typed diagnostic. `DEF001`-`DEF006` hold too: the tool has no parameter
159
+ defaults outside tests, no mapping fallbacks, and no `or`/`is None` defaults.
160
+
161
+ ## License
162
+
163
+ MIT. See `LICENSE`.