sourcecode 4.10.4__py3-none-any.whl → 4.10.6__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.
Potentially problematic release.
This version of sourcecode might be problematic. Click here for more details.
- sourcecode/__init__.py +1 -1
- sourcecode/architectural_baseline.py +80 -1
- sourcecode/cache_model.py +11 -4
- sourcecode/change_plan.py +43 -8
- sourcecode/cli.py +376 -55
- sourcecode/compare.py +91 -11
- sourcecode/container_wiring.py +315 -0
- sourcecode/context_cache.py +63 -0
- sourcecode/data_exposure.py +17 -0
- sourcecode/detach.py +155 -0
- sourcecode/non_coverage.py +54 -0
- sourcecode/output_budget.py +26 -3
- sourcecode/output_encoding.py +51 -0
- sourcecode/phased_run.py +210 -0
- sourcecode/pr_impact.py +107 -6
- sourcecode/prepare_context.py +38 -4
- sourcecode/progress.py +156 -9
- sourcecode/repository_ir.py +113 -3
- sourcecode/runs.py +8 -0
- sourcecode/security_config_scan.py +30 -22
- sourcecode/security_posture.py +28 -0
- sourcecode/serializer.py +6 -1
- sourcecode/servlet_surface.py +17 -1
- sourcecode/spring_impact.py +38 -0
- sourcecode/spring_security_audit.py +21 -5
- sourcecode/text_input.py +93 -0
- {sourcecode-4.10.4.dist-info → sourcecode-4.10.6.dist-info}/METADATA +12 -6
- {sourcecode-4.10.4.dist-info → sourcecode-4.10.6.dist-info}/RECORD +31 -26
- {sourcecode-4.10.4.dist-info → sourcecode-4.10.6.dist-info}/WHEEL +0 -0
- {sourcecode-4.10.4.dist-info → sourcecode-4.10.6.dist-info}/entry_points.txt +0 -0
- {sourcecode-4.10.4.dist-info → sourcecode-4.10.6.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
|
@@ -136,6 +136,19 @@ def _fan_in_from_stored(value: Any) -> dict[str, int]:
|
|
|
136
136
|
return {}
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def _comparability_env() -> dict:
|
|
140
|
+
"""The environment fields a delta actually depends on (C3-68).
|
|
141
|
+
|
|
142
|
+
A baseline is comparable to another when the platform and the tool agree;
|
|
143
|
+
the CPU model, the core count and a hash of the hostname change none of
|
|
144
|
+
that. `--record-env` restores the full set for someone benchmarking runs
|
|
145
|
+
against each other on one machine.
|
|
146
|
+
"""
|
|
147
|
+
import sys as _sys
|
|
148
|
+
|
|
149
|
+
return {"os": _sys.platform, "tool_version": TOOL_VERSION, "recorded": "comparability_only"}
|
|
150
|
+
|
|
151
|
+
|
|
139
152
|
def build_baseline(
|
|
140
153
|
cir: "CanonicalRepositoryIR",
|
|
141
154
|
*,
|
|
@@ -145,6 +158,7 @@ def build_baseline(
|
|
|
145
158
|
captured_at: str | None = None,
|
|
146
159
|
env: dict | None = None,
|
|
147
160
|
tool_version: str | None = None,
|
|
161
|
+
record_env: bool = False,
|
|
148
162
|
) -> dict:
|
|
149
163
|
"""Freeze a CIR's measured architectural metrics into a baseline artifact.
|
|
150
164
|
|
|
@@ -168,6 +182,21 @@ def build_baseline(
|
|
|
168
182
|
"files": m.file_count,
|
|
169
183
|
"symbols": m.symbol_count,
|
|
170
184
|
"endpoints": m.endpoint_count,
|
|
185
|
+
# C1-37: this artefact's declared purpose is to be a replayable
|
|
186
|
+
# fingerprint, and it disagreed with itself — `totals.endpoints`
|
|
187
|
+
# 3 574 against `len(endpoint_surface)` 3 538 on the field
|
|
188
|
+
# repository. Neither figure was wrong: `endpoints` counts handler
|
|
189
|
+
# mappings and `endpoint_surface` is the set of distinct
|
|
190
|
+
# (METHOD, path) contract pairs, so 36 routes are declared by more
|
|
191
|
+
# than one handler. Two facts, two names, and the invariant the
|
|
192
|
+
# battery can actually hold.
|
|
193
|
+
"endpoint_contract_pairs": len(m.endpoints),
|
|
194
|
+
"endpoints_unit": (
|
|
195
|
+
"handler mappings declared in the repository; "
|
|
196
|
+
"`endpoint_contract_pairs` counts the distinct (METHOD, path) "
|
|
197
|
+
"pairs those mappings expose, which is what `endpoint_surface` "
|
|
198
|
+
"lists and what a diff compares"
|
|
199
|
+
),
|
|
171
200
|
"dependency_edges": m.dependency_edge_count,
|
|
172
201
|
"import_cycles": len(m.cycles),
|
|
173
202
|
},
|
|
@@ -175,7 +204,16 @@ def build_baseline(
|
|
|
175
204
|
"endpoint_surface": endpoints,
|
|
176
205
|
"fan_in": _fan_in_to_list(m.fan_in),
|
|
177
206
|
"import_cycles": cycles,
|
|
178
|
-
|
|
207
|
+
# C3-68: this artefact is written inside the work tree, so what it
|
|
208
|
+
# records is what a `git add -A` publishes. `hostname_hash`, `cpu` and
|
|
209
|
+
# `cores` describe the developer's machine and affect nothing a delta
|
|
210
|
+
# compares — from a tool whose own `ask config` says telemetry is off.
|
|
211
|
+
# The default is now the two fields that DO decide comparability; the
|
|
212
|
+
# rest is opt-in per capture.
|
|
213
|
+
"env": (
|
|
214
|
+
env if env is not None
|
|
215
|
+
else (collect_env() if record_env else _comparability_env())
|
|
216
|
+
),
|
|
179
217
|
"provenance": (
|
|
180
218
|
"architectural_baseline (D5): a measured structural fingerprint of one "
|
|
181
219
|
"CIR, persisted per ref. Metric content is a pure function of the repo "
|
|
@@ -319,9 +357,50 @@ def _baseline_filename(baseline: dict) -> str:
|
|
|
319
357
|
return f"{safe}.json"
|
|
320
358
|
|
|
321
359
|
|
|
360
|
+
#: What `.ask/` gets the moment it is created, and why.
|
|
361
|
+
#:
|
|
362
|
+
#: C3-68: `baseline capture` writes into the work tree and nothing protected it.
|
|
363
|
+
#: The field verified `grep -nE "^\.ask|sourcecode" .gitignore` → no matches, so
|
|
364
|
+
#: a `git add -A` commits the artefact — which carries a machine fingerprint and
|
|
365
|
+
#: a 14 587-entry structural inventory of the code. A self-confined
|
|
366
|
+
#: `.ask/.gitignore` is the honest form: it never touches the user's own
|
|
367
|
+
#: `.gitignore`, and it stops applying the moment they delete the directory.
|
|
368
|
+
_ASK_DIR_GITIGNORE = (
|
|
369
|
+
"# Written by `ask` the first time it created this directory.\n"
|
|
370
|
+
"# These are local analysis artefacts: a structural inventory of your code\n"
|
|
371
|
+
"# and, if you asked for it, the machine that measured it. Nothing here\n"
|
|
372
|
+
"# belongs in a commit. Delete this file to version them deliberately.\n"
|
|
373
|
+
"*\n"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def protect_ask_dir(out_dir: Path) -> None:
|
|
378
|
+
"""Give a freshly created `.ask/` its own `.gitignore` (C3-68).
|
|
379
|
+
|
|
380
|
+
Best-effort and never overwriting: a user who edited it decided something,
|
|
381
|
+
and a tool that re-imposes its default on every run is worse than one that
|
|
382
|
+
never wrote it.
|
|
383
|
+
"""
|
|
384
|
+
try:
|
|
385
|
+
ask_dir = Path(out_dir)
|
|
386
|
+
while ask_dir.name and ask_dir.name != ".ask":
|
|
387
|
+
if ask_dir.parent == ask_dir:
|
|
388
|
+
return
|
|
389
|
+
ask_dir = ask_dir.parent
|
|
390
|
+
if ask_dir.name != ".ask":
|
|
391
|
+
return
|
|
392
|
+
marker = ask_dir / ".gitignore"
|
|
393
|
+
if not marker.exists():
|
|
394
|
+
ask_dir.mkdir(parents=True, exist_ok=True)
|
|
395
|
+
marker.write_text(_ASK_DIR_GITIGNORE, encoding="utf-8")
|
|
396
|
+
except OSError:
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
|
|
322
400
|
def write_baseline(baseline: dict, out_dir: Path) -> Path:
|
|
323
401
|
"""Write `baseline` as deterministic JSON under `out_dir`; return the file path."""
|
|
324
402
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
403
|
+
protect_ask_dir(out_dir)
|
|
325
404
|
path = out_dir / _baseline_filename(baseline)
|
|
326
405
|
path.write_text(
|
|
327
406
|
json.dumps(baseline, sort_keys=True, indent=2) + "\n", encoding="utf-8"
|
sourcecode/cache_model.py
CHANGED
|
@@ -202,10 +202,17 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
202
202
|
"`refactor`, `fix-bug` and `generate-tests` cache their own answer, but a warm does "
|
|
203
203
|
"not run them, so their first call pays full price; `delta` and `review-pr` are "
|
|
204
204
|
"diff-dependent and never cached.",
|
|
205
|
-
"onboard 6.5 s → 0.3 s
|
|
206
|
-
"
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
"onboard 6.5 s → 0.3 s with a warm — but a second run with NO warm recomputes "
|
|
206
|
+
"(10.2 s → 9.4 s at 5 486 files; C1-40) · refactor 7.7 s → 7.7 s (0.3 s on "
|
|
207
|
+
"repeat) · generate-tests 12.4 s → 11.0 s (0.3 s on repeat)"),
|
|
208
|
+
CommandCache("onboard", ("task", "ris"), "answer", False,
|
|
209
|
+
"Shorthand for `prepare-context onboard`. A warm stores its answer; the command "
|
|
210
|
+
"does NOT store its own, so a second run without a warm pays full price. C1-40: "
|
|
211
|
+
"the field read `repeat cached` here, ran it twice with no warm, and measured "
|
|
212
|
+
"56 s then 23,5 s — this row said it would be a hit.",
|
|
213
|
+
"with a warm: 6.5 s → 0.3 s (2 000 files) · 10.2 s → 0.7 s (5 486 files). "
|
|
214
|
+
"WITHOUT a warm, a second identical run: 10.2 s → 9.4 s (5 486 files) — "
|
|
215
|
+
"no answer hit"),
|
|
209
216
|
CommandCache("explain", ("cir",), "shared", False, "Serves from the shared CIR a warm builds.",
|
|
210
217
|
"9.8 s → 1.6 s"),
|
|
211
218
|
CommandCache("export", ("parse",), "shared", False, "", "8.8 s → 3.7 s"),
|
sourcecode/change_plan.py
CHANGED
|
@@ -93,17 +93,31 @@ def build_change_plan(
|
|
|
93
93
|
from sourcecode.target_admission import is_unresolved
|
|
94
94
|
|
|
95
95
|
if is_unresolved(resolution) or not blast.get("matched_fqns"):
|
|
96
|
+
# C1-39: this is a well-formed `change-plan-v1`, not an error envelope,
|
|
97
|
+
# so an agent reads its fields — and every cost used to be `0`. Nothing
|
|
98
|
+
# was measured, and `0` is the answer to a different question. The
|
|
99
|
+
# product already gets this right elsewhere (`data-exposure` answers
|
|
100
|
+
# `answered: false` rather than measuring nothing and publishing a
|
|
101
|
+
# confident zero); this is the same rule, one level up.
|
|
102
|
+
_not_measured = {
|
|
103
|
+
"answered": False,
|
|
104
|
+
"reason": f"target {target!r} did not resolve — nothing was measured",
|
|
105
|
+
}
|
|
96
106
|
return {
|
|
97
107
|
"schema": CHANGE_PLAN_SCHEMA,
|
|
98
108
|
"target": target,
|
|
99
109
|
"resolution": resolution,
|
|
100
110
|
"message": blast.get("message", f"Target {target!r} not found."),
|
|
101
111
|
"candidates": blast.get("candidates", []),
|
|
102
|
-
"affected_components": {"count":
|
|
103
|
-
"covering_tests": {"count":
|
|
104
|
-
"affected_endpoints": {"count":
|
|
105
|
-
"rollback_surface": {"file_count":
|
|
112
|
+
"affected_components": {"count": None, "components": [], **_not_measured},
|
|
113
|
+
"covering_tests": {"count": None, "tests": [], **_not_measured},
|
|
114
|
+
"affected_endpoints": {"count": None, "endpoints": [], **_not_measured},
|
|
115
|
+
"rollback_surface": {"file_count": None, "files": [], **_not_measured},
|
|
106
116
|
"review_checklist": [],
|
|
117
|
+
"how_to_read": (
|
|
118
|
+
"Every cost here is `null`, not `0`: the target did not resolve, so "
|
|
119
|
+
"nothing was measured. A zero would say the change is free."
|
|
120
|
+
),
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
seeds = list(blast["matched_fqns"])
|
|
@@ -148,10 +162,21 @@ def build_change_plan(
|
|
|
148
162
|
checklist.append(
|
|
149
163
|
f"Re-verify the contract of {len(ep_strings)} endpoint(s) in the blast radius."
|
|
150
164
|
)
|
|
165
|
+
# C1-39, second half: on a repository with no test source root at all — the
|
|
166
|
+
# field one had 0 test files for 3 337 non-test Java sources — "0 covering
|
|
167
|
+
# tests" reads as *the change is safe* when it means *there are no tests*.
|
|
168
|
+
_has_test_root = any(
|
|
169
|
+
is_test_path(str(m.get("file") or "")) for m in meta.values()
|
|
170
|
+
)
|
|
151
171
|
if tests:
|
|
152
172
|
checklist.append(
|
|
153
173
|
f"Run {len(tests)} covering test symbol(s) that reach the change."
|
|
154
174
|
)
|
|
175
|
+
elif not _has_test_root:
|
|
176
|
+
checklist.append(
|
|
177
|
+
"Covering tests were not measured: this repository has no test source "
|
|
178
|
+
"root, so `covering_tests.count` is null rather than 0."
|
|
179
|
+
)
|
|
155
180
|
else:
|
|
156
181
|
checklist.append(
|
|
157
182
|
"No covering tests reach the change — no existing test exercises this blast radius."
|
|
@@ -178,10 +203,20 @@ def build_change_plan(
|
|
|
178
203
|
"count": len(prod),
|
|
179
204
|
"components": prod[:_LIST_CAP],
|
|
180
205
|
},
|
|
181
|
-
"covering_tests":
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
"covering_tests": (
|
|
207
|
+
{
|
|
208
|
+
"count": len(tests),
|
|
209
|
+
"tests": tests[:_LIST_CAP],
|
|
210
|
+
"answered": True,
|
|
211
|
+
}
|
|
212
|
+
if _has_test_root else
|
|
213
|
+
{
|
|
214
|
+
"count": None,
|
|
215
|
+
"tests": [],
|
|
216
|
+
"answered": False,
|
|
217
|
+
"reason": "no test source root in this repository — nothing to measure",
|
|
218
|
+
}
|
|
219
|
+
),
|
|
185
220
|
"affected_endpoints": {
|
|
186
221
|
"count": len(ep_strings),
|
|
187
222
|
"endpoints": ep_strings[:_LIST_CAP],
|