agent-code-guard 0.2.0__tar.gz → 0.3.1__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.
- {agent_code_guard-0.2.0/src/agent_code_guard.egg-info → agent_code_guard-0.3.1}/PKG-INFO +38 -14
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/README.md +37 -13
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/pyproject.toml +1 -1
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/SKILL.md +13 -10
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/references/loc-policy.md +9 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/adapters.py +4 -4
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/callable_identity.py +1 -2
- agent_code_guard-0.3.1/src/agent_code_guard/analysis/csharp_compat.py +115 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/errors.py +4 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/facts.py +21 -1
- agent_code_guard-0.3.1/src/agent_code_guard/analysis/pipeline.py +92 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/provider.py +5 -3
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/regions.py +33 -17
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/code_guard.py +127 -36
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/config_validation.py +18 -19
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/file_selection.py +42 -17
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/guards/callable_size.py +14 -20
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/guards/complexity.py +14 -19
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/guards/loc.py +46 -36
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/guards/markdown_document_size.py +16 -18
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/guards/markdown_section_size.py +16 -18
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/guards/nesting.py +14 -19
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/human_output.py +19 -4
- agent_code_guard-0.3.1/src/agent_code_guard/invocation.py +54 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/loc_baseline.py +16 -7
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/markdown/facts.py +1 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/markdown/scanner.py +15 -6
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1/src/agent_code_guard.egg-info}/PKG-INFO +38 -14
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard.egg-info/SOURCES.txt +2 -0
- agent_code_guard-0.2.0/src/agent_code_guard/analysis/pipeline.py +0 -37
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/LICENSE +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/MANIFEST.in +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/setup.cfg +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/LICENSE.txt +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/agents/openai.yaml +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/references/callable-size-policy.md +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/references/complexity-policy.md +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/references/markdown-size-policy.md +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/references/nesting-policy.md +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/__init__.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/__init__.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/branch_normalization.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/language_specs.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/syntax_nodes.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/doctor.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/guards/__init__.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/markdown/__init__.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/path_matching.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/reporting.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/result_model.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/skill_distribution.py +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard.egg-info/dependency_links.txt +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard.egg-info/entry_points.txt +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard.egg-info/requires.txt +0 -0
- {agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-code-guard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Deterministic cross-language guardrails for agent-assisted development
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Source, https://github.com/stef-k/agent-code-guard
|
|
@@ -82,16 +82,28 @@ environment, uv, and developer alternatives.
|
|
|
82
82
|
|
|
83
83
|
Copy this prompt to a coding agent:
|
|
84
84
|
|
|
85
|
-
>
|
|
86
|
-
>
|
|
87
|
-
>
|
|
88
|
-
>
|
|
89
|
-
>
|
|
90
|
-
>
|
|
91
|
-
>
|
|
92
|
-
>
|
|
93
|
-
>
|
|
94
|
-
>
|
|
85
|
+
> Adopt Agent Code Guard for this repository using the official project and
|
|
86
|
+
> documentation at https://github.com/stef-k/agent-code-guard. This prompt
|
|
87
|
+
> authorizes installing the published distribution in an isolated supported
|
|
88
|
+
> way, preferably with `pipx install agent-code-guard`, and persistently
|
|
89
|
+
> activating or exporting its installed, version-matched skill for the current
|
|
90
|
+
> agent environment. Verify `code-guard --version` and run `code-guard doctor`,
|
|
91
|
+
> then locate the skill with `code-guard --skill-path`. Follow the
|
|
92
|
+
> [skill activation guide](https://github.com/stef-k/agent-code-guard/blob/main/docs/skill-distribution.md):
|
|
93
|
+
> activate that installed path when the platform supports it, or export it with
|
|
94
|
+
> `code-guard --export-skill <target-directory>` to the platform's documented
|
|
95
|
+
> skill directory. Do not guess a target or overwrite an existing export; verify
|
|
96
|
+
> that `.agent-code-guard-version` matches the installed distribution. Follow the
|
|
97
|
+
> [agent workflow](https://github.com/stef-k/agent-code-guard/blob/main/docs/agent-workflow.md)
|
|
98
|
+
> without creating a LOC baseline. After meaningful source or Markdown edits, run
|
|
99
|
+
> `code-guard . --changed-only --json --json-mode compact`; outside Git, pass the
|
|
100
|
+
> exact edited files. Inspect REVIEW and FAIL findings, make only genuine
|
|
101
|
+
> improvements, rerun the check, and report the result. Never weaken thresholds,
|
|
102
|
+
> exclusions, configuration, or baselines merely to silence findings. Code Guard
|
|
103
|
+
> does not install or enable hooks by default. Ask before configuring a post-edit
|
|
104
|
+
> or post-turn hook, or making unrelated persistent agent or platform changes.
|
|
105
|
+
> Finish by reporting the installed version, doctor status, activated skill
|
|
106
|
+
> location, first analysis result, and any REVIEW findings you accepted.
|
|
95
107
|
|
|
96
108
|
## Five-minute start
|
|
97
109
|
|
|
@@ -138,12 +150,14 @@ hook-assisted process.
|
|
|
138
150
|
warranted; normally exit `1`.
|
|
139
151
|
- **Completed FAIL** — blocks normal completion until fixed or an explicitly
|
|
140
152
|
authorized exception applies; exit `2` with a completed report on stdout.
|
|
153
|
+
- **INCOMPLETE** — one or more requested syntax analyses are unavailable;
|
|
154
|
+
independent completed findings remain authoritative, but the run exits `3`.
|
|
141
155
|
- An argparse usage or invalid-choice error exits `2`, writes usage/error text
|
|
142
156
|
to stderr, and produces no completed report.
|
|
143
|
-
- Other Code Guard tool, configuration, scope, or
|
|
157
|
+
- Other Code Guard tool, configuration, scope, or unexpected errors exit `3`.
|
|
144
158
|
|
|
145
159
|
`--ci` makes REVIEW nonblocking at the process level by changing its exit to
|
|
146
|
-
`0`; it does not hide
|
|
160
|
+
`0`; it does not hide findings or change FAIL, INCOMPLETE, and tool-error exits.
|
|
147
161
|
|
|
148
162
|
**Never game a metric.** Do not create artificial helpers, files,
|
|
149
163
|
abstractions, formatting, exclusions, or policy changes merely to lower a
|
|
@@ -175,6 +189,12 @@ result, scope, required policies, guards, ordering, and actionable findings.
|
|
|
175
189
|
Named modes require `--json`. See [Usage](https://github.com/stef-k/agent-code-guard/blob/main/docs/usage.md) for the schema and
|
|
176
190
|
option contract.
|
|
177
191
|
|
|
192
|
+
Known per-file syntax or provider failures instead produce `overall:
|
|
193
|
+
"incomplete"`, ordered structured `unavailable` records, and completed guard
|
|
194
|
+
evidence. Syntax guards identify unavailable paths; LOC and Markdown remain
|
|
195
|
+
complete. Full, debug, and compact JSON retain unavailable records unchanged,
|
|
196
|
+
and completed runs retain their existing schema.
|
|
197
|
+
|
|
178
198
|
### Common scope commands
|
|
179
199
|
|
|
180
200
|
```bash
|
|
@@ -200,7 +220,8 @@ Markdown guards apply to `.md` files.
|
|
|
200
220
|
Generic `.h` files are not syntax-dispatched; `.markdown` is not enabled; Vue
|
|
201
221
|
template and style regions are not executable syntax input; and unsupported
|
|
202
222
|
artifacts are inapplicable. Malformed applicable syntax or a required provider
|
|
203
|
-
failure is
|
|
223
|
+
failure is unavailable evidence, distinct from both an inapplicable file and a
|
|
224
|
+
completed FAIL finding. See [Language support](https://github.com/stef-k/agent-code-guard/blob/main/docs/language-support.md).
|
|
204
225
|
|
|
205
226
|
### Skill integration
|
|
206
227
|
|
|
@@ -221,6 +242,9 @@ Built-in defaults require no configuration. Configure a project only for a
|
|
|
221
242
|
concrete policy reason; see the [configuration guide](https://github.com/stef-k/agent-code-guard/blob/main/docs/configuration.md).
|
|
222
243
|
The LOC baseline is an explicit adoption tool for established legacy
|
|
223
244
|
repositories, not an ordinary-use requirement or a way to silence findings.
|
|
245
|
+
Its default `ratchetAt: "fail"` policy freezes files above `failAt`; choose
|
|
246
|
+
`ratchetAt: "review"` only when every established file above `warnAt` must be
|
|
247
|
+
non-increasing while ordinary findings in that range remain REVIEW.
|
|
224
248
|
|
|
225
249
|
## Trust, CI, and platform support
|
|
226
250
|
|
|
@@ -68,16 +68,28 @@ environment, uv, and developer alternatives.
|
|
|
68
68
|
|
|
69
69
|
Copy this prompt to a coding agent:
|
|
70
70
|
|
|
71
|
-
>
|
|
72
|
-
>
|
|
73
|
-
>
|
|
74
|
-
>
|
|
75
|
-
>
|
|
76
|
-
>
|
|
77
|
-
>
|
|
78
|
-
>
|
|
79
|
-
>
|
|
80
|
-
>
|
|
71
|
+
> Adopt Agent Code Guard for this repository using the official project and
|
|
72
|
+
> documentation at https://github.com/stef-k/agent-code-guard. This prompt
|
|
73
|
+
> authorizes installing the published distribution in an isolated supported
|
|
74
|
+
> way, preferably with `pipx install agent-code-guard`, and persistently
|
|
75
|
+
> activating or exporting its installed, version-matched skill for the current
|
|
76
|
+
> agent environment. Verify `code-guard --version` and run `code-guard doctor`,
|
|
77
|
+
> then locate the skill with `code-guard --skill-path`. Follow the
|
|
78
|
+
> [skill activation guide](https://github.com/stef-k/agent-code-guard/blob/main/docs/skill-distribution.md):
|
|
79
|
+
> activate that installed path when the platform supports it, or export it with
|
|
80
|
+
> `code-guard --export-skill <target-directory>` to the platform's documented
|
|
81
|
+
> skill directory. Do not guess a target or overwrite an existing export; verify
|
|
82
|
+
> that `.agent-code-guard-version` matches the installed distribution. Follow the
|
|
83
|
+
> [agent workflow](https://github.com/stef-k/agent-code-guard/blob/main/docs/agent-workflow.md)
|
|
84
|
+
> without creating a LOC baseline. After meaningful source or Markdown edits, run
|
|
85
|
+
> `code-guard . --changed-only --json --json-mode compact`; outside Git, pass the
|
|
86
|
+
> exact edited files. Inspect REVIEW and FAIL findings, make only genuine
|
|
87
|
+
> improvements, rerun the check, and report the result. Never weaken thresholds,
|
|
88
|
+
> exclusions, configuration, or baselines merely to silence findings. Code Guard
|
|
89
|
+
> does not install or enable hooks by default. Ask before configuring a post-edit
|
|
90
|
+
> or post-turn hook, or making unrelated persistent agent or platform changes.
|
|
91
|
+
> Finish by reporting the installed version, doctor status, activated skill
|
|
92
|
+
> location, first analysis result, and any REVIEW findings you accepted.
|
|
81
93
|
|
|
82
94
|
## Five-minute start
|
|
83
95
|
|
|
@@ -124,12 +136,14 @@ hook-assisted process.
|
|
|
124
136
|
warranted; normally exit `1`.
|
|
125
137
|
- **Completed FAIL** — blocks normal completion until fixed or an explicitly
|
|
126
138
|
authorized exception applies; exit `2` with a completed report on stdout.
|
|
139
|
+
- **INCOMPLETE** — one or more requested syntax analyses are unavailable;
|
|
140
|
+
independent completed findings remain authoritative, but the run exits `3`.
|
|
127
141
|
- An argparse usage or invalid-choice error exits `2`, writes usage/error text
|
|
128
142
|
to stderr, and produces no completed report.
|
|
129
|
-
- Other Code Guard tool, configuration, scope, or
|
|
143
|
+
- Other Code Guard tool, configuration, scope, or unexpected errors exit `3`.
|
|
130
144
|
|
|
131
145
|
`--ci` makes REVIEW nonblocking at the process level by changing its exit to
|
|
132
|
-
`0`; it does not hide
|
|
146
|
+
`0`; it does not hide findings or change FAIL, INCOMPLETE, and tool-error exits.
|
|
133
147
|
|
|
134
148
|
**Never game a metric.** Do not create artificial helpers, files,
|
|
135
149
|
abstractions, formatting, exclusions, or policy changes merely to lower a
|
|
@@ -161,6 +175,12 @@ result, scope, required policies, guards, ordering, and actionable findings.
|
|
|
161
175
|
Named modes require `--json`. See [Usage](https://github.com/stef-k/agent-code-guard/blob/main/docs/usage.md) for the schema and
|
|
162
176
|
option contract.
|
|
163
177
|
|
|
178
|
+
Known per-file syntax or provider failures instead produce `overall:
|
|
179
|
+
"incomplete"`, ordered structured `unavailable` records, and completed guard
|
|
180
|
+
evidence. Syntax guards identify unavailable paths; LOC and Markdown remain
|
|
181
|
+
complete. Full, debug, and compact JSON retain unavailable records unchanged,
|
|
182
|
+
and completed runs retain their existing schema.
|
|
183
|
+
|
|
164
184
|
### Common scope commands
|
|
165
185
|
|
|
166
186
|
```bash
|
|
@@ -186,7 +206,8 @@ Markdown guards apply to `.md` files.
|
|
|
186
206
|
Generic `.h` files are not syntax-dispatched; `.markdown` is not enabled; Vue
|
|
187
207
|
template and style regions are not executable syntax input; and unsupported
|
|
188
208
|
artifacts are inapplicable. Malformed applicable syntax or a required provider
|
|
189
|
-
failure is
|
|
209
|
+
failure is unavailable evidence, distinct from both an inapplicable file and a
|
|
210
|
+
completed FAIL finding. See [Language support](https://github.com/stef-k/agent-code-guard/blob/main/docs/language-support.md).
|
|
190
211
|
|
|
191
212
|
### Skill integration
|
|
192
213
|
|
|
@@ -207,6 +228,9 @@ Built-in defaults require no configuration. Configure a project only for a
|
|
|
207
228
|
concrete policy reason; see the [configuration guide](https://github.com/stef-k/agent-code-guard/blob/main/docs/configuration.md).
|
|
208
229
|
The LOC baseline is an explicit adoption tool for established legacy
|
|
209
230
|
repositories, not an ordinary-use requirement or a way to silence findings.
|
|
231
|
+
Its default `ratchetAt: "fail"` policy freezes files above `failAt`; choose
|
|
232
|
+
`ratchetAt: "review"` only when every established file above `warnAt` must be
|
|
233
|
+
non-increasing while ordinary findings in that range remain REVIEW.
|
|
210
234
|
|
|
211
235
|
## Trust, CI, and platform support
|
|
212
236
|
|
|
@@ -15,6 +15,7 @@ Code Guard provides deterministic measurements that act as anchors for agent jud
|
|
|
15
15
|
- `PASS` — no special action is required.
|
|
16
16
|
- `REVIEW` — inspect the finding and either accept it with a meaningful justification or improve the code when doing so improves real clarity, cohesion, or boundaries.
|
|
17
17
|
- `FAIL` — do not declare normal completion until the condition is fixed or an explicitly permitted/user-approved exception applies.
|
|
18
|
+
- `INCOMPLETE` — known per-file syntax or provider evidence is unavailable; retain and report independent completed evidence, but do not declare normal completion.
|
|
18
19
|
|
|
19
20
|
## Universal rules
|
|
20
21
|
|
|
@@ -50,10 +51,11 @@ python3 skills/code-guard/scripts/code_guard.py . --changed-only
|
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
`pyproject.toml` canonically owns the production pins. Tree-sitter remains
|
|
53
|
-
dormant during LOC-only execution
|
|
54
|
-
|
|
55
|
-
Disabling every syntax guard preserves
|
|
56
|
-
LOC-only result also requires both
|
|
54
|
+
dormant during LOC-only execution. A known per-file provider, grammar, or syntax
|
|
55
|
+
failure produces blocking `INCOMPLETE` output while retaining independent LOC,
|
|
56
|
+
Markdown, and unaffected syntax evidence. Disabling every syntax guard preserves
|
|
57
|
+
the lazy no-Tree-sitter path. A strictly LOC-only result also requires both
|
|
58
|
+
Markdown guards to be explicitly disabled.
|
|
57
59
|
|
|
58
60
|
Without Git, pass exactly the files you created or modified. You are
|
|
59
61
|
responsible for supplying the complete edited-file set:
|
|
@@ -75,8 +77,9 @@ project exclusions. LOC `--exclude` remains LOC-specific. Explicit files may
|
|
|
75
77
|
intentionally inspect Git-ignored or built-in-pruned artifacts, unless Code
|
|
76
78
|
Guard `scope.exclude` or `--scope-exclude` removes them.
|
|
77
79
|
|
|
78
|
-
Inspect every REVIEW and FAIL.
|
|
79
|
-
|
|
80
|
+
Inspect every REVIEW and FAIL. Treat INCOMPLETE as blocking, preserve its
|
|
81
|
+
completed findings, and report its unavailable paths and failure context. When
|
|
82
|
+
all guards return `PASS`, no detailed policy file needs to be loaded.
|
|
80
83
|
|
|
81
84
|
When a guard returns `REVIEW` or `FAIL`, read only the policy file named by that finding. The runner returns required policy identifiers/files in both human-readable and JSON output.
|
|
82
85
|
|
|
@@ -103,10 +106,10 @@ code-guard . --changed-only --ci --json --json-mode compact
|
|
|
103
106
|
```
|
|
104
107
|
|
|
105
108
|
`--ci` makes REVIEW exit `0`, but REVIEW remains visible and requires
|
|
106
|
-
judgment; FAIL remains `2
|
|
107
|
-
must supply the exact edited files. Do not install hooks, export
|
|
108
|
-
skill directories, or modify persistent user or repository
|
|
109
|
-
without authorization.
|
|
109
|
+
judgment; FAIL remains `2`, and INCOMPLETE and tool errors remain `3`. Outside
|
|
110
|
+
Git, a hook must supply the exact edited files. Do not install hooks, export
|
|
111
|
+
into persistent skill directories, or modify persistent user or repository
|
|
112
|
+
configuration without authorization.
|
|
110
113
|
|
|
111
114
|
## Scope
|
|
112
115
|
|
{agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/skills/code-guard/references/loc-policy.md
RENAMED
|
@@ -27,6 +27,15 @@ Existing `allowedLargeFiles` entries may be honored with their configured reason
|
|
|
27
27
|
|
|
28
28
|
Do not infer approval from inconvenience, historical size, a nearby exemption, time pressure, or a request to finish the coding task.
|
|
29
29
|
|
|
30
|
+
## Source-controlled ratchets
|
|
31
|
+
|
|
32
|
+
An established repository may use `guards.loc.ratchetAt: "fail"` (the default)
|
|
33
|
+
to freeze only files already above `failAt`, or `"review"` to freeze every
|
|
34
|
+
recorded file above `warnAt` while retaining ordinary REVIEW semantics within
|
|
35
|
+
its allowance. Growth above a recorded allowance is always FAIL. Never switch
|
|
36
|
+
the policy, raise thresholds, add exclusions or exemptions, or increase/remove
|
|
37
|
+
allowances merely to silence growth.
|
|
38
|
+
|
|
30
39
|
## Do not game LOC
|
|
31
40
|
|
|
32
41
|
Project formatting conventions take priority. Never combine independent statements, compress control flow or expressions unusually, minify handwritten code, remove useful comments/structure, or fight the formatter merely to lower physical LOC.
|
|
@@ -116,11 +116,11 @@ def _range_end_node(node, language: str):
|
|
|
116
116
|
|
|
117
117
|
def _callable_range(node, region: ExecutableRegion) -> SourceRange:
|
|
118
118
|
"""Snapshot provider points once before mapping them to original source."""
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
start = callable_source_start(node, region.language)
|
|
120
|
+
end = _range_end_node(node, region.language)
|
|
121
121
|
return SourceRange(
|
|
122
|
-
region.
|
|
123
|
-
region.
|
|
122
|
+
region.original_point_at_byte(start.start_byte),
|
|
123
|
+
region.original_point_at_byte(end.end_byte),
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
|
{agent_code_guard-0.2.0 → agent_code_guard-0.3.1}/src/agent_code_guard/analysis/callable_identity.py
RENAMED
|
@@ -272,8 +272,7 @@ def _javascript_lexical_name(node, source: bytes) -> str | None:
|
|
|
272
272
|
|
|
273
273
|
|
|
274
274
|
def _callback_name(node, region: ExecutableRegion) -> str:
|
|
275
|
-
|
|
276
|
-
point = region.original_point(row, column)
|
|
275
|
+
point = region.original_point_at_byte(node.start_byte)
|
|
277
276
|
return f"<callback@{point.line}:{point.byte_column}>"
|
|
278
277
|
|
|
279
278
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Narrow compatibility correction for upstream C# contextual-keyword errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_CONTEXTUAL_KEYWORD = b"async"
|
|
7
|
+
_NEUTRAL_IDENTIFIER = b"azync"
|
|
8
|
+
_EXPRESSION_CONTAINERS = {
|
|
9
|
+
"argument", "arrow_expression_clause", "expression_statement", "return_statement",
|
|
10
|
+
}
|
|
11
|
+
_NON_EXPRESSION_FIELDS = {"alias", "label", "name", "type"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def corrected_csharp_root(provider, source: bytes, first_tree):
|
|
15
|
+
"""Return one verified corrected root, or ``None`` when correction is unsafe."""
|
|
16
|
+
problems = tuple(_problem_nodes(first_tree.root_node))
|
|
17
|
+
if not problems:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
token_offsets: set[int] = set()
|
|
21
|
+
for problem in problems:
|
|
22
|
+
candidates = _contained_unescaped_tokens(source, problem.start_byte, problem.end_byte)
|
|
23
|
+
if len(candidates) > 1:
|
|
24
|
+
candidates = tuple(
|
|
25
|
+
offset
|
|
26
|
+
for offset in candidates
|
|
27
|
+
if not _is_declaration_identifier(first_tree.root_node, offset)
|
|
28
|
+
)
|
|
29
|
+
if len(candidates) != 1:
|
|
30
|
+
return None
|
|
31
|
+
token_offsets.add(candidates[0])
|
|
32
|
+
|
|
33
|
+
corrected = bytearray(source)
|
|
34
|
+
for offset in token_offsets:
|
|
35
|
+
corrected[offset:offset + len(_CONTEXTUAL_KEYWORD)] = _NEUTRAL_IDENTIFIER
|
|
36
|
+
corrected_source = bytes(corrected)
|
|
37
|
+
if len(corrected_source) != len(source) or _newline_offsets(corrected_source) != _newline_offsets(source):
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
retry_tree = provider.parse("csharp", corrected_source)
|
|
41
|
+
if retry_tree.root_node.has_error or any(_problem_nodes(retry_tree.root_node)):
|
|
42
|
+
return None
|
|
43
|
+
if not all(_has_authorized_role(retry_tree.root_node, offset) for offset in token_offsets):
|
|
44
|
+
return None
|
|
45
|
+
return retry_tree.root_node
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _problem_nodes(node):
|
|
49
|
+
if node.is_error or node.is_missing:
|
|
50
|
+
yield node
|
|
51
|
+
for child in node.children:
|
|
52
|
+
yield from _problem_nodes(child)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _contained_unescaped_tokens(source: bytes, start: int, end: int) -> tuple[int, ...]:
|
|
56
|
+
offsets: list[int] = []
|
|
57
|
+
position = source.find(_CONTEXTUAL_KEYWORD, start, end)
|
|
58
|
+
while position >= 0:
|
|
59
|
+
token_end = position + len(_CONTEXTUAL_KEYWORD)
|
|
60
|
+
if token_end <= end and _is_token_boundary(source, position, token_end):
|
|
61
|
+
offsets.append(position)
|
|
62
|
+
position = source.find(_CONTEXTUAL_KEYWORD, position + 1, end)
|
|
63
|
+
return tuple(offsets)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _is_token_boundary(source: bytes, start: int, end: int) -> bool:
|
|
67
|
+
before = source[start - 1] if start else None
|
|
68
|
+
after = source[end] if end < len(source) else None
|
|
69
|
+
return before != ord("@") and not _identifier_byte(before) and not _identifier_byte(after)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _identifier_byte(value: int | None) -> bool:
|
|
73
|
+
return value is not None and (value >= 0x80 or value == ord("_") or chr(value).isalnum())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _newline_offsets(source: bytes) -> tuple[int, ...]:
|
|
77
|
+
return tuple(index for index, value in enumerate(source) if value == ord("\n"))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_declaration_identifier(root, offset: int) -> bool:
|
|
81
|
+
node = root.descendant_for_byte_range(offset, offset + len(_CONTEXTUAL_KEYWORD))
|
|
82
|
+
if (
|
|
83
|
+
node.type != "identifier"
|
|
84
|
+
or node.start_byte != offset
|
|
85
|
+
or node.end_byte != offset + len(_CONTEXTUAL_KEYWORD)
|
|
86
|
+
or node.parent is None
|
|
87
|
+
):
|
|
88
|
+
return False
|
|
89
|
+
field_name = next(
|
|
90
|
+
(
|
|
91
|
+
node.parent.field_name_for_child(index)
|
|
92
|
+
for index, child in enumerate(node.parent.children)
|
|
93
|
+
if child == node
|
|
94
|
+
),
|
|
95
|
+
None,
|
|
96
|
+
)
|
|
97
|
+
return field_name in _NON_EXPRESSION_FIELDS
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _has_authorized_role(root, offset: int) -> bool:
|
|
101
|
+
node = root.descendant_for_byte_range(offset, offset + len(_NEUTRAL_IDENTIFIER))
|
|
102
|
+
if node.type != "identifier" or node.start_byte != offset or node.end_byte != offset + len(_NEUTRAL_IDENTIFIER):
|
|
103
|
+
return False
|
|
104
|
+
parent = node.parent
|
|
105
|
+
if parent is None:
|
|
106
|
+
return False
|
|
107
|
+
if parent.type == "argument" and parent.child_by_field_name("name") == node:
|
|
108
|
+
return True
|
|
109
|
+
field_name = next(
|
|
110
|
+
(parent.field_name_for_child(index) for index, child in enumerate(parent.children) if child == node),
|
|
111
|
+
None,
|
|
112
|
+
)
|
|
113
|
+
return (
|
|
114
|
+
parent.type.endswith("_expression") or parent.type in _EXPRESSION_CONTAINERS
|
|
115
|
+
) and field_name not in _NON_EXPRESSION_FIELDS
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
class AnalysisError(RuntimeError):
|
|
5
5
|
"""Base error suitable for Code Guard's existing exit-3 boundary."""
|
|
6
6
|
|
|
7
|
+
def __init__(self, message: str, *, language: str | None = None) -> None:
|
|
8
|
+
super().__init__(message)
|
|
9
|
+
self.language = language
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
class ProviderUnavailableError(AnalysisError):
|
|
9
13
|
"""The configured parser provider or a required grammar is unavailable."""
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import Mapping
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
@dataclass(frozen=True, order=True)
|
|
@@ -82,11 +84,20 @@ class FileFacts:
|
|
|
82
84
|
controls: tuple[ControlFlowFact, ...]
|
|
83
85
|
decisions: tuple[DecisionFact, ...]
|
|
84
86
|
region_count: int
|
|
87
|
+
reporting_path: str | None = None
|
|
85
88
|
|
|
86
89
|
|
|
87
90
|
@dataclass(frozen=True)
|
|
88
91
|
class AnalysisFacts:
|
|
89
92
|
files: tuple[FileFacts, ...]
|
|
93
|
+
_reporting_paths: Mapping[Path, str] = field(init=False, repr=False, compare=False)
|
|
94
|
+
|
|
95
|
+
def __post_init__(self) -> None:
|
|
96
|
+
object.__setattr__(self, "_reporting_paths", MappingProxyType({
|
|
97
|
+
file.path: file.reporting_path
|
|
98
|
+
for file in self.files
|
|
99
|
+
if file.reporting_path is not None
|
|
100
|
+
}))
|
|
90
101
|
|
|
91
102
|
@property
|
|
92
103
|
def callables(self) -> tuple[CallableFact, ...]:
|
|
@@ -99,3 +110,12 @@ class AnalysisFacts:
|
|
|
99
110
|
@property
|
|
100
111
|
def decisions(self) -> tuple[DecisionFact, ...]:
|
|
101
112
|
return tuple(fact for file in self.files for fact in file.decisions)
|
|
113
|
+
|
|
114
|
+
def reporting_path_for(self, path: Path, root: Path | None = None) -> str:
|
|
115
|
+
stored = self._reporting_paths.get(path)
|
|
116
|
+
if stored is not None:
|
|
117
|
+
return stored
|
|
118
|
+
try:
|
|
119
|
+
return path.relative_to(root).as_posix() if root is not None else path.as_posix()
|
|
120
|
+
except ValueError:
|
|
121
|
+
return path.as_posix()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Parse applicable selected files once and return reusable immutable facts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..invocation import SelectedFile
|
|
9
|
+
|
|
10
|
+
from .adapters import extract_facts
|
|
11
|
+
from .csharp_compat import corrected_csharp_root
|
|
12
|
+
from .errors import ProviderUnavailableError, SyntaxAnalysisError
|
|
13
|
+
from .facts import AnalysisFacts, FileFacts
|
|
14
|
+
from .provider import ParserProvider, TreeSitterProvider
|
|
15
|
+
from .regions import executable_regions, is_applicable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def analyze_files(files: tuple[SelectedFile, ...] | list[SelectedFile], provider: ParserProvider | None = None) -> AnalysisFacts:
|
|
19
|
+
"""Analyze only applicable entries from the already-resolved caller scope."""
|
|
20
|
+
active_provider = provider or TreeSitterProvider()
|
|
21
|
+
results = [
|
|
22
|
+
_analyze_file(selected.physical_path, active_provider, selected.reporting_path)
|
|
23
|
+
for selected in files if is_applicable(selected.physical_path)
|
|
24
|
+
]
|
|
25
|
+
return AnalysisFacts(tuple(results))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class UnavailableAnalysis:
|
|
30
|
+
path: Path
|
|
31
|
+
reporting_path: str
|
|
32
|
+
language: str
|
|
33
|
+
kind: str
|
|
34
|
+
message: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class BatchAnalysis:
|
|
39
|
+
facts: AnalysisFacts
|
|
40
|
+
unavailable: tuple[UnavailableAnalysis, ...]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def analyze_files_for_runner(
|
|
44
|
+
files: tuple[SelectedFile, ...], provider: ParserProvider | None = None,
|
|
45
|
+
) -> BatchAnalysis:
|
|
46
|
+
"""Analyze selected files independently while retaining only known unavailable evidence."""
|
|
47
|
+
active_provider = provider or TreeSitterProvider()
|
|
48
|
+
results: list[FileFacts] = []
|
|
49
|
+
unavailable: list[UnavailableAnalysis] = []
|
|
50
|
+
for selected in files:
|
|
51
|
+
path = selected.physical_path
|
|
52
|
+
if not is_applicable(path):
|
|
53
|
+
continue
|
|
54
|
+
try:
|
|
55
|
+
results.append(_analyze_file(path, active_provider, selected.reporting_path))
|
|
56
|
+
except (SyntaxAnalysisError, ProviderUnavailableError) as exc:
|
|
57
|
+
if exc.language is None:
|
|
58
|
+
raise
|
|
59
|
+
kind = "syntax" if isinstance(exc, SyntaxAnalysisError) else "provider"
|
|
60
|
+
unavailable.append(UnavailableAnalysis(path, selected.reporting_path, exc.language, kind, str(exc)))
|
|
61
|
+
return BatchAnalysis(AnalysisFacts(tuple(results)), tuple(unavailable))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _analyze_file(path: Path, provider: ParserProvider, reporting_path: str | None = None) -> FileFacts:
|
|
65
|
+
callables = []
|
|
66
|
+
controls = []
|
|
67
|
+
decisions = []
|
|
68
|
+
regions = executable_regions(path, provider)
|
|
69
|
+
for region in regions:
|
|
70
|
+
try:
|
|
71
|
+
tree = provider.parse(region.language, region.source)
|
|
72
|
+
except (SyntaxAnalysisError, ProviderUnavailableError) as exc:
|
|
73
|
+
if exc.language is None:
|
|
74
|
+
raise type(exc)(str(exc), language=region.language) from exc
|
|
75
|
+
raise
|
|
76
|
+
root = tree.root_node
|
|
77
|
+
if tree.root_node.has_error:
|
|
78
|
+
root = (
|
|
79
|
+
corrected_csharp_root(provider, region.source, tree)
|
|
80
|
+
if region.language == "csharp"
|
|
81
|
+
else None
|
|
82
|
+
)
|
|
83
|
+
if root is None:
|
|
84
|
+
raise SyntaxAnalysisError(
|
|
85
|
+
f"unable to parse {path}: embedded {region.language} syntax tree contains errors",
|
|
86
|
+
language=region.language,
|
|
87
|
+
)
|
|
88
|
+
region_callables, region_controls, region_decisions = extract_facts(root, region)
|
|
89
|
+
callables.extend(region_callables)
|
|
90
|
+
controls.extend(region_controls)
|
|
91
|
+
decisions.extend(region_decisions)
|
|
92
|
+
return FileFacts(path, tuple(callables), tuple(controls), tuple(decisions), len(regions), reporting_path)
|
|
@@ -33,13 +33,15 @@ class TreeSitterProvider:
|
|
|
33
33
|
except (ImportError, LookupError, OSError, RuntimeError) as exc:
|
|
34
34
|
raise ProviderUnavailableError(
|
|
35
35
|
f"syntax provider unavailable for supported language {language!r}: {exc}; "
|
|
36
|
-
"reinstall Agent Code Guard"
|
|
36
|
+
"reinstall Agent Code Guard",
|
|
37
|
+
language=language,
|
|
37
38
|
) from exc
|
|
38
39
|
self._parsers[language] = parser
|
|
39
40
|
try:
|
|
40
41
|
return parser.parse(source)
|
|
41
|
-
except
|
|
42
|
+
except RuntimeError as exc:
|
|
42
43
|
raise ProviderUnavailableError(
|
|
43
44
|
f"syntax provider failed for supported language {language!r}: {exc}; "
|
|
44
|
-
"verify the Agent Code Guard installation"
|
|
45
|
+
"verify the Agent Code Guard installation",
|
|
46
|
+
language=language,
|
|
45
47
|
) from exc
|