sourcecode 4.17.0__py3-none-any.whl → 5.0.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.
Potentially problematic release.
This version of sourcecode might be problematic. Click here for more details.
- sourcecode/__init__.py +1 -1
- sourcecode/cache_model.py +199 -37
- sourcecode/cli.py +38 -17
- sourcecode/context_cache.py +94 -7
- sourcecode/detach.py +7 -3
- sourcecode/execution_plan.py +36 -19
- sourcecode/identity_fallback.py +6 -12
- sourcecode/perf.py +4 -2
- sourcecode/phased_run.py +10 -8
- sourcecode/posture.py +9 -14
- sourcecode/release_info.py +1 -1
- sourcecode/risk.py +29 -6
- sourcecode/security_config_scan.py +4 -8
- sourcecode/source_text.py +65 -0
- {sourcecode-4.17.0.dist-info → sourcecode-5.0.0.dist-info}/METADATA +5 -5
- {sourcecode-4.17.0.dist-info → sourcecode-5.0.0.dist-info}/RECORD +19 -19
- {sourcecode-4.17.0.dist-info → sourcecode-5.0.0.dist-info}/WHEEL +0 -0
- {sourcecode-4.17.0.dist-info → sourcecode-5.0.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-4.17.0.dist-info → sourcecode-5.0.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cache_model.py
CHANGED
|
@@ -74,11 +74,11 @@ class CommandCache:
|
|
|
74
74
|
cold_seconds: "Optional[float]" = None
|
|
75
75
|
warm_seconds: "Optional[float]" = None
|
|
76
76
|
#: The other measured point: this command on the field repository
|
|
77
|
-
#: (3 342 Java files, Windows 11 / PowerShell 5.1 / pipx,
|
|
78
|
-
#: evaluations
|
|
79
|
-
#:
|
|
80
|
-
#:
|
|
81
|
-
#:
|
|
77
|
+
#: (3 342 Java files, Windows 11 / PowerShell 5.1 / pipx, warm), from the
|
|
78
|
+
#: field evaluations. Seconds where a run finished; `field_blocked` where it
|
|
79
|
+
#: did not — the harness promoted the process and the session died, which is a
|
|
80
|
+
#: measurement of a different kind and is never written as a duration. Rows
|
|
81
|
+
#: with neither were not attempted there.
|
|
82
82
|
#:
|
|
83
83
|
#: C3-72: where two evaluations of the same command at this size disagree, the
|
|
84
84
|
#: figure here is the **most recent** one, because it describes the build a
|
|
@@ -88,6 +88,17 @@ class CommandCache:
|
|
|
88
88
|
#: measurement C3-76 exists for.
|
|
89
89
|
field_seconds: "Optional[float]" = None
|
|
90
90
|
field_blocked: bool = False
|
|
91
|
+
#: **C3-97.** The build the field figure was taken on. An anchor without one is
|
|
92
|
+
#: a measurement that can outlive its build and keep deciding: 4.18.0 deleted
|
|
93
|
+
#: two catastrophic-backtracking patterns (C3-95, C3-96) and every figure taken
|
|
94
|
+
#: before it describes a cost that no longer exists — `spring-audit` at 2 351 s
|
|
95
|
+
#: where the same command on the same commit of the same repository now takes
|
|
96
|
+
#: 8,4 s. Anchors older than the running build are still published, because a
|
|
97
|
+
#: measurement is worth more than silence; what changes is that they are
|
|
98
|
+
#: **labelled** with the build they belong to, so a reader can see the figure is
|
|
99
|
+
#: not this one's. `field_anchor_note()` renders that label and
|
|
100
|
+
#: `execution_plan` appends it to the `here:` line.
|
|
101
|
+
field_measured_version: "Optional[str]" = None
|
|
91
102
|
|
|
92
103
|
|
|
93
104
|
#: Where every `measured` figure comes from.
|
|
@@ -109,26 +120,38 @@ REFERENCE_JAVA_FILES = 2000
|
|
|
109
120
|
REFERENCE_MEASURED_VERSION = "3.2.2"
|
|
110
121
|
|
|
111
122
|
#: The other end of the measured range, and the reason this module publishes a
|
|
112
|
-
#: *class* rather than a projected duration (C4-19). Field evaluation #
|
|
123
|
+
#: *class* rather than a projected duration (C4-19). Field evaluation #22, on
|
|
113
124
|
#: Windows 11 / PowerShell 5.1 / pipx / Python 3.10: `spring-audit` on a
|
|
114
|
-
#: 3 342-file repository ran **
|
|
115
|
-
#:
|
|
116
|
-
#: scale with file count in any way this product has measured, so a projected
|
|
117
|
-
#: "~15 s on your repository" would be a confident falsehood of exactly the kind
|
|
118
|
-
#: the ledger exists to prevent. What can be said honestly is the class, the two
|
|
119
|
-
#: measured anchors, and how to run it.
|
|
125
|
+
#: 3 342-file repository ran **8,4 s** — the same command takes 8.8 s on the
|
|
126
|
+
#: 2 000-file reference, for a repository 1,7× the size.
|
|
120
127
|
#:
|
|
121
|
-
#:
|
|
122
|
-
#:
|
|
123
|
-
#:
|
|
124
|
-
#:
|
|
125
|
-
#:
|
|
128
|
+
#: **The refusal to project stands, and this evaluation strengthened it** (C3-97).
|
|
129
|
+
#: For eight releases the pair of anchors said *cost does not track file count*;
|
|
130
|
+
#: what the series actually shows is that **cost tracks the build**. The identical
|
|
131
|
+
#: command on the identical commit of the identical repository measured 2 557 s
|
|
132
|
+
#: (4.10.6), 2 398 s (4.10.7), 2 351 s (4.11.0), 2 377 s (4.12.0), 1 371 s
|
|
133
|
+
#: (4.16.0) and 8,4 s (4.18.0) — a 280× move produced by deleting two
|
|
134
|
+
#: catastrophic-backtracking patterns (C3-95, C3-96), with every payload
|
|
135
|
+
#: byte-identical throughout. A projection would therefore have to predict our own
|
|
136
|
+
#: next release, which is the confident falsehood the ledger exists to prevent.
|
|
137
|
+
#: What can be said honestly is the class, the two measured anchors, **the build
|
|
138
|
+
#: each was measured on**, and how to run it.
|
|
139
|
+
#:
|
|
140
|
+
#: History of this constant, kept because it is the measurement C3-76 exists for:
|
|
141
|
+
#: 408 s (#13) → 2 351 s (#16, C3-72) → 8,4 s (#22, C3-97). The middle figure was
|
|
142
|
+
#: quoted as an operational anchor for four releases after the build that produced
|
|
143
|
+
#: it, and told operators to detach commands that finish in seconds.
|
|
126
144
|
FIELD_ANCHOR = (
|
|
127
|
-
"field evaluation #
|
|
145
|
+
"field evaluation #22: spring-audit on 3 342 Java files took 8.4 s "
|
|
128
146
|
"(Windows, pipx, warm cache) against 8.8 s on the 2 000-file reference"
|
|
129
147
|
)
|
|
130
148
|
FIELD_ANCHOR_JAVA_FILES = 3342
|
|
131
|
-
FIELD_ANCHOR_SECONDS =
|
|
149
|
+
FIELD_ANCHOR_SECONDS = 8.4
|
|
150
|
+
|
|
151
|
+
#: The build the field anchors above were taken on. Rows carrying a different
|
|
152
|
+
#: `field_measured_version` are published with that build's name attached rather
|
|
153
|
+
#: than as figures for this one (C3-97).
|
|
154
|
+
FIELD_ANCHOR_MEASURED_VERSION = "4.18.0"
|
|
132
155
|
|
|
133
156
|
|
|
134
157
|
#: Every layer keys on `cache.worktree_signature` — the exact tree state (C1-9).
|
|
@@ -196,12 +219,12 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
196
219
|
"`--env-map`, `--depth N` and `--exclude` change the *analysis*, so they miss the "
|
|
197
220
|
"warmed core and rescan — this is the 171 s the field measured after a 103 s warm.",
|
|
198
221
|
"--compact 13.3 s cold → 0.3 s warm (cold re-measured on 3.7.0: was 19.3 s, C3-6); "
|
|
199
|
-
"--agent --full --env-map --depth 20 34.7 s → 33.9 s (no gain)", analysis_class="repo-wide", cold_seconds=13.3, warm_seconds=0.3, field_seconds=115.0),
|
|
222
|
+
"--agent --full --env-map --depth 20 34.7 s → 33.9 s (no gain)", analysis_class="repo-wide", cold_seconds=13.3, warm_seconds=0.3, field_seconds=115.0, field_measured_version="4.10.4"),
|
|
200
223
|
CommandCache("posture", ("cir", "parse"), "shared", False,
|
|
201
224
|
"Resolves the conditional bean graph on every run, over the shared CIR a warm "
|
|
202
225
|
"builds — the parse it used to repeat for itself. `--diff` compares two profile "
|
|
203
226
|
"sets over that one IR, so the second side costs the resolution only.",
|
|
204
|
-
"10.1 s → 1.6 s", analysis_class="repo-wide", cold_seconds=10.1, warm_seconds=1.6, field_seconds=
|
|
227
|
+
"10.1 s → 1.6 s", analysis_class="repo-wide", cold_seconds=10.1, warm_seconds=1.6, field_seconds=8.0, field_measured_version="4.18.0"),
|
|
205
228
|
CommandCache("risk", ("cir", "parse"), "shared", False,
|
|
206
229
|
"Composes what the audit, impact-chain and the posture already answer, so it "
|
|
207
230
|
"pays each of their costs once over the shared CIR a warm builds — one parse "
|
|
@@ -209,7 +232,7 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
209
232
|
"within the run.",
|
|
210
233
|
"not measured on the battery yet — the composition is bounded by the "
|
|
211
234
|
"`spring-audit` + `impact-chain` costs listed here, not by new analysis",
|
|
212
|
-
analysis_class="deep", field_seconds=
|
|
235
|
+
analysis_class="deep", field_seconds=73.3, field_measured_version="4.18.0"),
|
|
213
236
|
CommandCache("enrich", ("cir", "parse"), "shared", False,
|
|
214
237
|
"Runs the same composition as `risk` over the repository, then joins a SARIF "
|
|
215
238
|
"log to it. Reading the log is negligible; everything a warm helps with is the "
|
|
@@ -239,25 +262,31 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
239
262
|
"Recomputes the endpoint surface on every run, over a parse a warm has already "
|
|
240
263
|
"paid for. Until 3.7.0 the extractor parsed every file itself instead of reading "
|
|
241
264
|
"the shared parse cache, and a warm measurably bought it nothing (C3-6).",
|
|
242
|
-
"3.3 s → 1.4 s (re-measured on 3.7.0; was 2.8 s → 2.9 s)", analysis_class="repo-wide", cold_seconds=3.3, warm_seconds=1.4, field_seconds=
|
|
265
|
+
"3.3 s → 1.4 s (re-measured on 3.7.0; was 2.8 s → 2.9 s)", analysis_class="repo-wide", cold_seconds=3.3, warm_seconds=1.4, field_seconds=3.8, field_measured_version="4.18.0"),
|
|
243
266
|
CommandCache("spring-audit", ("ris", "parse"), "shared", False,
|
|
244
267
|
"Recomputes every run, but over a parse a warm has already paid for.",
|
|
245
|
-
"8.8 s → 3.7 s", analysis_class="repo-wide", cold_seconds=8.8, warm_seconds=3.7, field_seconds=
|
|
268
|
+
"8.8 s → 3.7 s", analysis_class="repo-wide", cold_seconds=8.8, warm_seconds=3.7, field_seconds=8.4, field_measured_version="4.18.0"),
|
|
246
269
|
CommandCache("migrate-check", ("cir",), "none", False,
|
|
247
270
|
"Computes its own inventory and shares nothing a warm builds. Only `--blast-radius` "
|
|
248
271
|
"reuses the shared CIR.",
|
|
249
|
-
"4.8 s → 4.8 s", analysis_class="repo-wide", cold_seconds=4.8, warm_seconds=4.8, field_seconds=
|
|
272
|
+
"4.8 s → 4.8 s", analysis_class="repo-wide", cold_seconds=4.8, warm_seconds=4.8, field_seconds=10.2, field_measured_version="4.18.0"),
|
|
250
273
|
CommandCache("impact-chain", ("cir", "parse"), "shared", False,
|
|
251
274
|
"The CIR is the expensive half — this is where a warm pays most.",
|
|
252
|
-
"9.9 s → 1.7 s", analysis_class="core", cold_seconds=9.9, warm_seconds=1.7
|
|
253
|
-
|
|
275
|
+
"9.9 s → 1.7 s", analysis_class="core", cold_seconds=9.9, warm_seconds=1.7,
|
|
276
|
+
field_seconds=5.5, field_measured_version="4.18.0"),
|
|
277
|
+
CommandCache("impact", ("parse",), "shared", False, "", "4.9 s → 2.8 s", analysis_class="core", cold_seconds=4.9, warm_seconds=2.8,
|
|
278
|
+
field_seconds=7.7, field_measured_version="4.18.0"),
|
|
254
279
|
CommandCache("pr-impact", ("parse",), "shared", False,
|
|
255
|
-
"Diff-dependent: the answer itself is never stored.
|
|
280
|
+
"Diff-dependent: the answer itself is never stored. What it costs follows the "
|
|
281
|
+
"diff, not the repository: 12,5 s on an ordinary one and 11,8 s on a diff of "
|
|
282
|
+
"security configuration, measured at field scale.", analysis_class="core",
|
|
283
|
+
field_seconds=12.5, field_measured_version="4.18.0"),
|
|
256
284
|
CommandCache("verify", (), "none", False, "Runs the contracts against a fresh reading.", analysis_class="core"),
|
|
257
285
|
CommandCache("verify-edit", ("parse",), "shared", True,
|
|
258
286
|
"Built for the edit loop: the parse cache is what keeps an unchanged file out of the "
|
|
259
287
|
"next run. Its own second run is faster again.",
|
|
260
|
-
"14.6 s → 9.6 s → 5.6 s on repeat", analysis_class="repo-wide", cold_seconds=14.6, warm_seconds=9.6,
|
|
288
|
+
"14.6 s → 9.6 s → 5.6 s on repeat", analysis_class="repo-wide", cold_seconds=14.6, warm_seconds=9.6,
|
|
289
|
+
field_blocked=True, field_measured_version="4.10.4"),
|
|
261
290
|
CommandCache("review-pr", ("cir",), "none", False,
|
|
262
291
|
"Diff-dependent, and it reuses the CIR only if one exists. On a small diff, loading "
|
|
263
292
|
"the warmed CIR costs more than the work it saves.",
|
|
@@ -286,11 +315,18 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
286
315
|
"WITHOUT a warm, a second identical run: 10.2 s → 9.4 s (5 486 files) — "
|
|
287
316
|
"no answer hit", analysis_class="repo-wide", cold_seconds=6.5, warm_seconds=0.3),
|
|
288
317
|
CommandCache("explain", ("cir",), "shared", False, "Serves from the shared CIR a warm builds.",
|
|
289
|
-
"9.8 s → 1.6 s", analysis_class="core", cold_seconds=9.8, warm_seconds=1.6
|
|
318
|
+
"9.8 s → 1.6 s", analysis_class="core", cold_seconds=9.8, warm_seconds=1.6,
|
|
319
|
+
field_seconds=4.4, field_measured_version="4.18.0"),
|
|
290
320
|
CommandCache("export", ("parse",), "shared", False, "", "8.8 s → 3.7 s", analysis_class="repo-wide", cold_seconds=8.8, warm_seconds=3.7),
|
|
291
|
-
CommandCache("repo-ir", ("parse",), "shared", False, "", "5.1 s → 2.9 s", analysis_class="repo-wide", cold_seconds=5.1, warm_seconds=2.9,
|
|
292
|
-
|
|
293
|
-
CommandCache("
|
|
321
|
+
CommandCache("repo-ir", ("parse",), "shared", False, "", "5.1 s → 2.9 s", analysis_class="repo-wide", cold_seconds=5.1, warm_seconds=2.9,
|
|
322
|
+
field_blocked=True, field_measured_version="4.10.4"),
|
|
323
|
+
CommandCache("validation", ("parse",), "shared", False, "", "11.6 s → 6.4 s", analysis_class="repo-wide", cold_seconds=11.6, warm_seconds=6.4,
|
|
324
|
+
field_seconds=17.4, field_measured_version="4.18.0"),
|
|
325
|
+
CommandCache("modernize", ("parse",), "shared", False,
|
|
326
|
+
"Blocked in the session that recorded C3-53 and measured since: a run that did "
|
|
327
|
+
"not finish once is not a command that cannot finish.",
|
|
328
|
+
"5.2 s → 3.0 s", analysis_class="repo-wide", cold_seconds=5.2, warm_seconds=3.0,
|
|
329
|
+
field_seconds=9.1, field_measured_version="4.18.0"),
|
|
294
330
|
CommandCache("chunk-file", (), "none", False, "Reads one file; nothing to cache.", analysis_class="core"),
|
|
295
331
|
CommandCache("cold-start", ("ris",), "answer", True,
|
|
296
332
|
"Reads the RIS a warm rebuilds — that is all it does. Without one it answers "
|
|
@@ -298,7 +334,7 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
298
334
|
"0.2 s either way", analysis_class="core", cold_seconds=0.2, warm_seconds=0.2),
|
|
299
335
|
CommandCache("trend", (), "none", False, "Reads stored baseline artifacts from disk; analyses no source, so no cache layer applies. Same command as `baseline trend`.", analysis_class="none"),
|
|
300
336
|
CommandCache("baseline", ("parse",), "shared", False, "`capture`/`diff`/`trend` over architectural metrics.",
|
|
301
|
-
"capture 8.7 s → 3.6 s", analysis_class="repo-wide", cold_seconds=8.7, warm_seconds=3.6, field_seconds=11.0),
|
|
337
|
+
"capture 8.7 s → 3.6 s", analysis_class="repo-wide", cold_seconds=8.7, warm_seconds=3.6, field_seconds=11.0, field_measured_version="4.10.4"),
|
|
302
338
|
CommandCache("retrieve", ("cir", "parse"), "shared", False,
|
|
303
339
|
"Every query builds or reuses the shared CIR a warm builds.", analysis_class="core"),
|
|
304
340
|
CommandCache("archetype", ("parse",), "shared", False, "", "9.3 s → 5.6 s", analysis_class="repo-wide", cold_seconds=9.3, warm_seconds=5.6),
|
|
@@ -363,12 +399,102 @@ class Conditioning:
|
|
|
363
399
|
return (
|
|
364
400
|
f"This repository ({self.scope}) is larger than the repository the "
|
|
365
401
|
f"figures below were measured on ({REFERENCE_JAVA_FILES} Java files), "
|
|
366
|
-
f"and cost
|
|
402
|
+
f"and what cost tracks here is the build rather than the file count: "
|
|
367
403
|
f"{FIELD_ANCHOR}. Read each figure as the reference's, not as yours; "
|
|
368
404
|
f"the `here:` lines say how each command can be run on this one."
|
|
369
405
|
)
|
|
370
406
|
|
|
371
407
|
|
|
408
|
+
def field_anchor_note(row: "Optional[CommandCache]") -> "Optional[str]":
|
|
409
|
+
"""The build a row's field figure belongs to, when it is not this one (C3-97).
|
|
410
|
+
|
|
411
|
+
``None`` when there is nothing to qualify — no field figure, or one measured on
|
|
412
|
+
the running build. Otherwise a short parenthetical naming the build, because a
|
|
413
|
+
figure carrying another release's name is a measurement and a figure carrying
|
|
414
|
+
none is a claim about this one.
|
|
415
|
+
|
|
416
|
+
The figure is never suppressed. A measurement from four releases ago still
|
|
417
|
+
beats silence for a reader deciding how to run a command; what it must not do
|
|
418
|
+
is arrive undated, which is how `spring-audit`'s 2 351 s went on recommending
|
|
419
|
+
a nightly job for a command that had come to take 8,4 s.
|
|
420
|
+
"""
|
|
421
|
+
if row is None or (row.field_seconds is None and not row.field_blocked):
|
|
422
|
+
return None
|
|
423
|
+
from sourcecode import __version__
|
|
424
|
+
|
|
425
|
+
measured_on = row.field_measured_version
|
|
426
|
+
if measured_on is None:
|
|
427
|
+
return "build not recorded"
|
|
428
|
+
if measured_on == __version__:
|
|
429
|
+
return None
|
|
430
|
+
# Short on purpose: this rides along every `here:` line and every row of
|
|
431
|
+
# `cache model`, and the build is the whole fact. The sentence that explains
|
|
432
|
+
# what an older build means is `field_currency()['statement']`, printed once.
|
|
433
|
+
return f"on {measured_on}"
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def cost_sentence() -> str:
|
|
437
|
+
"""The one paragraph the front page says about cost, derived here (CL-21).
|
|
438
|
+
|
|
439
|
+
`--help` and the README used to carry their own hand-written version of it —
|
|
440
|
+
*"repo-wide/deep compositions can take minutes … use deep jobs nightly"* —
|
|
441
|
+
which stayed true for as long as the anchors behind it did and then went on
|
|
442
|
+
being printed for two more releases, recommending a nightly job for commands
|
|
443
|
+
that had come to finish in seconds. A sentence maintained beside the table it
|
|
444
|
+
describes is the second copy of a fact, which is the rule this module opens
|
|
445
|
+
with. It is generated from the anchors instead, so refreshing them refreshes
|
|
446
|
+
the prose.
|
|
447
|
+
"""
|
|
448
|
+
audit = next((r for r in COMMANDS if r.command == "spring-audit"), None)
|
|
449
|
+
seconds = FIELD_ANCHOR_SECONDS if audit is None or audit.field_seconds is None else audit.field_seconds
|
|
450
|
+
return (
|
|
451
|
+
f"Cost tracks the command class and the build, not the file count: "
|
|
452
|
+
f"per-symbol queries are the fast path, inventory commands scale with "
|
|
453
|
+
f"files, and a repository-wide audit measured {seconds:g} s on a "
|
|
454
|
+
f"{_thousands(FIELD_ANCHOR_JAVA_FILES)}-file repository "
|
|
455
|
+
f"({FIELD_ANCHOR_MEASURED_VERSION}). `ask cache model` prints the figure "
|
|
456
|
+
f"and the build behind it for every command; "
|
|
457
|
+
f"ASK_MAX_ANALYSIS_SECONDS/ASK_PROGRESS bound and narrate a run where CI "
|
|
458
|
+
f"wants an explicit budget."
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _thousands(n: int) -> str:
|
|
463
|
+
return f"{n:,}".replace(",", " ")
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def field_currency() -> dict:
|
|
467
|
+
"""The same statement as `reference_currency`, for the field anchors (C3-97).
|
|
468
|
+
|
|
469
|
+
Published so a consumer can see which side of the pair is current without
|
|
470
|
+
parsing prose, and so the gap is visible the day it opens rather than the day
|
|
471
|
+
an evaluation points at it.
|
|
472
|
+
"""
|
|
473
|
+
from sourcecode import __version__
|
|
474
|
+
|
|
475
|
+
current = __version__
|
|
476
|
+
stale = sorted(
|
|
477
|
+
row.command
|
|
478
|
+
for row in COMMANDS
|
|
479
|
+
if (row.field_seconds is not None or row.field_blocked)
|
|
480
|
+
and row.field_measured_version not in (None, current)
|
|
481
|
+
)
|
|
482
|
+
return {
|
|
483
|
+
"anchor": FIELD_ANCHOR,
|
|
484
|
+
"measured_on_version": FIELD_ANCHOR_MEASURED_VERSION,
|
|
485
|
+
"running_version": current,
|
|
486
|
+
"current": FIELD_ANCHOR_MEASURED_VERSION == current,
|
|
487
|
+
"rows_measured_on_an_earlier_build": stale,
|
|
488
|
+
"statement": (
|
|
489
|
+
f"The field anchors were taken on {FIELD_ANCHOR_MEASURED_VERSION}; you "
|
|
490
|
+
f"are running {current}. Each row states the build its field figure "
|
|
491
|
+
f"belongs to, and no figure is projected onto this one."
|
|
492
|
+
if FIELD_ANCHOR_MEASURED_VERSION != current
|
|
493
|
+
else f"The field anchors were taken on {current}, the build you are running."
|
|
494
|
+
),
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
|
|
372
498
|
def reference_currency() -> dict:
|
|
373
499
|
"""How old the reference figures are, in releases the reader can name (F-AP).
|
|
374
500
|
|
|
@@ -429,9 +555,10 @@ def as_dict(here: "Optional[Conditioning]" = None) -> dict:
|
|
|
429
555
|
# table nine releases old cannot read as this build's measurement.
|
|
430
556
|
"reference_currency": reference_currency(),
|
|
431
557
|
# Published beside the reference so no consumer reads the figures as a
|
|
432
|
-
# law: the same command
|
|
433
|
-
#
|
|
558
|
+
# law: the same command on the same commit of the same repository has
|
|
559
|
+
# measured 2 351 s and 8,4 s on two of our own builds (C4-19, C3-97).
|
|
434
560
|
"field_anchor": FIELD_ANCHOR,
|
|
561
|
+
"field_currency": field_currency(),
|
|
435
562
|
"commands": [
|
|
436
563
|
{
|
|
437
564
|
"command": cmd.command,
|
|
@@ -453,6 +580,20 @@ def as_dict(here: "Optional[Conditioning]" = None) -> dict:
|
|
|
453
580
|
if cmd.cold_seconds is None
|
|
454
581
|
else {"cold": cmd.cold_seconds, "warm": cmd.warm_seconds}
|
|
455
582
|
),
|
|
583
|
+
# C3-97: the field figure and the build it belongs to travel
|
|
584
|
+
# together, or not at all. A consumer that reads the seconds
|
|
585
|
+
# without the build is the defect this key exists to prevent.
|
|
586
|
+
"field_measurement": (
|
|
587
|
+
None
|
|
588
|
+
if cmd.field_seconds is None and not cmd.field_blocked
|
|
589
|
+
else {
|
|
590
|
+
"seconds": cmd.field_seconds,
|
|
591
|
+
"did_not_finish": cmd.field_blocked,
|
|
592
|
+
"java_files": FIELD_ANCHOR_JAVA_FILES,
|
|
593
|
+
"measured_on_version": cmd.field_measured_version,
|
|
594
|
+
"note": field_anchor_note(cmd),
|
|
595
|
+
}
|
|
596
|
+
),
|
|
456
597
|
}
|
|
457
598
|
for cmd in COMMANDS
|
|
458
599
|
],
|
|
@@ -478,6 +619,13 @@ def render_markdown() -> str:
|
|
|
478
619
|
f"performance gate (`docs/perf/REGRESSION-GATE.md`) is what re-measures them."
|
|
479
620
|
)
|
|
480
621
|
out.append("")
|
|
622
|
+
out.append(
|
|
623
|
+
f"The second anchor is the field one, and it carries its own build for the "
|
|
624
|
+
f"same reason (C3-97): {FIELD_ANCHOR}. Where a command was measured on an "
|
|
625
|
+
f"earlier release than the one you are running, `ask cache model` says so "
|
|
626
|
+
f"on that row rather than presenting the figure as this build's."
|
|
627
|
+
)
|
|
628
|
+
out.append("")
|
|
481
629
|
out.append("| Command | A warm gives it | Measured (nothing cached → after a warm) | Repeat run cached | Layers | Notes |")
|
|
482
630
|
out.append("|---|---|---|---|---|---|")
|
|
483
631
|
for cmd in COMMANDS:
|
|
@@ -556,12 +704,26 @@ def render_text(here: "Optional[Conditioning]" = None) -> str:
|
|
|
556
704
|
lines.append("What a warm gives each command")
|
|
557
705
|
lines.append(f" Timings: {REFERENCE_REPOSITORY}, each command measured in isolation.")
|
|
558
706
|
lines.append(f" {reference_currency()['statement']}")
|
|
707
|
+
lines.append(f" {field_currency()['statement']}")
|
|
559
708
|
width = max(len(c.command) for c in COMMANDS)
|
|
560
709
|
for cmd in COMMANDS:
|
|
561
710
|
repeat = "repeat cached" if cmd.repeat else "recomputes"
|
|
562
711
|
lines.append(f" {cmd.command.ljust(width)} {_WARM_LABEL[cmd.warm]:<16} ({repeat})")
|
|
563
712
|
if cmd.measured:
|
|
564
713
|
lines.append(f" {' ' * width} measured: {cmd.measured}")
|
|
714
|
+
if cmd.field_seconds is not None or cmd.field_blocked:
|
|
715
|
+
# C3-97: never the seconds without the build they were taken on.
|
|
716
|
+
note = field_anchor_note(cmd)
|
|
717
|
+
shown = (
|
|
718
|
+
"did not finish in an interactive session"
|
|
719
|
+
if cmd.field_seconds is None
|
|
720
|
+
else f"{cmd.field_seconds:g} s"
|
|
721
|
+
)
|
|
722
|
+
suffix = f" ({note})" if note else ""
|
|
723
|
+
lines.append(
|
|
724
|
+
f" {' ' * width} field: {shown} at "
|
|
725
|
+
f"{_thousands(FIELD_ANCHOR_JAVA_FILES)} Java files{suffix}"
|
|
726
|
+
)
|
|
565
727
|
if cmd.note:
|
|
566
728
|
lines.append(f" {' ' * width} {cmd.note}")
|
|
567
729
|
if here is not None and cmd.command in here.per_command:
|
sourcecode/cli.py
CHANGED
|
@@ -467,6 +467,25 @@ def _help_scope() -> "Optional[Any]":
|
|
|
467
467
|
return None
|
|
468
468
|
|
|
469
469
|
|
|
470
|
+
def _cost_sentence() -> str:
|
|
471
|
+
"""What the front page says about cost, from the module that measured it.
|
|
472
|
+
|
|
473
|
+
CL-21: this paragraph used to be written here, went stale with the anchors it
|
|
474
|
+
paraphrased, and kept telling operators to schedule a nightly job for commands
|
|
475
|
+
that finish in seconds. It is `cache_model`'s sentence now, so it cannot drift
|
|
476
|
+
from the table `ask cache model` prints.
|
|
477
|
+
"""
|
|
478
|
+
try:
|
|
479
|
+
from sourcecode.cache_model import cost_sentence
|
|
480
|
+
|
|
481
|
+
return cost_sentence()
|
|
482
|
+
except Exception:
|
|
483
|
+
return (
|
|
484
|
+
"Cost tracks the command class and the build: `ask cache model` prints "
|
|
485
|
+
"what each command measured, and on which build."
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
|
|
470
489
|
def _build_help_text(scope: "Optional[Any]" = None) -> str:
|
|
471
490
|
"""Build --help text dynamically based on current license state."""
|
|
472
491
|
try:
|
|
@@ -485,10 +504,7 @@ def _build_help_text(scope: "Optional[Any]" = None) -> str:
|
|
|
485
504
|
Deterministic Java/Spring semantics and reusable structural context for AI coding agents.
|
|
486
505
|
|
|
487
506
|
Cache warms on first scan; later calls reuse pre-built context instead of rescanning.
|
|
488
|
-
|
|
489
|
-
inventory commands scale with files, and repo-wide/deep compositions can take
|
|
490
|
-
minutes on multi-thousand-endpoint repositories. Use deep jobs nightly or with
|
|
491
|
-
ASK_MAX_ANALYSIS_SECONDS/ASK_PROGRESS when CI needs an explicit budget.
|
|
507
|
+
{_cost_sentence()}
|
|
492
508
|
|
|
493
509
|
{_start_here_block(scope)}
|
|
494
510
|
|
|
@@ -8285,14 +8301,20 @@ def spring_audit_cmd(
|
|
|
8285
8301
|
)
|
|
8286
8302
|
_stopped_early: Optional[str] = None
|
|
8287
8303
|
|
|
8304
|
+
from sourcecode import context_cache as _ctxcache
|
|
8305
|
+
|
|
8288
8306
|
phase = f"auditing {len(file_list)} Java files"
|
|
8289
8307
|
with _expensive_analysis_scope("spring-audit", target, phase):
|
|
8290
8308
|
_prog = Progress()
|
|
8291
8309
|
_prog.start(phase)
|
|
8292
8310
|
try:
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8311
|
+
# The parse is ~89 % of this command's wall clock (measured: 8,54 s of
|
|
8312
|
+
# 9,53 s on a 1 303-file repository) and it is the same parse the other
|
|
8313
|
+
# knowledge commands share, so it is fetched rather than rebuilt — which
|
|
8314
|
+
# is what makes a warm worth anything here (cold == warm before C3-94).
|
|
8315
|
+
cir = _ctxcache.shared_cir(
|
|
8316
|
+
target, file_list, progress=_work_sink(_prog)
|
|
8317
|
+
)
|
|
8296
8318
|
# C3-85: the stretch between the last `linking` tick and the first rule
|
|
8297
8319
|
# tick reports nothing, and what stays on the line is `n/n` — a counter
|
|
8298
8320
|
# that finished, reading as a stage still running. Measured on keycloak
|
|
@@ -9966,7 +9988,7 @@ def _migration_blast_radius(
|
|
|
9966
9988
|
from sourcecode.spring_model import SpringSemanticModel
|
|
9967
9989
|
|
|
9968
9990
|
try:
|
|
9969
|
-
cir
|
|
9991
|
+
cir = _ctxcache.shared_cir(target, file_list)
|
|
9970
9992
|
except Exception:
|
|
9971
9993
|
cir = ContextGraph.build(file_list, target).cir
|
|
9972
9994
|
model = SpringSemanticModel.build(cir)
|
|
@@ -10520,10 +10542,7 @@ def impact_chain_cmd(
|
|
|
10520
10542
|
# a fresh build, exactly as explain does, so impact-chain never breaks.
|
|
10521
10543
|
from sourcecode import context_cache as _ctxcache
|
|
10522
10544
|
try:
|
|
10523
|
-
cir
|
|
10524
|
-
_resolve_repo_root(target), target, file_list,
|
|
10525
|
-
progress=_work_sink(_prog),
|
|
10526
|
-
)
|
|
10545
|
+
cir = _ctxcache.shared_cir(target, file_list, progress=_work_sink(_prog))
|
|
10527
10546
|
except Exception:
|
|
10528
10547
|
cir = ContextGraph.build(file_list, target, progress=_work_sink(_prog)).cir
|
|
10529
10548
|
_model = SpringSemanticModel.build(cir)
|
|
@@ -10849,11 +10868,13 @@ def explain_cmd(
|
|
|
10849
10868
|
)
|
|
10850
10869
|
raise typer.Exit(code=1)
|
|
10851
10870
|
|
|
10852
|
-
# AI Context Cache — operates at the *knowledge* level: it caches the
|
|
10853
|
-
#
|
|
10854
|
-
#
|
|
10855
|
-
# shared CIR
|
|
10856
|
-
# Provider-agnostic and best-effort: any fault degrades to a fresh build.
|
|
10871
|
+
# AI Context Cache — operates at the *knowledge* level: it caches the reusable
|
|
10872
|
+
# Canonical IR (the expensive Java parse), keyed by knowledge state and by the
|
|
10873
|
+
# analysed set, not by command. explain then derives its answer cheaply from
|
|
10874
|
+
# that shared CIR, and any other command over the same files reuses it for
|
|
10875
|
+
# free. Provider-agnostic and best-effort: any fault degrades to a fresh build.
|
|
10876
|
+
# This call site keeps the lower-level entry point because it reports the
|
|
10877
|
+
# lookup (`_cc_look`); `shared_cir` is the same door without the receipt.
|
|
10857
10878
|
_prog = Progress()
|
|
10858
10879
|
_prog.start(f"explaining {class_name} ({len(file_list)} files)")
|
|
10859
10880
|
_cc_look = None
|
sourcecode/context_cache.py
CHANGED
|
@@ -651,6 +651,33 @@ class KnowledgeLookup:
|
|
|
651
651
|
return f"Context cache: MISS (build {self.build_ms:.0f}ms)"
|
|
652
652
|
|
|
653
653
|
|
|
654
|
+
def analysed_set_signature(root: Path, file_list: list[str]) -> str:
|
|
655
|
+
"""Fingerprint of *what was analysed*: the scope root and the exact file set.
|
|
656
|
+
|
|
657
|
+
The shared CIR is keyed on knowledge state, and until C3-94 "knowledge state"
|
|
658
|
+
was read as the worktree alone — which is only half of it. A run bounded to a
|
|
659
|
+
subdirectory parses a *subset* of the same worktree, and the resulting CIR is
|
|
660
|
+
a different artifact answering a different question. Keyed on the worktree
|
|
661
|
+
alone, both artifacts collided on one entry: measured on a 861-file repository,
|
|
662
|
+
``explain WebUtil <repo>/web`` stored a 42-file / 382-symbol CIR under the
|
|
663
|
+
repo-wide key, and the next repo-wide command read it and answered about 3 % of
|
|
664
|
+
the repository — silently, because the reader passes its own ``file_paths`` to
|
|
665
|
+
``ir_dict_to_canonical`` and the reconstructed CIR then reports 861 files
|
|
666
|
+
beside 382 symbols.
|
|
667
|
+
|
|
668
|
+
So the analysed set is stated, not hoped for. The scope path and the file list
|
|
669
|
+
are both in it: two roots can yield the same file count, and a caller that
|
|
670
|
+
filters its list (tests in or out) is asking about a different population than
|
|
671
|
+
one that does not.
|
|
672
|
+
"""
|
|
673
|
+
payload = "".join([
|
|
674
|
+
str(root.resolve()),
|
|
675
|
+
str(len(file_list)),
|
|
676
|
+
hashlib.sha256("\n".join(sorted(file_list)).encode("utf-8")).hexdigest(),
|
|
677
|
+
])
|
|
678
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
|
|
679
|
+
|
|
680
|
+
|
|
654
681
|
def get_or_build_cir(
|
|
655
682
|
repo_root: Path,
|
|
656
683
|
root: Path,
|
|
@@ -662,11 +689,19 @@ def get_or_build_cir(
|
|
|
662
689
|
"""Return ``(CanonicalRepositoryIR, KnowledgeLookup)``, caching the CIR.
|
|
663
690
|
|
|
664
691
|
This is the knowledge-level entry point. It caches the **reusable** raw IR
|
|
665
|
-
(the expensive Java parse) keyed
|
|
666
|
-
so every command that
|
|
667
|
-
CIR is rebuilt cheaply from the cached raw IR via
|
|
668
|
-
skipping the parse entirely. Best-effort: any cache
|
|
669
|
-
fresh build.
|
|
692
|
+
(the expensive Java parse) keyed by knowledge state and by the analysed set —
|
|
693
|
+
not by command — so every command that parses the same files shares one cache
|
|
694
|
+
entry. On a hit the full CIR is rebuilt cheaply from the cached raw IR via
|
|
695
|
+
``ir_dict_to_canonical``, skipping the parse entirely. Best-effort: any cache
|
|
696
|
+
fault falls back to a fresh build.
|
|
697
|
+
|
|
698
|
+
A **repo-wide** run (``root`` is ``repo_root``) owns the scope-free entry —
|
|
699
|
+
the one :func:`peek_cir` reads without knowing any file list, which is why
|
|
700
|
+
that key must stay free of one. Every **bounded** run keys on its own analysed
|
|
701
|
+
set instead, so it can neither read nor overwrite the repo-wide answer. Either
|
|
702
|
+
way the entry carries the analysed signature it was built from and a reader
|
|
703
|
+
whose set does not match rebuilds: the key prevents the collision, the check
|
|
704
|
+
makes the prevention verifiable rather than assumed (C3-94).
|
|
670
705
|
|
|
671
706
|
`progress` is the (done, total, stage) sink the caller's heartbeat reads
|
|
672
707
|
(C3-56). It reaches only the *building* paths, which is the honest place for
|
|
@@ -684,14 +719,23 @@ def get_or_build_cir(
|
|
|
684
719
|
cir = build_canonical_ir(file_list, root, since=since, progress=progress)
|
|
685
720
|
return cir, KnowledgeLookup(hit=False, enabled=False)
|
|
686
721
|
|
|
687
|
-
|
|
722
|
+
analysed = analysed_set_signature(root, file_list)
|
|
723
|
+
options: dict[str, Any] = {"since": since} if since else None
|
|
724
|
+
if root.resolve() != repo_root.resolve():
|
|
725
|
+
# A bounded run: its own entry, never the repo-wide one.
|
|
726
|
+
options = dict(options or {})
|
|
727
|
+
options["analysed"] = analysed
|
|
728
|
+
key = cache.knowledge_key(SCOPE_JAVA_CIR, options=options)
|
|
688
729
|
|
|
689
730
|
t0 = time.perf_counter()
|
|
690
731
|
cached = cache.get(key)
|
|
691
732
|
lookup_ms = (time.perf_counter() - t0) * 1000
|
|
692
733
|
if cached is not None:
|
|
693
734
|
raw = cached.payload.get("raw_ir")
|
|
694
|
-
|
|
735
|
+
stored = (cached.metadata or {}).get("analysed_set")
|
|
736
|
+
# An entry written before C3-94 carries no signature; it cannot be shown to
|
|
737
|
+
# describe this analysed set, so it is not served as if it did.
|
|
738
|
+
if isinstance(raw, dict) and stored == analysed:
|
|
695
739
|
try:
|
|
696
740
|
cir = ir_dict_to_canonical(raw, file_paths=file_list)
|
|
697
741
|
if not validate_canonical_ir(cir):
|
|
@@ -708,6 +752,8 @@ def get_or_build_cir(
|
|
|
708
752
|
"scope": SCOPE_JAVA_CIR,
|
|
709
753
|
"cir_hash": cir.cir_hash,
|
|
710
754
|
"schema_version": cir.schema_version,
|
|
755
|
+
"analysed_set": analysed,
|
|
756
|
+
"analysed_root": str(root.resolve()),
|
|
711
757
|
"file_count": len(cir.files),
|
|
712
758
|
"symbol_count": len(cir.symbols),
|
|
713
759
|
},
|
|
@@ -725,6 +771,41 @@ def get_or_build_cir(
|
|
|
725
771
|
return cir, KnowledgeLookup(hit=False, enabled=True, build_ms=build_ms)
|
|
726
772
|
|
|
727
773
|
|
|
774
|
+
def repo_root_of(root: Path) -> Path:
|
|
775
|
+
"""The enclosing git root, or *root* itself outside a repository."""
|
|
776
|
+
candidate = root.resolve()
|
|
777
|
+
while True:
|
|
778
|
+
if (candidate / ".git").exists():
|
|
779
|
+
return candidate
|
|
780
|
+
if candidate.parent == candidate:
|
|
781
|
+
return root.resolve()
|
|
782
|
+
candidate = candidate.parent
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def shared_cir(
|
|
786
|
+
root: Path,
|
|
787
|
+
file_list: list[str],
|
|
788
|
+
*,
|
|
789
|
+
since: Optional[str] = None,
|
|
790
|
+
progress: "Optional[Callable[[int, int, str], None]]" = None,
|
|
791
|
+
) -> Any:
|
|
792
|
+
"""The Java CIR for *root*, from the shared knowledge cache.
|
|
793
|
+
|
|
794
|
+
The one door for a command that needs the repository parse and does not care
|
|
795
|
+
where it comes from: it resolves the enclosing repository itself, so a caller
|
|
796
|
+
holds no opinion about cache scope — the reason each call-site that built its
|
|
797
|
+
own CIR was one more place for the scope rule to be got wrong (C3-94).
|
|
798
|
+
|
|
799
|
+
Best-effort by construction: `get_or_build_cir` degrades to a fresh build on
|
|
800
|
+
any cache fault, so the answer never depends on the cache being there.
|
|
801
|
+
"""
|
|
802
|
+
cir, _lookup = get_or_build_cir(
|
|
803
|
+
repo_root_of(root), Path(root).resolve(), file_list,
|
|
804
|
+
since=since, progress=progress,
|
|
805
|
+
)
|
|
806
|
+
return cir
|
|
807
|
+
|
|
808
|
+
|
|
728
809
|
def peek_cir(
|
|
729
810
|
repo_root: Path,
|
|
730
811
|
*,
|
|
@@ -755,6 +836,12 @@ def peek_cir(
|
|
|
755
836
|
raw = cached.payload.get("raw_ir")
|
|
756
837
|
if not isinstance(raw, dict):
|
|
757
838
|
return None
|
|
839
|
+
# The caller is promised the *repo-wide* CIR, so the entry has to say it is one.
|
|
840
|
+
# C3-94 makes the key unreachable to bounded runs; this re-reads the fact from
|
|
841
|
+
# the entry rather than inferring it from the key, and treats an entry written
|
|
842
|
+
# before that guarantee (no recorded root) as a miss.
|
|
843
|
+
if (cached.metadata or {}).get("analysed_root") != str(repo_root.resolve()):
|
|
844
|
+
return None
|
|
758
845
|
try:
|
|
759
846
|
cir = ir_dict_to_canonical(raw, file_paths=None)
|
|
760
847
|
# validate_canonical_ir returns a LIST of problems — empty means valid.
|
sourcecode/detach.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"""detach.py — run a repository-wide analysis so it outlives the shell that started it.
|
|
2
2
|
|
|
3
|
-
C3-53, three reproductions on the field repository: `spring-audit`
|
|
4
|
-
analysis
|
|
5
|
-
|
|
3
|
+
C3-53, three reproductions on the field repository: `spring-audit` ran 408 s of
|
|
4
|
+
analysis **on the build measured then** (4.7.0-class; the same command on the same
|
|
5
|
+
repository measures 8,4 s on 4.18.0 — C3-97), the agent harness promoted the
|
|
6
|
+
foreground process to background at ~600 s, and the session died. The cost that
|
|
7
|
+
provoked this module has since collapsed; what the module answers — *a run that
|
|
8
|
+
must outlive the shell that started it* — has not, because a nightly composition
|
|
9
|
+
or a `risk` on a large tree still crosses that boundary. **Backgrounding it did not help, and neither did
|
|
6
10
|
`Start-Process -NoNewWindow -PassThru`** — all six detached processes died at
|
|
7
11
|
the same second the session did.
|
|
8
12
|
|
sourcecode/execution_plan.py
CHANGED
|
@@ -14,13 +14,15 @@ answer lives: `ask --help` orders its front page from it, and `remedies` checks
|
|
|
14
14
|
every command it suggests against it.
|
|
15
15
|
|
|
16
16
|
**What this module will not do is invent a number.** The natural design is to
|
|
17
|
-
project seconds by scaling the reference measurement with the file count, and
|
|
18
|
-
|
|
19
|
-
on
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
project seconds by scaling the reference measurement with the file count, and the
|
|
18
|
+
series refutes it from both ends: `spring-audit` takes 8.8 s on the 2 000-file
|
|
19
|
+
reference, and on one 3 342-file repository at one commit the field measured
|
|
20
|
+
408 s (#13), 2 351 s (#16) and **8,4 s** (#22) — three builds of ours, one of them
|
|
21
|
+
280× apart from another, with byte-identical payloads. Cost is dominated by what
|
|
22
|
+
the repository contains, by the platform, and above all by the build; a file count
|
|
23
|
+
predicts none of the three. So what is published is a *class*, the measured
|
|
24
|
+
anchors behind it and **the release each was taken on** (C3-97), never a projected
|
|
25
|
+
duration for the repository in hand.
|
|
24
26
|
"""
|
|
25
27
|
|
|
26
28
|
from __future__ import annotations
|
|
@@ -36,6 +38,7 @@ from sourcecode.cache_model import (
|
|
|
36
38
|
FIELD_ANCHOR_JAVA_FILES,
|
|
37
39
|
REFERENCE_JAVA_FILES,
|
|
38
40
|
REFERENCE_REPOSITORY,
|
|
41
|
+
field_anchor_note,
|
|
39
42
|
)
|
|
40
43
|
|
|
41
44
|
#: How a command can be run on the scope in hand.
|
|
@@ -45,11 +48,13 @@ DETACH = "detach" # `--output` + `--detach`, or a nightly job
|
|
|
45
48
|
UNKNOWN = "unknown" # not measured on this axis; never guessed
|
|
46
49
|
|
|
47
50
|
#: Above this many Java files a repository-wide pass is in the regime the field
|
|
48
|
-
#:
|
|
49
|
-
#:
|
|
50
|
-
#:
|
|
51
|
-
#: field anchor we have a measurement, and the boundary between them is
|
|
52
|
-
#: honest place to change the advice.
|
|
51
|
+
#: measures in, rather than the one the reference was taken in. The number is the
|
|
52
|
+
#: midpoint of the two sizes this product has actually measured (2 000 files and
|
|
53
|
+
#: 3 342 files), not a preference: below the reference we have a measurement,
|
|
54
|
+
#: above the field anchor we have a measurement, and the boundary between them is
|
|
55
|
+
#: the honest place to change the advice. What changes there is which anchor a
|
|
56
|
+
#: verdict rests on — not an assumption that the larger one is slow, which is the
|
|
57
|
+
#: half C3-97 had to correct.
|
|
53
58
|
LARGE_SCOPE_JAVA_FILES = (REFERENCE_JAVA_FILES + FIELD_ANCHOR_JAVA_FILES) // 2
|
|
54
59
|
|
|
55
60
|
#: Stop walking after this many directory entries. A front page must not pay for
|
|
@@ -253,12 +258,17 @@ def _reference(row) -> Optional[str]:
|
|
|
253
258
|
parts.append(
|
|
254
259
|
f"{row.cold_seconds:g} s cold{warm} at {REFERENCE_JAVA_FILES} files"
|
|
255
260
|
)
|
|
261
|
+
note = field_anchor_note(row)
|
|
262
|
+
dated = f", {note}" if note else ""
|
|
256
263
|
if row.field_seconds is not None:
|
|
257
|
-
parts.append(
|
|
264
|
+
parts.append(
|
|
265
|
+
f"{row.field_seconds:g} s at {_files(FIELD_ANCHOR_JAVA_FILES)} files "
|
|
266
|
+
f"(field{dated})"
|
|
267
|
+
)
|
|
258
268
|
elif row.field_blocked:
|
|
259
269
|
parts.append(
|
|
260
270
|
f"did not finish in an interactive session at "
|
|
261
|
-
f"{_files(FIELD_ANCHOR_JAVA_FILES)} files (field)"
|
|
271
|
+
f"{_files(FIELD_ANCHOR_JAVA_FILES)} files (field{dated})"
|
|
262
272
|
)
|
|
263
273
|
return " · ".join(parts) or None
|
|
264
274
|
|
|
@@ -310,16 +320,22 @@ def verdict(command: str, scope: Scope) -> Verdict:
|
|
|
310
320
|
cls, reference,
|
|
311
321
|
)
|
|
312
322
|
if row is not None and row.field_seconds is not None:
|
|
323
|
+
# C3-97: the figure decides, and the build it was taken on travels with
|
|
324
|
+
# it. An anchor from an earlier release still outranks the class — it is
|
|
325
|
+
# a measurement at this size and the class is not — but it is never
|
|
326
|
+
# printed as though it described the build in hand.
|
|
327
|
+
note = field_anchor_note(row)
|
|
328
|
+
dated = f" ({note})" if note else ""
|
|
313
329
|
if row.field_seconds <= FOREGROUND_SECONDS:
|
|
314
330
|
return Verdict(
|
|
315
331
|
command, RUNS_NOW,
|
|
316
|
-
f"measured at {row.field_seconds:g} s on a repository this size",
|
|
332
|
+
f"measured at {row.field_seconds:g} s on a repository this size{dated}",
|
|
317
333
|
cls, reference,
|
|
318
334
|
)
|
|
319
335
|
return Verdict(
|
|
320
336
|
command, DETACH,
|
|
321
337
|
(
|
|
322
|
-
f"measured at {row.field_seconds:g} s on a repository this size — "
|
|
338
|
+
f"measured at {row.field_seconds:g} s on a repository this size{dated} — "
|
|
323
339
|
f"run it with `--output` and `--detach`, or nightly"
|
|
324
340
|
),
|
|
325
341
|
cls, reference,
|
|
@@ -328,9 +344,10 @@ def verdict(command: str, scope: Scope) -> Verdict:
|
|
|
328
344
|
return Verdict(
|
|
329
345
|
command, DETACH,
|
|
330
346
|
(
|
|
331
|
-
f"not measured on a repository this size
|
|
332
|
-
f"
|
|
333
|
-
f"
|
|
347
|
+
f"not measured on a repository this size, and its class is the "
|
|
348
|
+
f"only thing left to go on — a {cls} pass here has ranged from "
|
|
349
|
+
f"seconds to tens of minutes across our own releases "
|
|
350
|
+
f"({FIELD_ANCHOR}). Offered detached rather than assumed cheap"
|
|
334
351
|
),
|
|
335
352
|
cls, reference,
|
|
336
353
|
)
|
sourcecode/identity_fallback.py
CHANGED
|
@@ -81,11 +81,6 @@ _METHOD_RE = re.compile(
|
|
|
81
81
|
re.MULTILINE,
|
|
82
82
|
)
|
|
83
83
|
|
|
84
|
-
_TYPE_RE = re.compile(
|
|
85
|
-
r"^[ \t]*(?:public|final|abstract|static|\s)*\s*(?:class|interface|enum)\s+(?P<name>\w+)",
|
|
86
|
-
re.MULTILINE,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
84
|
#: Returns that are not an identity even inside an absence branch: re-dispatch,
|
|
90
85
|
#: constants and the empty-optional answers, which are refusals in disguise.
|
|
91
86
|
_NON_IDENTITY_RETURNS = re.compile(
|
|
@@ -126,13 +121,12 @@ class IdentityFallback:
|
|
|
126
121
|
|
|
127
122
|
def _enclosing_type(source: str, offset: int) -> str:
|
|
128
123
|
"""The type declared closest above `offset`. Nested types resolve to the nearest
|
|
129
|
-
enclosing declaration, which is the one a reader would name.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return name
|
|
124
|
+
enclosing declaration, which is the one a reader would name.
|
|
125
|
+
|
|
126
|
+
Thin alias of the one authority (C3-95) — see `source_text.enclosing_type`."""
|
|
127
|
+
from sourcecode.source_text import enclosing_type
|
|
128
|
+
|
|
129
|
+
return enclosing_type(source, offset, "")
|
|
136
130
|
|
|
137
131
|
|
|
138
132
|
def _method_body(source: str, brace_index: int) -> str:
|
sourcecode/perf.py
CHANGED
|
@@ -440,8 +440,10 @@ RELEASE_GATE_COMMANDS: tuple[str, ...] = (
|
|
|
440
440
|
#: What the gate does **not** cover, and why — published rather than omitted, so
|
|
441
441
|
#: "the gate is green" is never read as "every command was measured".
|
|
442
442
|
RELEASE_GATE_EXCLUSIONS: tuple[tuple[str, str], ...] = (
|
|
443
|
-
("risk", "
|
|
444
|
-
"
|
|
443
|
+
("risk", "68 s on the gate repository since C3-96 deleted the catastrophic "
|
|
444
|
+
"backtracking that made it 54 minutes (F-AQ) — no longer excluded for "
|
|
445
|
+
"cost, only until a baseline cell exists for it; the first capture "
|
|
446
|
+
"that includes it makes it gated (C3-99)"),
|
|
445
447
|
("impact-chain", "takes a symbol, not a repository — its cost is the audit "
|
|
446
448
|
"`risk` and `spring-audit` already gate"),
|
|
447
449
|
("verify-edit", "measures an edit loop, which needs a working-tree mutation "
|
sourcecode/phased_run.py
CHANGED
|
@@ -300,14 +300,16 @@ def budget_warning(
|
|
|
300
300
|
service from a 3 342-file monolith.
|
|
301
301
|
|
|
302
302
|
What replaces it is what this product actually has: the **measured anchors for
|
|
303
|
-
this command** (`cache_model`, both ends of the range
|
|
304
|
-
|
|
305
|
-
*not* replace it is a
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
303
|
+
this command** (`cache_model`, both ends of the range, each carrying the build
|
|
304
|
+
it was taken on) and the **size of the repository in hand**
|
|
305
|
+
(`execution_plan.measure_scope`). What deliberately does *not* replace it is a
|
|
306
|
+
projection, and C3-97 made that case stronger rather than weaker: the same
|
|
307
|
+
command on the same commit of the same 3 342-file repository measured 2 351 s
|
|
308
|
+
on 4.11.0 and 8,4 s on 4.18.0, against 8,8 s at 2 000 files throughout. Cost
|
|
309
|
+
tracks the build at least as much as the tree, so "~15 s on your repository"
|
|
310
|
+
would be a confident falsehood of the exact kind this ledger exists to
|
|
311
|
+
prevent. The anchors and the size are the honest claim, and the class floor is
|
|
312
|
+
named as a class floor when there is nothing better.
|
|
311
313
|
"""
|
|
312
314
|
lines = [
|
|
313
315
|
f"[ask] budget: {command} is a {cls} analysis and the configured budget "
|
sourcecode/posture.py
CHANGED
|
@@ -1109,27 +1109,22 @@ def _repo_root_of(root: Path) -> Path:
|
|
|
1109
1109
|
|
|
1110
1110
|
|
|
1111
1111
|
def shared_cir(root: Path, files: "list[str]"):
|
|
1112
|
-
"""The Java CIR for *root*, from the shared knowledge cache
|
|
1112
|
+
"""The Java CIR for *root*, from the shared knowledge cache.
|
|
1113
1113
|
|
|
1114
1114
|
`posture` built its own CIR on every run — the same parse `explain`,
|
|
1115
1115
|
`impact-chain` and `cache warm` already share — so the one command the
|
|
1116
1116
|
product differentiates on was the one a warm could not help (C3-25).
|
|
1117
1117
|
|
|
1118
|
-
Poison-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1118
|
+
Poison-safety used to live here: the shared key was repo-wide and carried no
|
|
1119
|
+
scope, so a run bounded to a subdirectory had to keep its own unshared build
|
|
1120
|
+
or it would store a truncated CIR under the repo-wide key. C3-94 moved that
|
|
1121
|
+
rule into the cache itself — a bounded run now keys on its own analysed set —
|
|
1122
|
+
so the bounded case is cached too instead of paying for the parse every time,
|
|
1123
|
+
and this function is the thin alias of the one authority it left behind.
|
|
1123
1124
|
"""
|
|
1124
|
-
from sourcecode.
|
|
1125
|
+
from sourcecode.context_cache import shared_cir as _shared_cir
|
|
1125
1126
|
|
|
1126
|
-
|
|
1127
|
-
if repo_root != root:
|
|
1128
|
-
return ContextGraph.build(files, root).cir
|
|
1129
|
-
from sourcecode.context_cache import get_or_build_cir
|
|
1130
|
-
|
|
1131
|
-
cir, _lookup = get_or_build_cir(repo_root, root, files)
|
|
1132
|
-
return cir
|
|
1127
|
+
return _shared_cir(root, files)
|
|
1133
1128
|
|
|
1134
1129
|
|
|
1135
1130
|
def _posture(
|
sourcecode/release_info.py
CHANGED
|
@@ -27,7 +27,7 @@ from typing import Any, Optional
|
|
|
27
27
|
#: The date `__version__` was released, from that version's CHANGELOG entry.
|
|
28
28
|
#: `tests/test_release_info.py` fails the build if the two disagree, so this is
|
|
29
29
|
#: a copy of a fact rather than a second authority for it.
|
|
30
|
-
RELEASE_DATE = "2026-08-
|
|
30
|
+
RELEASE_DATE = "2026-08-11"
|
|
31
31
|
|
|
32
32
|
#: Releases per day, measured over the 30 releases before this one (the same
|
|
33
33
|
#: computation the test re-runs against the CHANGELOG). Stable across window
|
sourcecode/risk.py
CHANGED
|
@@ -150,10 +150,30 @@ _URL_PATTERN_RE = re.compile(
|
|
|
150
150
|
re.IGNORECASE,
|
|
151
151
|
)
|
|
152
152
|
_HTTP_INPUT_ANNOTATION_RE = re.compile(r"@(RequestParam|PathVariable|RequestBody)\b")
|
|
153
|
+
#: C3-96. Three of this pattern's groups used to let the same whitespace be
|
|
154
|
+
#: consumed in more than one place — `(?:\s*@…\s*)*`, `\s*(?:public|…)?\s*`, and
|
|
155
|
+
#: `[\w<>\[\].?,\s]+\s+`, whose character class contains the `\s` the following
|
|
156
|
+
#: `\s+` also matches. Each run of whitespace can then be split between the two in
|
|
157
|
+
#: many ways, and on text that never completes a signature the engine tries them
|
|
158
|
+
#: all: `_java_methods` runs this over **every** source, and a repository-wide
|
|
159
|
+
#: `risk` on a 2 985-file repository did not finish in 20 minutes — inside the C
|
|
160
|
+
#: regex engine, where no signal, budget or Ctrl-C can reach it (C3-87's deadline
|
|
161
|
+
#: is checked between stages, and this never returns to one).
|
|
162
|
+
#:
|
|
163
|
+
#: Rewritten so every separator is consumed once: each modifier takes its own
|
|
164
|
+
#: trailing whitespace, and the return type is tokens joined by explicit
|
|
165
|
+
#: separators instead of one class that contains the separator too.
|
|
166
|
+
#:
|
|
167
|
+
#: The **same** language, deliberately, including the part that reads like a bug:
|
|
168
|
+
#: the old class matched whitespace alone, so a bare `if (cond) {` was admitted as
|
|
169
|
+
#: a method named `if`. Those pseudo-methods carry propagation through control-flow
|
|
170
|
+
#: blocks in the walk below, so dropping them is a change to what `risk` finds, not
|
|
171
|
+
#: a speed-up — which is why the type stays optional here and the question of
|
|
172
|
+
#: whether it should is left to its own increment, with its own field measurement.
|
|
153
173
|
_JAVA_METHOD_RE = re.compile(
|
|
154
|
-
r"(?P<ann>(
|
|
155
|
-
r"(?P<sig
|
|
156
|
-
r"[\w<>\[\]
|
|
174
|
+
r"(?P<ann>(?:@[\w.]+(?:\([^;{}]*?\))?\s*)*)"
|
|
175
|
+
r"(?P<sig>(?:(?:public|protected|private)\s+)?(?:static\s+)?"
|
|
176
|
+
r"(?:[\w<>\[\].?,]+(?:\s+[\w<>\[\].?,]+)*\s+|\s{2,})(?P<name>[A-Za-z_$][\w$]*)\s*"
|
|
157
177
|
r"\((?P<params>[^)]*)\)\s*)\{",
|
|
158
178
|
re.MULTILINE,
|
|
159
179
|
)
|
|
@@ -797,7 +817,7 @@ class RiskComposer:
|
|
|
797
817
|
profiles: "Optional[set[str]]" = None,
|
|
798
818
|
progress: "Optional[Callable[..., Any]]" = None,
|
|
799
819
|
) -> None:
|
|
800
|
-
from sourcecode.
|
|
820
|
+
from sourcecode.context_cache import shared_cir
|
|
801
821
|
from sourcecode.repository_ir import extract_java_endpoints, find_java_files
|
|
802
822
|
from sourcecode.security_posture import (
|
|
803
823
|
endpoint_security_surface,
|
|
@@ -808,8 +828,11 @@ class RiskComposer:
|
|
|
808
828
|
from sourcecode.spring_tx_analyzer import run_tx_audit
|
|
809
829
|
|
|
810
830
|
self.root = Path(root).resolve()
|
|
811
|
-
|
|
812
|
-
|
|
831
|
+
# The parse is ~80 % of this command's wall clock (measured: 12,45 s of
|
|
832
|
+
# 15,64 s on a 1 303-file repository) and it is the same parse `posture`,
|
|
833
|
+
# `explain` and `cache warm` already share — so it is fetched, not rebuilt.
|
|
834
|
+
self.cir = cir if cir is not None else shared_cir(
|
|
835
|
+
self.root, find_java_files(self.root)
|
|
813
836
|
)
|
|
814
837
|
self.model = SpringSemanticModel.build(self.cir)
|
|
815
838
|
|
|
@@ -249,14 +249,10 @@ def _line_of(source: str, index: int) -> int:
|
|
|
249
249
|
|
|
250
250
|
|
|
251
251
|
def _enclosing_type(source: str, offset: int, fallback: str) -> str:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
re.MULTILINE,
|
|
257
|
-
):
|
|
258
|
-
name = m.group("n")
|
|
259
|
-
return name
|
|
252
|
+
"""Thin alias of the one authority (C3-95) — see `source_text.enclosing_type`."""
|
|
253
|
+
from sourcecode.source_text import enclosing_type
|
|
254
|
+
|
|
255
|
+
return enclosing_type(source, offset, fallback)
|
|
260
256
|
|
|
261
257
|
|
|
262
258
|
def _scan_java(source: str, rel: str) -> "list[SecurityConfigObservation]":
|
sourcecode/source_text.py
CHANGED
|
@@ -30,6 +30,7 @@ other.
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
32
|
import re
|
|
33
|
+
from typing import Optional
|
|
33
34
|
|
|
34
35
|
#: Java/C-family lexical tokens, in precedence order. String and character
|
|
35
36
|
#: literals are matched **first** so that a `//` or `/*` living inside one — as in
|
|
@@ -84,6 +85,70 @@ def blank_hash_comments(source: str) -> str:
|
|
|
84
85
|
return _HASH_COMMENT_LINE.sub(lambda m: _blank(m.group(0)), source)
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
#: The one authority for "a type is declared here", used by every scanner that has
|
|
89
|
+
#: to name the type enclosing a match it found in raw text.
|
|
90
|
+
#:
|
|
91
|
+
#: C3-95. Two modules kept their own copy, and both were written as
|
|
92
|
+
#: ``(?:public|final|abstract|static|\s)*\s*`` — a whitespace branch inside a
|
|
93
|
+
#: starred group followed by another starred whitespace group. Every run of spaces
|
|
94
|
+
#: can then be split between the two in exponentially many ways, and on a line that
|
|
95
|
+
#: never reaches `class` the engine tries them all before failing: measured at
|
|
96
|
+
#: 13 ms for 200 leading spaces, 1,17 s for 800 and **10,1 s for 1 600** — one line,
|
|
97
|
+
#: one file. A generated or reformatted source is enough to hang a scan that is
|
|
98
|
+
#: supposed to take seconds, and `spring-audit` and `risk` both run this scan.
|
|
99
|
+
#:
|
|
100
|
+
#: Written so each modifier must consume its own trailing whitespace, the group is
|
|
101
|
+
#: unambiguous and the match is linear. It accepts the same declarations: the
|
|
102
|
+
#: modifier list is wider than the original (which silently rejected
|
|
103
|
+
#: `private`/`protected`/`sealed`), and the separator is still any whitespace, so a
|
|
104
|
+
#: modifier on its own line still binds to the declaration below it.
|
|
105
|
+
JAVA_TYPE_DECLARATION = re.compile(
|
|
106
|
+
r"^[ \t]*"
|
|
107
|
+
r"(?:(?:public|protected|private|final|abstract|static|sealed|non-sealed)\s+)*"
|
|
108
|
+
r"(?:class|interface|enum|record)\s+(?P<name>\w+)",
|
|
109
|
+
re.MULTILINE,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def type_declarations(source: str) -> "list[tuple[int, str]]":
|
|
114
|
+
"""``(offset, name)`` of every type declared in *source*, in source order."""
|
|
115
|
+
return [(m.start(), m.group("name")) for m in JAVA_TYPE_DECLARATION.finditer(source)]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def enclosing_type(source: str, offset: int, fallback: str = "") -> str:
|
|
119
|
+
"""Name of the type declared closest above *offset*, else *fallback*.
|
|
120
|
+
|
|
121
|
+
Nested types resolve to the nearest enclosing declaration, which is the one a
|
|
122
|
+
reader would name. Callers ask this once per observation and a file can carry
|
|
123
|
+
many, so the scan of a given source is memoised: the previous shape sliced
|
|
124
|
+
``source[:offset]`` and re-scanned it per call, which is a copy and a walk of
|
|
125
|
+
the whole prefix for every finding in the file.
|
|
126
|
+
|
|
127
|
+
At the boundary the two prior copies disagreed, and this one keeps
|
|
128
|
+
``identity_fallback``'s rule: the nearest declaration **at or above** the
|
|
129
|
+
offset. Slicing the prefix dropped a declaration that straddled the offset, so
|
|
130
|
+
a match on the declaration's own line was attributed to the type above it or to
|
|
131
|
+
the fallback. Every real observation sits strictly after its declaration, which
|
|
132
|
+
is why the field results are unchanged either way.
|
|
133
|
+
"""
|
|
134
|
+
global _DECL_MEMO
|
|
135
|
+
memo_key = (len(source), hash(source))
|
|
136
|
+
if _DECL_MEMO is None or _DECL_MEMO[0] != memo_key:
|
|
137
|
+
_DECL_MEMO = (memo_key, type_declarations(source))
|
|
138
|
+
name = fallback
|
|
139
|
+
for start, declared in _DECL_MEMO[1]:
|
|
140
|
+
if start > offset:
|
|
141
|
+
break
|
|
142
|
+
name = declared
|
|
143
|
+
return name
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
#: One entry: callers sweep a file's findings together, so the next question is
|
|
147
|
+
#: almost always about the same source. Holding one keeps the memo from becoming a
|
|
148
|
+
#: second copy of every file the process ever read.
|
|
149
|
+
_DECL_MEMO: "Optional[tuple[tuple[int, int], list[tuple[int, str]]]]" = None
|
|
150
|
+
|
|
151
|
+
|
|
87
152
|
def commented_spans(source: str, *, xml: bool) -> "list[tuple[int, int]]":
|
|
88
153
|
"""`(start, end)` offsets of every comment — what was switched off, not what runs.
|
|
89
154
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -54,7 +54,7 @@ Description-Content-Type: text/markdown
|
|
|
54
54
|
|
|
55
55
|
## The problem
|
|
56
56
|
|
|
57
|
-
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch. On
|
|
57
|
+
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch. On a large Java/Spring monolith that is thousands of files of parsing per turn, repeated for every question asked about the same unchanged tree. Multiply it by dozens of agent turns per hour, and repo context acquisition becomes a real bottleneck — not just latency, but tokens, compute, and iteration velocity.
|
|
58
58
|
|
|
59
59
|
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan, later invocations reuse pre-built context whenever their command can consume the warmed layers. The repo doesn't change? The cache doesn't expire.
|
|
60
60
|
|
|
@@ -69,7 +69,7 @@ ASK Engine solves this with a persistent structural cache keyed on file content
|
|
|
69
69
|
| Keycloak | 7,885 Java files | 10.5s | 0.6s | **~17x** |
|
|
70
70
|
| BroadleafCommerce | 2,985 Java files | 2.7s | 0.3s | **~9x** |
|
|
71
71
|
|
|
72
|
-
These numbers are cache-layer benchmarks, not a promise for every command. Per-symbol queries and compact context benefit most
|
|
72
|
+
These numbers are cache-layer benchmarks, not a promise for every command. Per-symbol queries and compact context benefit most, and inventory commands scale with file count. What a repository-wide analysis costs tracks the build more than the file count: on one 3,342-file monolith, at one commit, `spring-audit` measured 2,351s on 4.11.0 and **8.4s on 4.18.0** with a byte-identical payload — so ASK publishes measured anchors and the release each was taken on, and never projects a duration for your repository. `ask cache model` prints both anchors per command, and `risk` — the one composition still measured in the tens of seconds at that size — is the one to give a budget or a detached run.
|
|
73
73
|
|
|
74
74
|
With the right command class, ASK Engine becomes **constant infrastructure** inside agent loops — call the fast/context surfaces before edits and reserve deep compositions for the points where their evidence is worth the runtime.
|
|
75
75
|
|
|
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
126
126
|
# pip / pipx
|
|
127
127
|
pipx install sourcecode # or: pip install sourcecode
|
|
128
128
|
|
|
129
|
-
ask version # ask
|
|
129
|
+
ask version # ask 5.0.0 — and, on a build that has aged,
|
|
130
130
|
# how many releases have probably shipped since
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=Y22bv5DzCS9Djs9Ql4VI3jyeczhm8uAgC4Nl9LjiXm0,308
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
|
|
3
3
|
sourcecode/archetype.py,sha256=CZvRLpkHot_D8D3JFQVorr-EHDJyh0BbS7RnSTqBigM,40499
|
|
4
4
|
sourcecode/architectural_baseline.py,sha256=4GiMVBLJVHvRKSWQGf2ZVurI1-R0qk5pmaevgvGSgVA,26325
|
|
@@ -9,7 +9,7 @@ sourcecode/ast_extractor.py,sha256=aXJjZ7XjAdxa99hWNm4SyzPqx4gNWiTbUYjNWGNl-Vc,5
|
|
|
9
9
|
sourcecode/audit_report.py,sha256=CLvvlQH2oA6Qj4UADUWhNwSsp1OcyzwXXey5DN_IiWI,8660
|
|
10
10
|
sourcecode/baseline_autocapture.py,sha256=tmMLexQSeaQRRbqxAd7walvGQqnF0mdpxQmkL40rlhI,15623
|
|
11
11
|
sourcecode/cache.py,sha256=CK_J8NqyaTNZ57KQT-R4puqI8yYlzG5WL7uLFRbzods,41727
|
|
12
|
-
sourcecode/cache_model.py,sha256=
|
|
12
|
+
sourcecode/cache_model.py,sha256=jQcXAIpfBl2rXrf8xitNKXy8M0RSIn4F4hQYV-tKGm8,41285
|
|
13
13
|
sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
|
|
14
14
|
sourcecode/caller_metrics.py,sha256=--sFGDnIog_YGu9xZHMcB91F5xZakfaAKvy15xU54hg,7904
|
|
15
15
|
sourcecode/caller_reach.py,sha256=RRF49tv4-QswraJ2vsZ89VAMOj7BmYXlAdXr-tLP_CA,9483
|
|
@@ -18,14 +18,14 @@ sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,127
|
|
|
18
18
|
sourcecode/change_plan.py,sha256=aX1mp2XnlDN-8R2BcTiu8HlkDwQ_4fN1ZLckj0VMXHM,9945
|
|
19
19
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
20
20
|
sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
|
|
21
|
-
sourcecode/cli.py,sha256=
|
|
21
|
+
sourcecode/cli.py,sha256=5sTk3jJ1Xh-JlnLiwN9bH0y3puiYejENXfl5iN0efrU,604917
|
|
22
22
|
sourcecode/client_calls.py,sha256=daRTgbXNUOfkzGXJpVb6A737R_Thhka8vw1_YgxCaLg,13548
|
|
23
23
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
24
24
|
sourcecode/compare.py,sha256=2GdDy0qkDSgmkkpgvoCHitoLj3mt30EeFgJ8PGEXJng,14712
|
|
25
25
|
sourcecode/confidence_analyzer.py,sha256=5li0NyOdS3Ie-f2ZY1gBmnjtkuRvI50l4OnGaKkXAOU,22358
|
|
26
26
|
sourcecode/constraint_diff.py,sha256=hWIO5vsxvHjsgQx23K3r_JMy1W6wQ2Ca4DqB9R_iMnY,5831
|
|
27
27
|
sourcecode/container_wiring.py,sha256=-jAOOgDR0VcZN3ph9kpZVU6iPyN1EK8H5DGAQzxGQKY,14394
|
|
28
|
-
sourcecode/context_cache.py,sha256=
|
|
28
|
+
sourcecode/context_cache.py,sha256=xfS0GpWG8MpHwqgw61tp3IR7jZ2IdG1j-v_HoIiSx7o,35608
|
|
29
29
|
sourcecode/context_graph.py,sha256=a6v2lgl2DxDnPAWE59K3yg6L5YFDak0LIUff3pi9p0c,47327
|
|
30
30
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
31
31
|
sourcecode/context_summarizer.py,sha256=cI2TZMvEhl0BEma12VtPaX6z03ZVBAetVzuK5GaCOvg,6852
|
|
@@ -41,7 +41,7 @@ sourcecode/defect_identity.py,sha256=XS2VFDImg9NLaRImybKTeyk-CoDSsXmchldg0dwvM7c
|
|
|
41
41
|
sourcecode/degradation.py,sha256=BtuGUKFgkzI3Z6fwmUdxEUEaLFAcPQ_bHhU2w9ItDxw,18376
|
|
42
42
|
sourcecode/dependency_analyzer.py,sha256=fQCaWQ7_BNcVgZv8gweJCwJ1CGlXsz8nw_tqN3aHJNE,63952
|
|
43
43
|
sourcecode/deployment_prefix.py,sha256=g0_4bE_m9svjhnapRpOp8BJkj6Ul94Ij_iVezygMZ0I,11956
|
|
44
|
-
sourcecode/detach.py,sha256=
|
|
44
|
+
sourcecode/detach.py,sha256=PvkCcbIuTaDKKt5TGqtQLahmsmCkflui33jLnMEWkvg,8006
|
|
45
45
|
sourcecode/doc_analyzer.py,sha256=05bjTUbDbmnbajD_cgRnACzS8T7xxBKVX4CjkJlhZg8,24411
|
|
46
46
|
sourcecode/dynamic_argument_surface.py,sha256=u6zJoY-zChYVw1-SrgiyAq-rUwqMZ5ZqBwLfbPJ_8ng,2960
|
|
47
47
|
sourcecode/endpoint_literals.py,sha256=Qf4gTZzvNSFDGDuOvF0YRL9NvaYKKj24klhR5LIOaXc,3263
|
|
@@ -52,7 +52,7 @@ sourcecode/envelope.py,sha256=fpF_8znvPqGXKZb0UPYcCYjm27yO7Pr-WsdXNE-tEoY,8911
|
|
|
52
52
|
sourcecode/environment_resolution.py,sha256=bfhkM0RGSyLwzFvfi-YhHjU1cy2ufA_WIkXBVeHs9y8,22130
|
|
53
53
|
sourcecode/error_schema.py,sha256=uwosfNaSujtYm11_732Hu92z5ITV040fQDaIyefSvR4,1683
|
|
54
54
|
sourcecode/evidence_provider.py,sha256=GSSL44JEaouO5AHks2sB3d1YvC9xIKIld1yBYxZpXxo,4277
|
|
55
|
-
sourcecode/execution_plan.py,sha256=
|
|
55
|
+
sourcecode/execution_plan.py,sha256=KRI_bFiBbmxOu0aF4bGwgVK9RdH8eB6g9Xhj1fkS2Zk,18120
|
|
56
56
|
sourcecode/explain.py,sha256=yqxvKiLF0XMMg11PdgqMFrsim5SXuEKM1U2cLvCLspM,29935
|
|
57
57
|
sourcecode/file_chunker.py,sha256=3vkM3mDQ5eE_yTPvUgjyjpGFBIjkW6_mrBmIbrylnA8,16444
|
|
58
58
|
sourcecode/file_classifier.py,sha256=pJCeN9KqWpAwKMCgGP4KDsBjuWMeo4zlbj5fv3hk9dA,15587
|
|
@@ -64,7 +64,7 @@ sourcecode/git_checkout.py,sha256=gCnkMwHpU6yLyc10FoGzE8W9-tI8KyYxKTgpbWPjayo,60
|
|
|
64
64
|
sourcecode/graph_analyzer.py,sha256=lp0eB1PWC20BYF-GpPhAyegRpKrUKgOmXZIcZSIX_Ks,65777
|
|
65
65
|
sourcecode/graph_evidence.py,sha256=rENNsYRZeNstX_ExNCLlbHJAruFQwxo5d00x6wO3xwI,15030
|
|
66
66
|
sourcecode/hibernate_strat.py,sha256=jYMV2mD-_DEjAwjTExA1Yuw4uEPHe9B8VvLKpG0A3oo,65808
|
|
67
|
-
sourcecode/identity_fallback.py,sha256=
|
|
67
|
+
sourcecode/identity_fallback.py,sha256=uRC_j_80uaI4Ue4tf-OhKX4h472dmufYcjEFGSxY2jk,9190
|
|
68
68
|
sourcecode/integration_coordinates.py,sha256=7vGFxN4tCn_KTisYFJuixa8XkyHBoasEFB3fqM62OBI,7407
|
|
69
69
|
sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
|
|
70
70
|
sourcecode/license.py,sha256=vvgE1vBUWh8ozWI7USYuteh6noxntnjuzDQIPF-l4rA,27795
|
|
@@ -82,10 +82,10 @@ sourcecode/parse_cache.py,sha256=fhXnoaOgVWwp_8q0ngJ76UZ09qJ6uxDd0jaX8jdezvo,208
|
|
|
82
82
|
sourcecode/partial_contract.py,sha256=x_N_0bF3mpNNjXd7JFB1zr6LSIroo2zN5GCz14GXvXQ,3510
|
|
83
83
|
sourcecode/path_admission.py,sha256=OGNSoluhVh8YYOBZBnACUrkmH4Tp_yxjGLifkcsxQpo,5838
|
|
84
84
|
sourcecode/path_filters.py,sha256=LmTYq735orssKTIuxTX1mE1FHAXF7dVEJEse35xE1RA,12371
|
|
85
|
-
sourcecode/perf.py,sha256=
|
|
86
|
-
sourcecode/phased_run.py,sha256=
|
|
85
|
+
sourcecode/perf.py,sha256=ZCPxDIg6ww6PXfLwcqNTH99lc1UgkYUZI3MywFgRO90,25991
|
|
86
|
+
sourcecode/phased_run.py,sha256=cFd0Lxfc8XL-XkCVCCzcjd9bZbtgkJkGZAHfmJ6dBJ4,16542
|
|
87
87
|
sourcecode/pipe_contract.py,sha256=PML0Er5d8uDyec4OrdUXuyqHbGPUYndjeaKUjkFz2u4,8369
|
|
88
|
-
sourcecode/posture.py,sha256=
|
|
88
|
+
sourcecode/posture.py,sha256=TGzSwa413NJyVvBgGWyCyaFqJXX-fnctq7vdxVs9z9A,76744
|
|
89
89
|
sourcecode/pr_comment_renderer.py,sha256=239PmJdf95av_ZW236C7_tvq_ahECeuF9AZxrheJ4OQ,15573
|
|
90
90
|
sourcecode/pr_impact.py,sha256=KyVBvHCCbgByKA_wqTbO1dSkfi05GEudIAuH4CyDPK4,27395
|
|
91
91
|
sourcecode/prepare_context.py,sha256=ntB2MSScHtYRKRm9SansA0FrklMnXZmE9_yRXqXk0qc,239264
|
|
@@ -97,14 +97,14 @@ sourcecode/readonly.py,sha256=dqGiNVvpfi4Ww8VzwUs6E3SdbqcHL1LFHItIK7IjrM0,5830
|
|
|
97
97
|
sourcecode/reconciliation.py,sha256=GU-1PTcVr8zcbtC7BASfpHcZndP9AdboXPNQiBc0fzo,34251
|
|
98
98
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
99
99
|
sourcecode/reference_facts.py,sha256=Ns495c6eTmq2SrqPkcUrJUJru4_wj_yRWm4YDeJwves,13440
|
|
100
|
-
sourcecode/release_info.py,sha256=
|
|
100
|
+
sourcecode/release_info.py,sha256=Y3PXhq7U_6Ewu2irpSWvlOzcSHs1cQlemvDXGtC1RnQ,5378
|
|
101
101
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
102
102
|
sourcecode/remedies.py,sha256=9eAuP9ndw78YvuUtOi2VvoeNnVCMh1q0uxfnPIkvx7c,7838
|
|
103
103
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
104
104
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
105
105
|
sourcecode/repository_ir.py,sha256=x0u0m-Dlgtcfr7VsggnKRw_mCobe6B7lZMw3U2H1rd8,368709
|
|
106
106
|
sourcecode/ris.py,sha256=nc0d6boOiREFqXO4eLHHaYcnqZj4pT_AG43-Xz9ejkc,24862
|
|
107
|
-
sourcecode/risk.py,sha256=
|
|
107
|
+
sourcecode/risk.py,sha256=QSm_F-3tGncQNMcdKGgr5bVONM8614J8n8qmP9x4FDU,73200
|
|
108
108
|
sourcecode/rule_catalog.py,sha256=pTkgkQZ1u7atB3fkQJ9BPWo8lNiamvEuRN7MtRgle0c,5263
|
|
109
109
|
sourcecode/rule_pass.py,sha256=DTNxD-nAecMJhIzI1LCd9FyRumInQSZjitacNh4Zoc8,14671
|
|
110
110
|
sourcecode/runs.py,sha256=zIxEPz9DBVVCrX958bZR6bODyfH56A6srAyc8LFMs8I,5303
|
|
@@ -113,7 +113,7 @@ sourcecode/sarif.py,sha256=3hQEegUxIZbojFdY59oB-yKsK-rnafHdTNmYsEP2--A,25984
|
|
|
113
113
|
sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
|
|
114
114
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
115
115
|
sourcecode/security_config.py,sha256=_tdyI939NeOlgwQWNCXPfsqaB1fTyNROizYqTOdvWGQ,3718
|
|
116
|
-
sourcecode/security_config_scan.py,sha256=
|
|
116
|
+
sourcecode/security_config_scan.py,sha256=7DD0ou6xsOPhdUbBbEKLDM07PREeEHNISQltboHPnRU,29976
|
|
117
117
|
sourcecode/security_posture.py,sha256=FiEGood01y5gKhuYyL6NK4VT-WtCUMJwHOSR5xTfnPI,54063
|
|
118
118
|
sourcecode/semantic_analyzer.py,sha256=bpgdC6m0_ftVtRf3rSdwhbhWjnZnGxRXaZVcfe4BbcQ,95414
|
|
119
119
|
sourcecode/semantic_impact_engine.py,sha256=t09IirGC3JjQDy33JZd1_WKzQVKXkoNl3-XEUr5kjis,20563
|
|
@@ -121,7 +121,7 @@ sourcecode/semantic_integration_engine.py,sha256=7a0WqAInOv39f0Yr_94TYo_JP_8QpeI
|
|
|
121
121
|
sourcecode/semantic_services.py,sha256=nbUuPv-F01USTt_9CHT8iy_ucCIw3fz4W3Aquea_pd4,10782
|
|
122
122
|
sourcecode/serializer.py,sha256=KiUUHMQNWOOJye95bZQKsRKFkVeVK0Vb2Y7IocQjVbk,140262
|
|
123
123
|
sourcecode/servlet_surface.py,sha256=aOeRne07man8DwHsbog4FTOyF3sYV-oP_N1e4gt3Uvk,9115
|
|
124
|
-
sourcecode/source_text.py,sha256=
|
|
124
|
+
sourcecode/source_text.py,sha256=uir8uBoOnxuH2R-eDJp7mqkDSQRgipxpFdjddLycj-E,8081
|
|
125
125
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
126
126
|
sourcecode/spring_findings.py,sha256=-0EZMFpZxPRmK2FiR4d7Mk4O73c4wkga-Dqs1-fBSUA,22895
|
|
127
127
|
sourcecode/spring_impact.py,sha256=GW77k7KMn13QmzP5520BbVcXSCYlTFeD1bFibhbmzJM,83907
|
|
@@ -205,8 +205,8 @@ sourcecode/telemetry/consent.py,sha256=pQdl-QeLl6Gcibn0eWHSKZrm-HYSsjpVqOnjrgFp8
|
|
|
205
205
|
sourcecode/telemetry/events.py,sha256=4_yeO58U-Cwc1Qb27VB0_EjhmroY0k91n3_VGxeALB8,2776
|
|
206
206
|
sourcecode/telemetry/filters.py,sha256=RzxauTz8HliO4BllQnXEXc7zTeqdCZi5MgqGEDuW7OQ,6570
|
|
207
207
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
208
|
-
sourcecode-
|
|
209
|
-
sourcecode-
|
|
210
|
-
sourcecode-
|
|
211
|
-
sourcecode-
|
|
212
|
-
sourcecode-
|
|
208
|
+
sourcecode-5.0.0.dist-info/METADATA,sha256=mW6-jtrJZ6KxEAqRhncgpItWR93mFkL5sHDq0NjTd1Y,42410
|
|
209
|
+
sourcecode-5.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
210
|
+
sourcecode-5.0.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
211
|
+
sourcecode-5.0.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
212
|
+
sourcecode-5.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|