sourcecode 2.0.0__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +28 -2
- sourcecode/migrate_check.py +64 -0
- sourcecode/security_posture.py +608 -0
- sourcecode/spring_findings.py +4 -0
- sourcecode/spring_security_audit.py +31 -0
- sourcecode/summarizer.py +33 -2
- sourcecode/validation_inference.py +249 -0
- sourcecode-2.1.0.dist-info/METADATA +241 -0
- {sourcecode-2.0.0.dist-info → sourcecode-2.1.0.dist-info}/RECORD +13 -11
- sourcecode-2.0.0.dist-info/METADATA +0 -925
- {sourcecode-2.0.0.dist-info → sourcecode-2.1.0.dist-info}/WHEEL +0 -0
- {sourcecode-2.0.0.dist-info → sourcecode-2.1.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-2.0.0.dist-info → sourcecode-2.1.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/summarizer.py
CHANGED
|
@@ -259,7 +259,15 @@ class ProjectSummarizer:
|
|
|
259
259
|
paragraphs.append(" ".join(current_lines).strip())
|
|
260
260
|
current_lines = []
|
|
261
261
|
continue
|
|
262
|
-
|
|
262
|
+
# Treat HTML-markup-only lines (<img .../>, <p align=...>, <a><img></a>)
|
|
263
|
+
# as paragraph breaks, like badges/links — otherwise a README whose first
|
|
264
|
+
# block is a centered logo leaks raw markup into the project summary.
|
|
265
|
+
_html_stripped = _re.sub(r"<[^>]+>", "", line).strip()
|
|
266
|
+
if (
|
|
267
|
+
_BADGE_RE.match(line)
|
|
268
|
+
or _LINK_ONLY_RE.match(line)
|
|
269
|
+
or ("<" in line and ">" in line and not _html_stripped)
|
|
270
|
+
):
|
|
263
271
|
if current_lines:
|
|
264
272
|
paragraphs.append(" ".join(current_lines).strip())
|
|
265
273
|
current_lines = []
|
|
@@ -290,9 +298,32 @@ class ProjectSummarizer:
|
|
|
290
298
|
_link_count = len(_MD_LINK_RE.findall(paragraph))
|
|
291
299
|
if _link_count > 2 and _link_count * 30 > len(paragraph):
|
|
292
300
|
continue
|
|
293
|
-
|
|
301
|
+
# Render to clean prose: unwrap inline markdown links, drop images,
|
|
302
|
+
# strip residual inline HTML tags and emphasis markers. A paragraph that
|
|
303
|
+
# collapses below the usefulness floor after cleaning is skipped.
|
|
304
|
+
cleaned = self._clean_inline_markup(paragraph)
|
|
305
|
+
if len(cleaned) < 30:
|
|
306
|
+
continue
|
|
307
|
+
return cleaned
|
|
294
308
|
return None
|
|
295
309
|
|
|
310
|
+
@staticmethod
|
|
311
|
+
def _clean_inline_markup(text: str) -> str:
|
|
312
|
+
"""Reduce README markdown/HTML to plain prose (presentation-only).
|
|
313
|
+
|
|
314
|
+
Removes inline images, unwraps `[label](url)` to `label`, strips residual
|
|
315
|
+
inline HTML tags and bold/italic/code markers, and collapses whitespace.
|
|
316
|
+
Does not touch underscores (they occur inside identifiers)."""
|
|
317
|
+
import re as _re
|
|
318
|
+
text = _re.sub(r"!\[[^\]]*\]\([^)]*\)", "", text) #  images
|
|
319
|
+
text = _re.sub(r"\[([^\]]+)\]\([^)]*\)", r"\1", text) # [label](url) -> label
|
|
320
|
+
text = _re.sub(r"<[^>]+>", "", text) # residual inline HTML
|
|
321
|
+
text = _re.sub(r"\*\*(.+?)\*\*", r"\1", text) # **bold** -> bold
|
|
322
|
+
text = _re.sub(r"\*(.+?)\*", r"\1", text) # *italic* -> italic
|
|
323
|
+
text = text.replace("`", "") # `code` -> code
|
|
324
|
+
text = _re.sub(r"\s+", " ", text).strip()
|
|
325
|
+
return text
|
|
326
|
+
|
|
296
327
|
_TYPE_LABELS: dict[str, str] = {
|
|
297
328
|
"cli": "CLI",
|
|
298
329
|
"api": "API",
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""validation_inference.py — Knowledge-Layer validation-pattern inference (P1-C).
|
|
2
|
+
|
|
3
|
+
Answers the field-audit failure "the report goes quiet or says 0 validated" by
|
|
4
|
+
classifying a repo's request-body validation *pattern* instead of counting zeros
|
|
5
|
+
(DESIGN-robustness-security-inference.md §2 / §5). The true finding on the field
|
|
6
|
+
repo was **909 `@RequestBody` and 0 `@Valid`** — "a bean-validation framework is
|
|
7
|
+
on the classpath but unused", not "nothing to report".
|
|
8
|
+
|
|
9
|
+
This is the Knowledge Layer: it reads facts that already exist — endpoint verbs,
|
|
10
|
+
`@Valid`/`@Validated` markers captured on handler nodes, and
|
|
11
|
+
`jakarta/javax.validation` import edges — and emits an Evidence-Confidence
|
|
12
|
+
`Verdict` per the shared §0 envelope. It adds no IR atom.
|
|
13
|
+
|
|
14
|
+
VAI note (§0.5): the only names this module keys on are **published open
|
|
15
|
+
standards** (`jakarta.validation`, `javax.validation`, the bean-validation
|
|
16
|
+
annotation vocabulary). No client / convention name enters a predicate.
|
|
17
|
+
|
|
18
|
+
ARCHITECTURAL DIVERGENCE — manual_validation (design §2, deferred)
|
|
19
|
+
-----------------------------------------------------------------
|
|
20
|
+
Design §2 also asks for a `manual_validation` class detected by the **β
|
|
21
|
+
GuardClause** shape on handler bodies (a guard that throws on a bad parameter).
|
|
22
|
+
That path assumes guard atoms cover handlers. They do NOT: `_extract_guard_facts`
|
|
23
|
+
(repository_ir.py) scans only `symbol_kind in {"method", "constructor"}`, and a
|
|
24
|
+
mapped handler surfaces as kind `"endpoint"` — so `guard_facts` is empty for
|
|
25
|
+
every handler body. Design §8 classifies §2 as *Incremental / no new primitive*;
|
|
26
|
+
against the current architecture that is false. Rather than smuggle in an IR
|
|
27
|
+
change under an "Incremental" increment, `manual_validation` is deferred and the
|
|
28
|
+
conflict is documented in the design doc (EPV / INV-2: build the endpoint-scoped
|
|
29
|
+
guard capability only when this consumer is ratified). The three classes that ARE
|
|
30
|
+
reuse-only (active / available-unused / none) ship here and already resolve the
|
|
31
|
+
field defect.
|
|
32
|
+
"""
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
from typing import TYPE_CHECKING
|
|
37
|
+
|
|
38
|
+
from sourcecode.security_posture import (
|
|
39
|
+
Confidence,
|
|
40
|
+
Evidence,
|
|
41
|
+
Verdict,
|
|
42
|
+
_annotation_simple,
|
|
43
|
+
_graph_nodes,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from sourcecode.canonical_ir import CanonicalRepositoryIR
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Published bean-validation import namespaces — DATA, subtract/match only (§0.5).
|
|
51
|
+
_VALIDATION_NAMESPACES: tuple[str, ...] = (
|
|
52
|
+
"jakarta.validation", "javax.validation",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Published bean-validation constraint / marker simple-names (open standard).
|
|
56
|
+
_BEAN_VALIDATION_VOCAB: frozenset[str] = frozenset({
|
|
57
|
+
"Valid", "Validated", "NotNull", "NotEmpty", "NotBlank", "Null", "AssertTrue",
|
|
58
|
+
"AssertFalse", "Min", "Max", "DecimalMin", "DecimalMax", "Digits", "Positive",
|
|
59
|
+
"PositiveOrZero", "Negative", "NegativeOrZero", "Size", "Pattern", "Email",
|
|
60
|
+
"Past", "PastOrPresent", "Future", "FutureOrPresent",
|
|
61
|
+
})
|
|
62
|
+
_VALIDATE_MARKERS: frozenset[str] = frozenset({"Valid", "Validated"})
|
|
63
|
+
|
|
64
|
+
# Body-carrying HTTP verbs — the surface where request-body validation applies.
|
|
65
|
+
_BODY_VERBS: frozenset[str] = frozenset({"POST", "PUT", "PATCH"})
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _validation_available(cir: "CanonicalRepositoryIR") -> tuple[bool, list[str]]:
|
|
69
|
+
"""True when a published bean-validation framework is on the classpath,
|
|
70
|
+
proven structurally by an import edge into a validation namespace (or, as a
|
|
71
|
+
fallback, any bean-validation annotation present anywhere). Returns
|
|
72
|
+
(available, evidence_imports)."""
|
|
73
|
+
imports_seen: list[str] = []
|
|
74
|
+
for edge in getattr(cir, "dependencies", None) or []:
|
|
75
|
+
if not isinstance(edge, dict) or edge.get("type") != "imports":
|
|
76
|
+
continue
|
|
77
|
+
target = str(edge.get("to") or "")
|
|
78
|
+
if any(target.startswith(ns + ".") or target == ns for ns in _VALIDATION_NAMESPACES):
|
|
79
|
+
imports_seen.append(target)
|
|
80
|
+
if imports_seen:
|
|
81
|
+
return True, sorted(set(imports_seen))
|
|
82
|
+
|
|
83
|
+
# Fallback: a bean-validation annotation used anywhere also proves the
|
|
84
|
+
# framework is on the classpath (even without a visible import edge).
|
|
85
|
+
for node in _graph_nodes(cir):
|
|
86
|
+
if not isinstance(node, dict):
|
|
87
|
+
continue
|
|
88
|
+
for tok in node.get("annotations") or []:
|
|
89
|
+
if _annotation_simple(str(tok)) in _BEAN_VALIDATION_VOCAB:
|
|
90
|
+
return True, []
|
|
91
|
+
return False, []
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _validated_handler_symbols(cir: "CanonicalRepositoryIR") -> set[str]:
|
|
95
|
+
"""handler_symbols whose node carries a captured `_validated_params` value
|
|
96
|
+
naming a @Valid/@Validated parameter (reuses the existing Phase-3.2 capture)."""
|
|
97
|
+
out: set[str] = set()
|
|
98
|
+
for node in _graph_nodes(cir):
|
|
99
|
+
if not isinstance(node, dict):
|
|
100
|
+
continue
|
|
101
|
+
av = node.get("annotation_values") or {}
|
|
102
|
+
params = av.get("_validated_params", "") if isinstance(av, dict) else ""
|
|
103
|
+
if not params:
|
|
104
|
+
continue
|
|
105
|
+
# the capture only fires when a marker is present, but re-check by shape
|
|
106
|
+
if "@Valid" in params or "@Validated" in params:
|
|
107
|
+
out.add(node.get("fqn") or "")
|
|
108
|
+
out.discard("")
|
|
109
|
+
return out
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class ValidationInferenceResult:
|
|
114
|
+
"""Repo + per-endpoint request-body validation-pattern projection (P1-C)."""
|
|
115
|
+
|
|
116
|
+
pattern: str # repo-level class (see classify)
|
|
117
|
+
verdict: Verdict
|
|
118
|
+
endpoint_findings: list[dict] = field(default_factory=list)
|
|
119
|
+
rollup: dict = field(default_factory=dict)
|
|
120
|
+
|
|
121
|
+
def to_dict(self) -> dict:
|
|
122
|
+
return {
|
|
123
|
+
"provenance": "validation_inference projection (P1-C)",
|
|
124
|
+
"pattern": self.pattern,
|
|
125
|
+
"verdict": self.verdict.to_dict(),
|
|
126
|
+
"endpoints": self.endpoint_findings,
|
|
127
|
+
"rollup": self.rollup,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenceResult:
|
|
132
|
+
"""Classify the repo's request-body validation pattern (P1-C).
|
|
133
|
+
|
|
134
|
+
Repo-level class (DESIGN §2):
|
|
135
|
+
- ``bean_validation_active`` — @Valid present on body handlers.
|
|
136
|
+
- ``bean_validation_available_unused`` — validation on the classpath and
|
|
137
|
+
body endpoints exist, but no handler validates → risk (Likely).
|
|
138
|
+
- ``no_validation_detected`` — none of the above (Unknown).
|
|
139
|
+
(``manual_validation`` deferred — see module docstring.)
|
|
140
|
+
|
|
141
|
+
Never raises.
|
|
142
|
+
"""
|
|
143
|
+
available, import_evidence = _validation_available(cir)
|
|
144
|
+
validated_handlers = _validated_handler_symbols(cir)
|
|
145
|
+
|
|
146
|
+
body_eps = [ep for ep in cir.endpoints if str(ep.method).upper() in _BODY_VERBS]
|
|
147
|
+
body_total = len(body_eps)
|
|
148
|
+
validated_body = sum(1 for ep in body_eps if ep.handler_symbol in validated_handlers)
|
|
149
|
+
unvalidated_body = body_total - validated_body
|
|
150
|
+
|
|
151
|
+
# per-endpoint findings (only the body surface — where a request body applies)
|
|
152
|
+
endpoint_findings: list[dict] = []
|
|
153
|
+
rollup = {"validated": 0, "available_unused": 0, "unvalidated_unknown": 0}
|
|
154
|
+
for ep in body_eps:
|
|
155
|
+
if ep.handler_symbol in validated_handlers:
|
|
156
|
+
klass, conf = "validated", Confidence.VERIFIED
|
|
157
|
+
elif available:
|
|
158
|
+
# @RequestBody-shaped verb, framework available, no @Valid → gap
|
|
159
|
+
klass, conf = "available_unused", Confidence.LIKELY
|
|
160
|
+
else:
|
|
161
|
+
klass, conf = "unvalidated_unknown", Confidence.UNKNOWN
|
|
162
|
+
rollup[klass] += 1
|
|
163
|
+
endpoint_findings.append({
|
|
164
|
+
"endpoint_id": ep.id,
|
|
165
|
+
"method": ep.method,
|
|
166
|
+
"path": ep.path,
|
|
167
|
+
"handler_symbol": ep.handler_symbol,
|
|
168
|
+
"finding": klass,
|
|
169
|
+
"confidence": conf.value,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
# repo-level classification + envelope
|
|
173
|
+
common_limitation = (
|
|
174
|
+
"Body surface is approximated by body-carrying verbs (POST/PUT/PATCH); "
|
|
175
|
+
"the exact @RequestBody parameter count is not captured (no IR atom "
|
|
176
|
+
"exposes it), so 'available_unused' may include body-less handlers."
|
|
177
|
+
)
|
|
178
|
+
manual_fn = (
|
|
179
|
+
"manual_validation (a handler that throws on a bad parameter) is not "
|
|
180
|
+
"detected — guard-clause atoms do not cover endpoint-kind handlers "
|
|
181
|
+
"(architectural defer, see module docstring); such a repo classifies "
|
|
182
|
+
"no_validation_detected here."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if validated_body > 0:
|
|
186
|
+
pattern = "bean_validation_active"
|
|
187
|
+
verdict = Verdict(
|
|
188
|
+
claim="Bean validation active on request bodies",
|
|
189
|
+
confidence=Confidence.VERIFIED,
|
|
190
|
+
evidence=Evidence(
|
|
191
|
+
atoms_used=[f"@Valid handler: {h}" for h in sorted(validated_handlers)][:50],
|
|
192
|
+
details={
|
|
193
|
+
"validated_body_endpoints": validated_body,
|
|
194
|
+
"body_endpoints": body_total,
|
|
195
|
+
"unvalidated_body_endpoints": unvalidated_body,
|
|
196
|
+
"validation_available": available,
|
|
197
|
+
},
|
|
198
|
+
),
|
|
199
|
+
limitations=[common_limitation],
|
|
200
|
+
fn_causes=[manual_fn],
|
|
201
|
+
)
|
|
202
|
+
elif available and body_total > 0:
|
|
203
|
+
pattern = "bean_validation_available_unused"
|
|
204
|
+
verdict = Verdict(
|
|
205
|
+
claim="Validation framework present but unused on request bodies",
|
|
206
|
+
confidence=Confidence.LIKELY,
|
|
207
|
+
evidence=Evidence(
|
|
208
|
+
relations_used=[f"imports {i}" for i in import_evidence][:20],
|
|
209
|
+
details={
|
|
210
|
+
"body_endpoints": body_total,
|
|
211
|
+
"validated_body_endpoints": 0,
|
|
212
|
+
"validation_available": True,
|
|
213
|
+
"validation_imports": import_evidence[:20],
|
|
214
|
+
},
|
|
215
|
+
),
|
|
216
|
+
limitations=[
|
|
217
|
+
common_limitation,
|
|
218
|
+
"Validation could live in a shared interceptor / filter this "
|
|
219
|
+
"projection does not traverse.",
|
|
220
|
+
],
|
|
221
|
+
fp_causes=[
|
|
222
|
+
"A body endpoint may be intentionally unvalidated (idempotent "
|
|
223
|
+
"echo, internal-only), or validated manually.",
|
|
224
|
+
],
|
|
225
|
+
fn_causes=[manual_fn],
|
|
226
|
+
)
|
|
227
|
+
else:
|
|
228
|
+
pattern = "no_validation_detected"
|
|
229
|
+
verdict = Verdict(
|
|
230
|
+
claim="No request-body validation pattern detected",
|
|
231
|
+
confidence=Confidence.UNKNOWN,
|
|
232
|
+
evidence=Evidence(details={
|
|
233
|
+
"body_endpoints": body_total,
|
|
234
|
+
"validation_available": available,
|
|
235
|
+
}),
|
|
236
|
+
limitations=[
|
|
237
|
+
"Absence of evidence is not evidence of absence — no @Valid, no "
|
|
238
|
+
"validation import, and no body endpoints, or validation is done "
|
|
239
|
+
"by a mechanism this projection does not model.",
|
|
240
|
+
],
|
|
241
|
+
fn_causes=[manual_fn],
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return ValidationInferenceResult(
|
|
245
|
+
pattern=pattern,
|
|
246
|
+
verdict=verdict,
|
|
247
|
+
endpoint_findings=endpoint_findings,
|
|
248
|
+
rollup=rollup,
|
|
249
|
+
)
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sourcecode
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: defusedxml>=0.7
|
|
21
|
+
Requires-Dist: mcp>=1.0.0
|
|
22
|
+
Requires-Dist: pathspec>=1.0
|
|
23
|
+
Requires-Dist: ruamel-yaml>=0.18
|
|
24
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
25
|
+
Requires-Dist: typer>=0.24
|
|
26
|
+
Provides-Extra: ast
|
|
27
|
+
Requires-Dist: tree-sitter-javascript>=0.21; extra == 'ast'
|
|
28
|
+
Requires-Dist: tree-sitter-typescript>=0.21; extra == 'ast'
|
|
29
|
+
Requires-Dist: tree-sitter>=0.21; extra == 'ast'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: mcp>=1.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.15; extra == 'dev'
|
|
35
|
+
Provides-Extra: mcp
|
|
36
|
+
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# ASK Engine
|
|
40
|
+
|
|
41
|
+
> **Persistent structural intelligence for AI coding agents.**
|
|
42
|
+
|
|
43
|
+
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
49
|
+
> command still works as a deprecated alias (it prints a one-line notice and forwards to
|
|
50
|
+
> `ask`) and remains the Python/PyPI package name for now. The authoritative version is
|
|
51
|
+
> whatever `ask version` reports. See [docs/PRODUCT_IDENTITY.md](docs/PRODUCT_IDENTITY.md).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## The problem
|
|
56
|
+
|
|
57
|
+
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch. For a large Java or TypeScript monolith, that means 5–15 seconds per invocation. Multiply 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
|
+
|
|
59
|
+
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan, every subsequent invocation returns pre-built context in milliseconds. The repo doesn't change? The cache doesn't expire.
|
|
60
|
+
|
|
61
|
+
**The cache is not a performance optimization. It is what makes ASK Engine usable as infrastructure rather than a one-off tool.**
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Proof — measured on real repos
|
|
66
|
+
|
|
67
|
+
| Repo | Size | Cold scan | Cache hit | Speedup |
|
|
68
|
+
|------|------|-----------|-----------|---------|
|
|
69
|
+
| Keycloak | 7,885 Java files | 10.5s | 0.6s | **~17x** |
|
|
70
|
+
| BroadleafCommerce | 2,985 Java files | 2.7s | 0.3s | **~9x** |
|
|
71
|
+
|
|
72
|
+
Cache keyed on content hashes — invalidated only when source changes. On repeated agent sessions against the same codebase, nearly every invocation is a cache hit.
|
|
73
|
+
|
|
74
|
+
At 0.3s per call, ASK Engine becomes **constant infrastructure** inside agent loops — call it before every edit, every PR review, every test run, without batching or caching manually.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Homebrew (macOS / Linux)
|
|
82
|
+
brew tap haroundominique/sourcecode && brew install sourcecode
|
|
83
|
+
|
|
84
|
+
# pip / pipx
|
|
85
|
+
pipx install sourcecode # or: pip install sourcecode
|
|
86
|
+
|
|
87
|
+
ask version # ask 2.1.0
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **Package vs. command.** The install package is named `sourcecode` this release
|
|
91
|
+
> (renaming the distribution is a separate, breaking change). Installing it gives you the
|
|
92
|
+
> canonical **`ask`** command plus the deprecated **`sourcecode`** alias.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Quickstart
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# High-signal structural summary — warm cache ~0.3s, cold 2–10s
|
|
100
|
+
ask --compact
|
|
101
|
+
|
|
102
|
+
# Blast radius: what breaks if this class changes?
|
|
103
|
+
ask impact OrderService /path/to/repo
|
|
104
|
+
|
|
105
|
+
# Spring Boot 2→3 migration readiness (bounded decision summary)
|
|
106
|
+
ask migrate-check /path/to/repo --compact
|
|
107
|
+
|
|
108
|
+
# Spring semantic audit: TX anomalies + security surface
|
|
109
|
+
ask spring-audit /path/to/repo
|
|
110
|
+
|
|
111
|
+
# Onboard to an unfamiliar codebase
|
|
112
|
+
ask onboard /path/to/repo
|
|
113
|
+
|
|
114
|
+
# PR review: risk, test gaps, changed modules
|
|
115
|
+
ask review-pr /path/to/repo --since main
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Full command reference: **[docs/USER_GUIDE.md](docs/USER_GUIDE.md)**.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Capabilities
|
|
123
|
+
|
|
124
|
+
Everything is computed from one cached structural model. Seven groups:
|
|
125
|
+
|
|
126
|
+
### 1 · Structural Context
|
|
127
|
+
Bounded, noise-free repo context designed to drop straight into an agent's context window.
|
|
128
|
+
`ask --compact` · `ask --agent` · `ask onboard` · `ask cold-start`
|
|
129
|
+
→ [reference](docs/USER_GUIDE.md#core-commands)
|
|
130
|
+
|
|
131
|
+
### 2 · Impact Analysis
|
|
132
|
+
Blast radius from a class or interface — reverse dependencies, through Spring DI, to the HTTP endpoints a change reaches.
|
|
133
|
+
`ask impact` · `ask impact-chain` (TX/SEC-enriched) · `ask pr-impact`
|
|
134
|
+
→ [reference](docs/USER_GUIDE.md#core-commands)
|
|
135
|
+
|
|
136
|
+
### 3 · Architecture Intelligence
|
|
137
|
+
The system map: module graph, dependency views, REST surface, per-class summaries, and a symbol-level IR for downstream tooling.
|
|
138
|
+
`ask export` · `ask repo-ir` · `ask endpoints` · `ask explain`
|
|
139
|
+
→ [reference](docs/USER_GUIDE.md#core-commands)
|
|
140
|
+
|
|
141
|
+
### 4 · Migration & Modernization
|
|
142
|
+
Is this codebase ready to upgrade? Per-dimension readiness (Jakarta / Spring Boot / JDK / Hibernate), located blockers, and an effort estimate.
|
|
143
|
+
`ask migrate-check` · `ask modernize`
|
|
144
|
+
→ [migrate-check reference](docs/migrate-check.md) · [MODERNIZATION.md](docs/MODERNIZATION.md)
|
|
145
|
+
|
|
146
|
+
### 5 · Spring Analysis
|
|
147
|
+
Deterministic Spring semantics: transactional anomalies (e.g. `@Transactional` on a private method = silent CGLIB no-op), security surface, request-body validation.
|
|
148
|
+
`ask spring-audit` · `ask validation`
|
|
149
|
+
→ [reference](docs/USER_GUIDE.md#core-commands)
|
|
150
|
+
|
|
151
|
+
### 6 · Developer Workflows
|
|
152
|
+
The everyday loop: diff-based PR review, symptom-driven bug triage, and delta context for continuous agent runs.
|
|
153
|
+
`ask review-pr` · `ask fix-bug` · `ask prepare-context`
|
|
154
|
+
→ [reference](docs/USER_GUIDE.md#typical-workflows)
|
|
155
|
+
|
|
156
|
+
### 7 · Utilities
|
|
157
|
+
`ask rename-class` (word-boundary Java rename) · `ask chunk-file` (split large files for agents) · `ask cache` (status / warm / clear / freshness)
|
|
158
|
+
→ [reference](docs/USER_GUIDE.md)
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## What it does — and doesn't
|
|
163
|
+
|
|
164
|
+
**ASK Engine reduces exploration cost.** It accelerates context acquisition and computes
|
|
165
|
+
blast radius; it does not replace reading code — it reduces how often an agent needs to.
|
|
166
|
+
All signals are **static and deterministic** (annotations, import graph, file structure) —
|
|
167
|
+
no runtime analysis, no LLM guessing.
|
|
168
|
+
|
|
169
|
+
Honest limits worth knowing before you rely on it:
|
|
170
|
+
|
|
171
|
+
- `impact` on an **implementation** class (`OrderServiceImpl`) returns 0 callers in Spring Boot — callers inject the interface. **Always target the interface.**
|
|
172
|
+
- `no_security_signal` on an endpoint means *no recognized method-level annotation*, **not** "unsecured" — Spring Security filter chains and custom authorization annotations show as `no_security_signal` unless taught via config (below).
|
|
173
|
+
- `spring-audit` / `impact-chain` are **Java/Spring only**; non-Java repos return `spring_detected: false`.
|
|
174
|
+
- Event topology (`--type events`) resolves Spring `ApplicationEvent` / `@EventListener` chains only — **not** Kafka/RabbitMQ/Redis routes.
|
|
175
|
+
- Architecture classification is tuned for Spring MVC layered apps; SPI/plugin models (e.g. Quarkus extensions) may be misclassified. JAX-RS subresource-locator endpoint recall is ~65%.
|
|
176
|
+
- Self-invocation `@Transactional` bypass (same-class call skipping the proxy) is not detected.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Pricing
|
|
181
|
+
|
|
182
|
+
> **🎉 Early-adoption: Pro is currently unlocked for everyone.** Every install runs with
|
|
183
|
+
> full Pro entitlements — no size gate, no key. The tiers below describe the model the
|
|
184
|
+
> paywall will return to later.
|
|
185
|
+
|
|
186
|
+
**Gating is by repo size and automation — never by command.** Every command runs at full
|
|
187
|
+
power on Free for small and mid-size repos; you upgrade when the work gets bigger or automated.
|
|
188
|
+
|
|
189
|
+
| | **Free** — €0 | **Pro** — €19/mo · €190/yr per dev |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| Repo size | ≤ 500 Java source files | **> 500 Java files** (enterprise monoliths) |
|
|
192
|
+
| Commands | All of them, full output | Same commands, unlocked at scale |
|
|
193
|
+
| `impact` / `fix-bug` / `review-pr` / `modernize` | ✅ full on small repos | ✅ full on large repos (Free gets a capped preview) |
|
|
194
|
+
| `prepare-context delta` | 30 free runs/repo | unlimited — CI/CD automation |
|
|
195
|
+
| MCP local server, offline, no data egress | ✅ | ✅ |
|
|
196
|
+
|
|
197
|
+
**Non-Java repos are free at any size** — the size limit counts Java source files only.
|
|
198
|
+
ASK Engine monetises enterprise Java monoliths. Activate with `ask activate <key>`.
|
|
199
|
+
Full breakdown: [docs/PRODUCT_TIERS.md](docs/PRODUCT_TIERS.md).
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Configuration & privacy
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
ask config # version, config file path, telemetry status
|
|
207
|
+
ask telemetry disable # anonymous telemetry is on by default (opt-out)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Telemetry collects version, OS, commands, flags, duration, repo-size range, and errors —
|
|
211
|
+
**no source code, paths, secrets, or output**. Disable any time with
|
|
212
|
+
`export SOURCECODE_TELEMETRY=0` (or `DO_NOT_TRACK=1`).
|
|
213
|
+
|
|
214
|
+
**Custom security annotations.** Teach `endpoints`, `spring-audit`, and `explain` about
|
|
215
|
+
project-specific authorization annotations via an optional `sourcecode.config.json` at the
|
|
216
|
+
repo root (otherwise they report `policy: "none_detected"`):
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"customSecurityAnnotations": [
|
|
221
|
+
{ "fullyQualifiedName": "com.example.security.M3FiltroSeguridad", "shortName": "M3FiltroSeguridad" }
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Matching endpoints report `policy: "custom"` and drop out of the `no_security_signal` count.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Documentation
|
|
231
|
+
|
|
232
|
+
| Doc | What it covers |
|
|
233
|
+
|-----|----------------|
|
|
234
|
+
| [USER_GUIDE.md](docs/USER_GUIDE.md) | Full command reference, flags, output schema, workflows |
|
|
235
|
+
| [migrate-check.md](docs/migrate-check.md) | Migration rule catalogue (MIG-001..043) + Hibernate stratification |
|
|
236
|
+
| [MODERNIZATION.md](docs/MODERNIZATION.md) | The modernization product: assess → understand → plan → execute |
|
|
237
|
+
| [PRODUCT_TIERS.md](docs/PRODUCT_TIERS.md) | Free vs Pro, pricing model |
|
|
238
|
+
| [DEMO-5MIN.md](docs/DEMO-5MIN.md) | A reproducible 5-minute demo |
|
|
239
|
+
| [MANUAL-USUARIO.md](docs/MANUAL-USUARIO.md) | Guía de usuario en español |
|
|
240
|
+
| [PRODUCT_IDENTITY.md](docs/PRODUCT_IDENTITY.md) | `ask` (command) vs `sourcecode` (package/alias) |
|
|
241
|
+
| [privacy.md](docs/privacy.md) | Telemetry and data-handling policy |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=6IP6ug5fLqU_NKOIzfMwRs-jSKYDBb21TLg1tIzLWAk,308
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=6_wC4TYuBYxaqckZS0I1vBSMRPeH2vzlDB48gXwOOd4,46627
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=UaxmUU77oTlWxttMU3c6PxHrU-Q6q5N31TeU_NWWTdU,24297
|
|
@@ -8,7 +8,7 @@ sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,41
|
|
|
8
8
|
sourcecode/canonical_ir.py,sha256=U69m8Vkr0p407xF1q5y1KguHXWEAubYw8NFHR9hDNJk,29178
|
|
9
9
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
10
10
|
sourcecode/classifier.py,sha256=YTTCoRdcLEFRVcql9Ow1dE7eYQj0jq2rgx32bRDnb1k,13852
|
|
11
|
-
sourcecode/cli.py,sha256=
|
|
11
|
+
sourcecode/cli.py,sha256=PksGF5DiVcPU0SUHw0vKwQA-9w934ZMKCix1K4WX8Rs,289590
|
|
12
12
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
13
13
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
14
14
|
sourcecode/context_graph.py,sha256=8MVDu06bPhvRDTgUqWEvRME2fOw34bFLf4OUILZd13I,43527
|
|
@@ -37,7 +37,7 @@ sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
|
|
|
37
37
|
sourcecode/license.py,sha256=keFuwNxdAtvK2Ds91Wl79GMYxuxWYnN5Wbw1qBpaoUI,24896
|
|
38
38
|
sourcecode/mcp_nudge.py,sha256=yjwclsBVsDg1BtocGdEi6GYV4vzPo-SBlC9pIcNmlDM,2910
|
|
39
39
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
40
|
-
sourcecode/migrate_check.py,sha256=
|
|
40
|
+
sourcecode/migrate_check.py,sha256=HJhvHuvXDGTJSRQU9_AMx0L94h2TImC1DCGs3m3RTu0,108199
|
|
41
41
|
sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
|
|
42
42
|
sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
|
|
43
43
|
sourcecode/path_filters.py,sha256=VnaD9jxZVNzluackNSTCdIddwzAqIseuSCs_A-gpCDM,6898
|
|
@@ -56,21 +56,23 @@ sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_X
|
|
|
56
56
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
57
57
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
58
58
|
sourcecode/security_config.py,sha256=_m8oQAy2gP7Ho2I-IIMTFFjFVWbJIGlLEOiAWK2TDjs,3503
|
|
59
|
+
sourcecode/security_posture.py,sha256=CAJw4oJD0aAW2yRewvd_fonkzi9_HpUjBZlIDZle00s,26034
|
|
59
60
|
sourcecode/semantic_analyzer.py,sha256=bpgdC6m0_ftVtRf3rSdwhbhWjnZnGxRXaZVcfe4BbcQ,95414
|
|
60
61
|
sourcecode/semantic_impact_engine.py,sha256=HpgjenY6DLPHPomP7Hw850zA-EBokf1S4O2sh8NegdI,20351
|
|
61
62
|
sourcecode/semantic_integration_engine.py,sha256=cUJFZaBfgzsYpj9hRa9CZjo3U9VSfIQV1k2SQYM5HZw,14524
|
|
62
63
|
sourcecode/semantic_services.py,sha256=AO_p7PRfjc-AUh6ThvLMfm4iUGi0issL_frCf8sxGlI,10637
|
|
63
64
|
sourcecode/serializer.py,sha256=_3aLX3iFbHlqVDx_IlmOBQUI6QLPrL_XX2jxF6gL_No,129094
|
|
64
65
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
65
|
-
sourcecode/spring_findings.py,sha256=
|
|
66
|
+
sourcecode/spring_findings.py,sha256=EX7kLZLN74CFyR9iZPm3CI115BfFKNd4WRPuErNljZM,5729
|
|
66
67
|
sourcecode/spring_impact.py,sha256=1Eu1vkdwTVsw92iBEJDe_i_FaSf4uGH9Rb0eSgIAAZc,57542
|
|
67
68
|
sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
|
|
68
|
-
sourcecode/spring_security_audit.py,sha256=
|
|
69
|
+
sourcecode/spring_security_audit.py,sha256=Rk-aSohezdc7YDYbSoJquVnwpkDB8ty1BCD-4Hc4R5A,22832
|
|
69
70
|
sourcecode/spring_semantic.py,sha256=O1nKSGVzlukuxLHQVuCPxc-XrcrMFxwlHA20_dmEGgM,13307
|
|
70
71
|
sourcecode/spring_tx_analyzer.py,sha256=iH9mK0Tgvhgy_Ee2r13KA_RMf8HKN-pjCKQGuyCCBjM,33812
|
|
71
|
-
sourcecode/summarizer.py,sha256=
|
|
72
|
+
sourcecode/summarizer.py,sha256=ByfFxIGVRrGrf3paQPuDOg1rVj78LyzXN_EXBkfbCLU,23582
|
|
72
73
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
73
74
|
sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
|
|
75
|
+
sourcecode/validation_inference.py,sha256=UtmYv-d4TEzT1ihGEipAhtXDrwq0XtVtjMpb3i7NsNc,10815
|
|
74
76
|
sourcecode/validation_surface.py,sha256=A7h0Z9xHjPrxUPkyoPKlhGb8tCF0w0PI_XT-tOj5sC4,21375
|
|
75
77
|
sourcecode/version_check.py,sha256=CHp6ZxTIfo8kyHPCBgJA1uFC0xQCoXMuuOfrW8QTL8o,4942
|
|
76
78
|
sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
|
|
@@ -111,8 +113,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
|
|
|
111
113
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
112
114
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
113
115
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
114
|
-
sourcecode-2.
|
|
115
|
-
sourcecode-2.
|
|
116
|
-
sourcecode-2.
|
|
117
|
-
sourcecode-2.
|
|
118
|
-
sourcecode-2.
|
|
116
|
+
sourcecode-2.1.0.dist-info/METADATA,sha256=YcJphSnTG4SGmAeb7R2XWhj4nIp4-1hazejT7QdPo9Y,10798
|
|
117
|
+
sourcecode-2.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
118
|
+
sourcecode-2.1.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
119
|
+
sourcecode-2.1.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
120
|
+
sourcecode-2.1.0.dist-info/RECORD,,
|