recursion-claim-audit 0.1.0__py3-none-any.whl
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.
- recursion_claim_audit/__init__.py +2 -0
- recursion_claim_audit/__main__.py +44 -0
- recursion_claim_audit/audit.py +181 -0
- recursion_claim_audit-0.1.0.dist-info/METADATA +165 -0
- recursion_claim_audit-0.1.0.dist-info/RECORD +9 -0
- recursion_claim_audit-0.1.0.dist-info/WHEEL +5 -0
- recursion_claim_audit-0.1.0.dist-info/entry_points.txt +2 -0
- recursion_claim_audit-0.1.0.dist-info/licenses/LICENSE +21 -0
- recursion_claim_audit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Command line interface."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from . import __version__
|
|
6
|
+
from .audit import audit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Parser(argparse.ArgumentParser):
|
|
10
|
+
def error(self, message):
|
|
11
|
+
self.print_usage(__import__('sys').stderr)
|
|
12
|
+
self.exit(1, self.prog + ': error: ' + message + '\n')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(argv=None):
|
|
16
|
+
parser = Parser(description='Audit documented directory traversal depth against Python source.')
|
|
17
|
+
parser.add_argument('path', nargs='?', default='.')
|
|
18
|
+
parser.add_argument('--json', action='store_true')
|
|
19
|
+
parser.add_argument('--verbose', action='store_true')
|
|
20
|
+
parser.add_argument('--ignore', action='append', default=[], metavar='GLOB')
|
|
21
|
+
parser.add_argument('--fail-on', choices=['MEDIUM', 'HIGH'], default='MEDIUM')
|
|
22
|
+
parser.add_argument('--version', action='version', version=__version__)
|
|
23
|
+
args = parser.parse_args(argv)
|
|
24
|
+
if not Path(args.path).is_dir():
|
|
25
|
+
parser.error('path must be an existing directory')
|
|
26
|
+
result = audit(args.path, args.ignore)
|
|
27
|
+
failed = any(f['severity'] == 'HIGH' or args.fail_on == 'MEDIUM' for f in result['findings'])
|
|
28
|
+
if args.json:
|
|
29
|
+
print(json.dumps(result, indent=2))
|
|
30
|
+
else:
|
|
31
|
+
print('FAIL' if failed else 'PASS')
|
|
32
|
+
for finding in result['findings']:
|
|
33
|
+
print(finding['severity'] + ' ' + finding['rule'])
|
|
34
|
+
for claim in finding['claims']:
|
|
35
|
+
print(' {path}:{line}: {text}'.format(**claim))
|
|
36
|
+
for evidence in finding['evidence']:
|
|
37
|
+
print(' ' + json.dumps(evidence, sort_keys=True))
|
|
38
|
+
if args.verbose:
|
|
39
|
+
print(json.dumps(dict(verified_claims=result['verified_claims'], errors=result['errors']), indent=2))
|
|
40
|
+
return 2 if failed else 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == '__main__':
|
|
44
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Offline documentation and directory-descent analysis."""
|
|
2
|
+
import ast
|
|
3
|
+
import fnmatch
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
RECURSIVE = ('recursively', 'recursive scan', 'recursive search', 'recursively scans',
|
|
8
|
+
'scans recursively', 'searches recursively', 'recursively searches',
|
|
9
|
+
'walks the tree', 'walks the directory tree', 'recursive traversal',
|
|
10
|
+
'traverses recursively', 'recursively walks', 'descends into subdirectories',
|
|
11
|
+
'all subdirectories', 'including subdirectories', 'nested directories')
|
|
12
|
+
SHALLOW_NEG = ('not recursive', 'does not recurse', "doesn't recurse", 'no recursion',
|
|
13
|
+
'without recursion', 'does not descend', "doesn't descend")
|
|
14
|
+
SHALLOW = ('top-level only', 'top level only', 'non-recursive', 'single directory',
|
|
15
|
+
'shallow scan', 'flat scan') + SHALLOW_NEG
|
|
16
|
+
NEGATION = re.compile(r"\b(?:no|not|never|without|doesn't|don't|isn't)\b", re.I)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def contains(sentence, phrase):
|
|
20
|
+
return re.search(r'(?<![\w-])' + re.escape(phrase) + r'(?![\w-])', sentence, re.I) is not None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def extract_claims(text, path='README.md'):
|
|
24
|
+
claims = []
|
|
25
|
+
fence = None
|
|
26
|
+
paragraph = []
|
|
27
|
+
|
|
28
|
+
def flush():
|
|
29
|
+
if not paragraph:
|
|
30
|
+
return
|
|
31
|
+
joined = ' '.join(line for _, line in paragraph)
|
|
32
|
+
offsets = []
|
|
33
|
+
for number, line in paragraph:
|
|
34
|
+
offsets.extend([number] * (len(line) + 1))
|
|
35
|
+
for match in re.finditer(r'.+?(?:[.!?](?=\s|$)|$)', joined):
|
|
36
|
+
sentence = match.group().strip()
|
|
37
|
+
negated = bool(NEGATION.search(sentence))
|
|
38
|
+
for family, phrases in [('recursive', RECURSIVE), ('shallow', SHALLOW)]:
|
|
39
|
+
hits = [p for p in phrases if contains(sentence, p)
|
|
40
|
+
and (not negated or (family == 'shallow' and p in SHALLOW_NEG))]
|
|
41
|
+
if hits:
|
|
42
|
+
start = match.start() + len(match.group()) - len(match.group().lstrip())
|
|
43
|
+
claims.append(dict(family=family, path=path, line=offsets[start],
|
|
44
|
+
text=sentence))
|
|
45
|
+
paragraph.clear()
|
|
46
|
+
|
|
47
|
+
for number, line in enumerate(text.splitlines(), 1):
|
|
48
|
+
mark = re.match(r'^\s{0,3}(`{3,}|~{3,})', line)
|
|
49
|
+
if mark:
|
|
50
|
+
token = mark.group(1)
|
|
51
|
+
if fence is None:
|
|
52
|
+
flush()
|
|
53
|
+
fence = token
|
|
54
|
+
elif token[0] == fence[0] and len(token) >= len(fence):
|
|
55
|
+
fence = None
|
|
56
|
+
continue
|
|
57
|
+
if fence:
|
|
58
|
+
continue
|
|
59
|
+
if not line.strip():
|
|
60
|
+
flush()
|
|
61
|
+
else:
|
|
62
|
+
paragraph.append((number, line))
|
|
63
|
+
flush()
|
|
64
|
+
return claims
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def dotted(node):
|
|
68
|
+
if isinstance(node, ast.Name):
|
|
69
|
+
return node.id
|
|
70
|
+
if isinstance(node, ast.Attribute):
|
|
71
|
+
return dotted(node.value) + '.' + node.attr
|
|
72
|
+
return ''
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def extract_evidence(source, path):
|
|
76
|
+
try:
|
|
77
|
+
tree = ast.parse(source)
|
|
78
|
+
except (SyntaxError, ValueError) as exc:
|
|
79
|
+
return [], str(exc)
|
|
80
|
+
aliases = {}
|
|
81
|
+
for node in ast.walk(tree):
|
|
82
|
+
if isinstance(node, ast.Import):
|
|
83
|
+
for item in node.names:
|
|
84
|
+
aliases[item.asname or item.name] = item.name
|
|
85
|
+
elif isinstance(node, ast.ImportFrom) and node.module:
|
|
86
|
+
for item in node.names:
|
|
87
|
+
aliases[item.asname or item.name] = node.module + '.' + item.name
|
|
88
|
+
|
|
89
|
+
def name(call):
|
|
90
|
+
parts = dotted(call.func).split('.')
|
|
91
|
+
return '.'.join([aliases.get(parts[0], parts[0])] + parts[1:])
|
|
92
|
+
|
|
93
|
+
items = set()
|
|
94
|
+
for node in ast.walk(tree):
|
|
95
|
+
if isinstance(node, ast.Call):
|
|
96
|
+
target = name(node)
|
|
97
|
+
attr = node.func.attr if isinstance(node.func, ast.Attribute) else None
|
|
98
|
+
kind = None
|
|
99
|
+
if target in ('os.walk', 'os.path.walk'):
|
|
100
|
+
kind = target
|
|
101
|
+
elif attr == 'rglob':
|
|
102
|
+
kind = 'rglob'
|
|
103
|
+
elif target == 'glob.glob' and any(k.arg == 'recursive' and
|
|
104
|
+
isinstance(k.value, ast.Constant) and k.value.value is True for k in node.keywords):
|
|
105
|
+
kind = 'recursive-glob'
|
|
106
|
+
elif (target == 'glob.glob' or attr == 'glob') and node.args and isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str) and '**' in node.args[0].value:
|
|
107
|
+
kind = 'glob-pattern'
|
|
108
|
+
if kind:
|
|
109
|
+
items.add((node.lineno, kind))
|
|
110
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
111
|
+
# Nested definitions are separate scopes, not part of this reader.
|
|
112
|
+
def body_nodes(root):
|
|
113
|
+
for child in ast.iter_child_nodes(root):
|
|
114
|
+
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)):
|
|
115
|
+
continue
|
|
116
|
+
yield child
|
|
117
|
+
yield from body_nodes(child)
|
|
118
|
+
calls = [n for n in body_nodes(node) if isinstance(n, ast.Call)]
|
|
119
|
+
self_call = any(isinstance(c.func, ast.Name) and c.func.id == node.name for c in calls)
|
|
120
|
+
reader = any(name(c) in ('os.listdir', 'os.scandir') or
|
|
121
|
+
(isinstance(c.func, ast.Attribute) and c.func.attr == 'iterdir') for c in calls)
|
|
122
|
+
if self_call and reader:
|
|
123
|
+
items.add((node.lineno, 'self-recursive-reader'))
|
|
124
|
+
return [dict(path=path, line=line, kind=kind) for line, kind in sorted(items)], None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def audit(root, ignore=()):
|
|
128
|
+
root = Path(root)
|
|
129
|
+
claims, production, testonly, errors = [], [], [], []
|
|
130
|
+
source_count = 0
|
|
131
|
+
# Do not follow directory symlinks, or open non-source files (including .env).
|
|
132
|
+
import os
|
|
133
|
+
for directory, dirs, files in os.walk(root, followlinks=False):
|
|
134
|
+
dirs[:] = sorted(d for d in dirs if not (Path(directory) / d).is_symlink()
|
|
135
|
+
and d not in ('.git', '__pycache__', '.venv', 'venv', 'build', 'dist')
|
|
136
|
+
and not any(fnmatch.fnmatch((Path(directory) / d).relative_to(root).as_posix(), p) for p in ignore))
|
|
137
|
+
for filename in sorted(files):
|
|
138
|
+
file = Path(directory) / filename
|
|
139
|
+
relative = file.relative_to(root).as_posix()
|
|
140
|
+
if file.is_symlink() or any(fnmatch.fnmatch(relative, p) for p in ignore):
|
|
141
|
+
continue
|
|
142
|
+
parts = file.relative_to(root).parts
|
|
143
|
+
is_doc = relative == 'README.md' or (parts[0] == 'docs' and file.suffix == '.md')
|
|
144
|
+
is_test = any(p in ('tests', 'fixtures') for p in parts[:-1]) or filename.startswith('test_') or filename.endswith('_test.py')
|
|
145
|
+
is_source = file.suffix == '.py' and not any(p in ('docs', 'examples') for p in parts[:-1])
|
|
146
|
+
if not (is_doc or is_source):
|
|
147
|
+
continue
|
|
148
|
+
if is_source and not is_test:
|
|
149
|
+
source_count += 1
|
|
150
|
+
try:
|
|
151
|
+
text = file.read_text(encoding='utf-8')
|
|
152
|
+
except (OSError, UnicodeError) as exc:
|
|
153
|
+
errors.append(dict(path=relative, error=str(exc)))
|
|
154
|
+
continue
|
|
155
|
+
if is_doc:
|
|
156
|
+
claims.extend(extract_claims(text, relative))
|
|
157
|
+
if is_source:
|
|
158
|
+
evidence, error = extract_evidence(text, relative)
|
|
159
|
+
(testonly if is_test else production).extend(evidence)
|
|
160
|
+
if error:
|
|
161
|
+
errors.append(dict(path=relative, error=error))
|
|
162
|
+
findings, verified = [], []
|
|
163
|
+
for family in ('recursive', 'shallow'):
|
|
164
|
+
family_claims = [c for c in claims if c['family'] == family]
|
|
165
|
+
if not family_claims:
|
|
166
|
+
continue
|
|
167
|
+
severity, evidence, suffix = None, [], ''
|
|
168
|
+
if not source_count:
|
|
169
|
+
severity, evidence = 'HIGH', [dict(kind='no-source')]
|
|
170
|
+
elif family == 'recursive' and not production:
|
|
171
|
+
severity, evidence = ('MEDIUM', testonly) if testonly else ('HIGH', [])
|
|
172
|
+
suffix = '-test-only' if testonly else ''
|
|
173
|
+
elif family == 'shallow' and (production or testonly):
|
|
174
|
+
severity, evidence = ('HIGH', production) if production else ('MEDIUM', testonly)
|
|
175
|
+
suffix = '' if production else '-test-only'
|
|
176
|
+
if severity:
|
|
177
|
+
findings.append(dict(rule='documented-' + family + '-claim-false' + suffix,
|
|
178
|
+
severity=severity, claims=family_claims, evidence=evidence))
|
|
179
|
+
else:
|
|
180
|
+
verified.append(dict(family=family, claims=family_claims, evidence=production if family == 'recursive' else []))
|
|
181
|
+
return dict(findings=findings, verified_claims=verified, errors=errors)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: recursion-claim-audit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Audit documented directory traversal depth against Python source.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
# recursion-claim-audit
|
|
12
|
+
|
|
13
|
+
Audit documented directory traversal depth against Python source.
|
|
14
|
+
|
|
15
|
+
Version 0.1.0. A stdlib-only, offline static auditor for Python projects.
|
|
16
|
+
It executes no audited code, uses no network, skips symlinks, and never reads
|
|
17
|
+
`.env` contents. Only root `README.md`, Markdown files beneath `docs/`, and
|
|
18
|
+
eligible Python source are opened. Files outside the target are not opened.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Requires Python 3.9 or newer. Install and run:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
python3 -m pip install .
|
|
26
|
+
recursion-claim-audit /path/to/project
|
|
27
|
+
python3 -m recursion_claim_audit /path/to/project --json
|
|
28
|
+
recursion-claim-audit . --verbose --ignore 'vendor/*' --fail-on HIGH
|
|
29
|
+
recursion-claim-audit --version
|
|
30
|
+
PYTHONPATH=src python3 -m unittest discover -s tests -v
|
|
31
|
+
python3 -m build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The target defaults to the current directory. `--ignore GLOB` is repeatable
|
|
35
|
+
and matches relative paths; matching directories are pruned. `--verbose`
|
|
36
|
+
adds verified claims and source diagnostics to text output. `--json` emits
|
|
37
|
+
an object with `findings`, `verified_claims`, and `errors`. Findings include
|
|
38
|
+
rule, severity, claims (path, line, text, family), and evidence (path, line,
|
|
39
|
+
kind). Parse/read errors appear in diagnostics and do not themselves create
|
|
40
|
+
rules. Exit status is 0 for PASS, 2 for findings meeting the threshold, and
|
|
41
|
+
1 for usage errors. The default threshold is MEDIUM; `--fail-on HIGH` keeps
|
|
42
|
+
MEDIUM findings visible while permitting exit 0.
|
|
43
|
+
|
|
44
|
+
## Claim extraction
|
|
45
|
+
|
|
46
|
+
Claims come only from root README.md and docs Markdown. Fenced blocks are
|
|
47
|
+
excluded; inline code counts as prose. Matching is case-insensitive and uses
|
|
48
|
+
only the exact phrases below, with word boundaries. The families are
|
|
49
|
+
independent; contradictory documentation can produce separate findings.
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
RECURSIVE:
|
|
53
|
+
recursively
|
|
54
|
+
recursive scan
|
|
55
|
+
recursive search
|
|
56
|
+
recursively scans
|
|
57
|
+
scans recursively
|
|
58
|
+
searches recursively
|
|
59
|
+
recursively searches
|
|
60
|
+
walks the tree
|
|
61
|
+
walks the directory tree
|
|
62
|
+
recursive traversal
|
|
63
|
+
traverses recursively
|
|
64
|
+
recursively walks
|
|
65
|
+
descends into subdirectories
|
|
66
|
+
all subdirectories
|
|
67
|
+
including subdirectories
|
|
68
|
+
nested directories
|
|
69
|
+
|
|
70
|
+
SHALLOW:
|
|
71
|
+
top-level only
|
|
72
|
+
top level only
|
|
73
|
+
non-recursive
|
|
74
|
+
not recursive
|
|
75
|
+
single directory
|
|
76
|
+
does not recurse
|
|
77
|
+
doesn't recurse
|
|
78
|
+
no recursion
|
|
79
|
+
without recursion
|
|
80
|
+
shallow scan
|
|
81
|
+
flat scan
|
|
82
|
+
does not descend
|
|
83
|
+
doesn't descend
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Negation is asymmetric: a sentence containing no, not, never, without,
|
|
87
|
+
doesn't, don't, or isn't suppresses the RECURSIVE family. SHALLOW phrases
|
|
88
|
+
which express negation themselves still count; other SHALLOW phrases are
|
|
89
|
+
suppressed in negated sentences. This includes the two descent-negation
|
|
90
|
+
phrases at the end of the list. Sentence boundaries are punctuation or
|
|
91
|
+
paragraph boundaries. The bare adverb in the first family counts on its
|
|
92
|
+
own, while the bare noun “recursion” does not. For example:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
walks recursively -> recursive claim
|
|
96
|
+
recursion support -> no claim
|
|
97
|
+
does not scan recursively -> no claim
|
|
98
|
+
this tool is not recursive -> shallow claim
|
|
99
|
+
does not scan top-level only -> no claim
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Rules
|
|
103
|
+
|
|
104
|
+
One finding per claimed family; repeated prose is retained in its claims.
|
|
105
|
+
|
|
106
|
+
| Rule | Severity | Condition |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| documented-recursive-claim-false | HIGH | RECURSIVE claim without production or test evidence |
|
|
109
|
+
| documented-recursive-claim-false-test-only | MEDIUM | RECURSIVE claim with test evidence alone |
|
|
110
|
+
| documented-shallow-claim-false | HIGH | SHALLOW claim with production evidence |
|
|
111
|
+
| documented-shallow-claim-false-test-only | MEDIUM | SHALLOW claim with test evidence alone |
|
|
112
|
+
|
|
113
|
+
A claimed family with no production Python source always gets its base HIGH
|
|
114
|
+
rule with `no-source` evidence. A RECURSIVE claim with production evidence
|
|
115
|
+
is verified. A SHALLOW claim without evidence anywhere is verified. No
|
|
116
|
+
claim means PASS regardless of source evidence.
|
|
117
|
+
|
|
118
|
+
## Named evidence
|
|
119
|
+
|
|
120
|
+
Each call site or definition is one item, deduplicated by file, line, kind.
|
|
121
|
+
|
|
122
|
+
| Kind | Recognized syntax |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| os.walk | Standard library call, including per-file imported aliases |
|
|
125
|
+
| os.path.walk | Legacy standard library call |
|
|
126
|
+
| rglob | Attribute call on any receiver |
|
|
127
|
+
| recursive-glob | glob.glob with literal True for its recursive keyword |
|
|
128
|
+
| glob-pattern | glob.glob or any .glob with a literal first argument containing ** |
|
|
129
|
+
| self-recursive-reader | Function calls its own name and reads directory entries via os.listdir, os.scandir or .iterdir |
|
|
130
|
+
|
|
131
|
+
Production excludes tests/ and fixtures/ directories, test_*.py, *_test.py,
|
|
132
|
+
docs/, and examples/. Test and fixture source is collected separately.
|
|
133
|
+
Symlinked files and directories are skipped. Build outputs, virtual
|
|
134
|
+
environments, Git metadata and bytecode directories are skipped too.
|
|
135
|
+
|
|
136
|
+
## Differentiation
|
|
137
|
+
|
|
138
|
+
| Neighbour | Scope compared with this tool |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| sort-claim-audit | Output order rather than traversal depth |
|
|
141
|
+
| pagination-claim-audit | Result paging rather than local descent |
|
|
142
|
+
| instruction-drift | Instruction-file truth rather than traversal behaviour |
|
|
143
|
+
| streaming-claim-audit | Read chunking rather than tree depth |
|
|
144
|
+
| bandit / ruff | Code issues rather than documented promises |
|
|
145
|
+
| os.walk / pathlib documentation | Primitive semantics rather than docs-versus-code truth |
|
|
146
|
+
|
|
147
|
+
## Honest limitations
|
|
148
|
+
|
|
149
|
+
Exact-phrase matching misses paraphrases and does not resolve which API a
|
|
150
|
+
sentence describes. Evidence is a deliberate over-approximation: dead code
|
|
151
|
+
counts, and presence does not prove the promised operation reaches that
|
|
152
|
+
code. Receiver types are not inferred for attribute matches. Imported names
|
|
153
|
+
are tracked per file, without resolving shadowing, control flow or dynamic
|
|
154
|
+
imports. Nested function definitions are treated separately.
|
|
155
|
+
|
|
156
|
+
A directory-entry read alone (os.listdir, os.scandir, or .iterdir) is not
|
|
157
|
+
evidence. Repeating a read over a fixed list of directories is not evidence.
|
|
158
|
+
glob.glob without the keyword or the double-star pattern is not evidence;
|
|
159
|
+
a plain Path.glob pattern is not evidence either. A function calling itself
|
|
160
|
+
without directory reads is not evidence. A directory reader without a
|
|
161
|
+
self-call or a recognized walker is not evidence. Third-party walkers such
|
|
162
|
+
as pathspec, wcmatch and the scandir package are outside scope. A keyword
|
|
163
|
+
named recursive with literal True on a non-glob call is not evidence.
|
|
164
|
+
Syntax and decoding errors are reported without executing fallback code.
|
|
165
|
+
The tool cannot prove runtime coverage or filesystem permissions.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
recursion_claim_audit/__init__.py,sha256=tyalzAT4_vApfhWD0BRLYxa7CWomUC8DfTN73emd4Zs,94
|
|
2
|
+
recursion_claim_audit/__main__.py,sha256=E65AtSdNvcXg4uRaULQU7zG_xoO1LBLTf4bIOxCXAic,1798
|
|
3
|
+
recursion_claim_audit/audit.py,sha256=_fjdlKAuCm5xGgc_fwFbaPTm1HV3RfQiKv4_SnkH4Xk,8482
|
|
4
|
+
recursion_claim_audit-0.1.0.dist-info/licenses/LICENSE,sha256=npbfPSaCrk2DCGzXcDpFkAFgZ1Ghc4aeo9Kl0GJTjyk,1091
|
|
5
|
+
recursion_claim_audit-0.1.0.dist-info/METADATA,sha256=_VZA66FPQz-WU9DAJzGHn5GvQS1cTS9f4CY6HkumDW0,6662
|
|
6
|
+
recursion_claim_audit-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
7
|
+
recursion_claim_audit-0.1.0.dist-info/entry_points.txt,sha256=43nMkT4UzORGzAPc6yfmUtd3UOOoDpxscgvb8PfZYgw,78
|
|
8
|
+
recursion_claim_audit-0.1.0.dist-info/top_level.txt,sha256=74_YOD4h_M5wQU0iUGgNTh0QtDPgT18k2hi1igRjeTo,22
|
|
9
|
+
recursion_claim_audit-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 recursion-claim-audit contributors
|
|
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 @@
|
|
|
1
|
+
recursion_claim_audit
|