sourcecode 4.17.0__py3-none-any.whl → 4.18.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 CHANGED
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "4.17.0"
7
+ __version__ = "4.18.0"
sourcecode/cli.py CHANGED
@@ -8285,14 +8285,20 @@ def spring_audit_cmd(
8285
8285
  )
8286
8286
  _stopped_early: Optional[str] = None
8287
8287
 
8288
+ from sourcecode import context_cache as _ctxcache
8289
+
8288
8290
  phase = f"auditing {len(file_list)} Java files"
8289
8291
  with _expensive_analysis_scope("spring-audit", target, phase):
8290
8292
  _prog = Progress()
8291
8293
  _prog.start(phase)
8292
8294
  try:
8293
- cir = ContextGraph.build(
8294
- file_list, target, progress=_work_sink(_prog)
8295
- ).cir
8295
+ # The parse is ~89 % of this command's wall clock (measured: 8,54 s of
8296
+ # 9,53 s on a 1 303-file repository) and it is the same parse the other
8297
+ # knowledge commands share, so it is fetched rather than rebuilt — which
8298
+ # is what makes a warm worth anything here (cold == warm before C3-94).
8299
+ cir = _ctxcache.shared_cir(
8300
+ target, file_list, progress=_work_sink(_prog)
8301
+ )
8296
8302
  # C3-85: the stretch between the last `linking` tick and the first rule
8297
8303
  # tick reports nothing, and what stays on the line is `n/n` — a counter
8298
8304
  # that finished, reading as a stage still running. Measured on keycloak
@@ -9966,7 +9972,7 @@ def _migration_blast_radius(
9966
9972
  from sourcecode.spring_model import SpringSemanticModel
9967
9973
 
9968
9974
  try:
9969
- cir, _ = _ctxcache.get_or_build_cir(_resolve_repo_root(target), target, file_list)
9975
+ cir = _ctxcache.shared_cir(target, file_list)
9970
9976
  except Exception:
9971
9977
  cir = ContextGraph.build(file_list, target).cir
9972
9978
  model = SpringSemanticModel.build(cir)
@@ -10520,10 +10526,7 @@ def impact_chain_cmd(
10520
10526
  # a fresh build, exactly as explain does, so impact-chain never breaks.
10521
10527
  from sourcecode import context_cache as _ctxcache
10522
10528
  try:
10523
- cir, _ = _ctxcache.get_or_build_cir(
10524
- _resolve_repo_root(target), target, file_list,
10525
- progress=_work_sink(_prog),
10526
- )
10529
+ cir = _ctxcache.shared_cir(target, file_list, progress=_work_sink(_prog))
10527
10530
  except Exception:
10528
10531
  cir = ContextGraph.build(file_list, target, progress=_work_sink(_prog)).cir
10529
10532
  _model = SpringSemanticModel.build(cir)
@@ -10849,11 +10852,13 @@ def explain_cmd(
10849
10852
  )
10850
10853
  raise typer.Exit(code=1)
10851
10854
 
10852
- # AI Context Cache — operates at the *knowledge* level: it caches the
10853
- # reusable Canonical IR (the expensive Java parse), keyed only by knowledge
10854
- # state, not by command. explain then derives its answer cheaply from that
10855
- # shared CIR. The same cached CIR is reused for free by any other command.
10856
- # Provider-agnostic and best-effort: any fault degrades to a fresh build.
10855
+ # AI Context Cache — operates at the *knowledge* level: it caches the reusable
10856
+ # Canonical IR (the expensive Java parse), keyed by knowledge state and by the
10857
+ # analysed set, not by command. explain then derives its answer cheaply from
10858
+ # that shared CIR, and any other command over the same files reuses it for
10859
+ # free. Provider-agnostic and best-effort: any fault degrades to a fresh build.
10860
+ # This call site keeps the lower-level entry point because it reports the
10861
+ # lookup (`_cc_look`); `shared_cir` is the same door without the receipt.
10857
10862
  _prog = Progress()
10858
10863
  _prog.start(f"explaining {class_name} ({len(file_list)} files)")
10859
10864
  _cc_look = None
@@ -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 only by knowledge state — not by command —
666
- so every command that needs a CIR shares one cache entry. On a hit the full
667
- CIR is rebuilt cheaply from the cached raw IR via ``ir_dict_to_canonical``,
668
- skipping the parse entirely. Best-effort: any cache fault falls back to a
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
- key = cache.knowledge_key(SCOPE_JAVA_CIR, options={"since": since} if since else None)
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
- if isinstance(raw, dict):
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.
@@ -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
- name = ""
131
- for m in _TYPE_RE.finditer(source):
132
- if m.start() > offset:
133
- break
134
- name = m.group("name")
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/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 when it is safe.
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-safe by the same rule `peek_cir` documents: the shared knowledge key
1119
- is repo-wide and carries no scope, so a run bounded to a subdirectory
1120
- neither reads it (it describes more than this run's scope) nor writes it (it
1121
- would store a truncated CIR under the repo-wide key). Those runs keep their
1122
- own unshared build.
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.context_graph import ContextGraph
1125
+ from sourcecode.context_cache import shared_cir as _shared_cir
1125
1126
 
1126
- repo_root = _repo_root_of(root)
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/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>(?:\s*@[\w.]+(?:\([^;{}]*?\))?\s*)*)"
155
- r"(?P<sig>\s*(?:public|protected|private)?\s*(?:static\s+)?"
156
- r"[\w<>\[\].?,\s]+\s+(?P<name>[A-Za-z_$][\w$]*)\s*"
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.canonical_ir import build_canonical_ir
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
- self.cir = cir if cir is not None else build_canonical_ir(
812
- find_java_files(self.root), self.root
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
- name = fallback
253
- for m in re.finditer(
254
- r"^[ \t]*(?:public|final|abstract|static|\s)*\s*(?:class|interface|enum)\s+(?P<n>\w+)",
255
- source[:offset],
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: 4.17.0
3
+ Version: 4.18.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
- ![Version](https://img.shields.io/badge/version-4.17.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-4.18.0-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -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 4.17.0 — and, on a build that has aged,
129
+ ask version # ask 4.18.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=oSpIjlJUp7yJ6x7QLvX2_MNZMIRyxWZlgQM8hLXJ4tQ,309
1
+ sourcecode/__init__.py,sha256=LpY49oJbepgtrBXuaM-nvqCIK9EhzKSzsI0twe763hk,309
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
@@ -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=FokyiwVRSX7OyKNixcXObqJouSFxG5gxj2OSFoJLcsw,604054
21
+ sourcecode/cli.py,sha256=rHwUACJ4ehe-QUUDB95LXuNrroNc_0BniGqOJhsFtTs,604512
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=kzwARIQrq8lUCXzV1iKLXCe95k71eS25FTyV7LPM6RU,31334
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
@@ -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=I2MAeec51ljXf7oLEop0D52Q-usovOFT-t4huQLB5pw,9295
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
@@ -85,7 +85,7 @@ sourcecode/path_filters.py,sha256=LmTYq735orssKTIuxTX1mE1FHAXF7dVEJEse35xE1RA,12
85
85
  sourcecode/perf.py,sha256=vZDEj1wS905peXmREdOniZrWCikElOyPTw-E32zVZAQ,25847
86
86
  sourcecode/phased_run.py,sha256=WiQhsSJyFZcRsqrsiHzQhzgSlRtOuSDe-Bd-g4BypYk,16420
87
87
  sourcecode/pipe_contract.py,sha256=PML0Er5d8uDyec4OrdUXuyqHbGPUYndjeaKUjkFz2u4,8369
88
- sourcecode/posture.py,sha256=20mPdF9FNZK3FDxIK5qkd3qZhARJJboMGoRmGEmm0Nw,76810
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
@@ -104,7 +104,7 @@ sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg
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=Umc8agAoqBrFxRDi3I3um3EhLrf9G1m2GPz2Gg0Ox1U,71504
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=vpAglQkh7QxUhY_eR6aOOE5d-aU2tsEd-bE8pMoFz8g,30021
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=5ZOqD04Vpdc6Jl6tUlkpeXtM3wXYC297hczZ71iksVk,4819
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-4.17.0.dist-info/METADATA,sha256=kWkJB10gFORG-sJiK6IEvaNZB2g2gvq05IbiIU1X7JA,42089
209
- sourcecode-4.17.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
210
- sourcecode-4.17.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
211
- sourcecode-4.17.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
212
- sourcecode-4.17.0.dist-info/RECORD,,
208
+ sourcecode-4.18.0.dist-info/METADATA,sha256=dlV7aqM2IWphltK1Fy-6IwjH4D_y3UGIymO26VBZTEU,42089
209
+ sourcecode-4.18.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
210
+ sourcecode-4.18.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
211
+ sourcecode-4.18.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
212
+ sourcecode-4.18.0.dist-info/RECORD,,