sourcecode 2.4.0__py3-none-any.whl → 2.5.1__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/archetype.py +46 -0
- sourcecode/cli.py +429 -3
- sourcecode/context_graph.py +32 -24
- sourcecode/repository_ir.py +116 -2
- sourcecode/retrieval/__init__.py +192 -0
- sourcecode/retrieval/context.py +241 -0
- sourcecode/retrieval/errors.py +41 -0
- sourcecode/retrieval/executor.py +51 -0
- sourcecode/retrieval/planner.py +297 -0
- sourcecode/retrieval/query.py +484 -0
- sourcecode/retrieval/request.py +94 -0
- sourcecode/retrieval/resolution.py +135 -0
- sourcecode/retrieval/result.py +144 -0
- sourcecode/retrieval/retriever.py +62 -0
- sourcecode/retrieval/runtime.py +259 -0
- sourcecode/retrieval/steps.py +242 -0
- sourcecode/retrieval/steps_endpoint.py +311 -0
- sourcecode/retrieval/steps_graph.py +248 -0
- sourcecode/retrieval/steps_impact.py +293 -0
- sourcecode/retrieval/steps_intf.py +247 -0
- sourcecode/retrieval/steps_struct.py +338 -0
- sourcecode/retrieval/steps_txsec.py +351 -0
- sourcecode/security_config.py +4 -4
- sourcecode/spring_impact.py +52 -10
- {sourcecode-2.4.0.dist-info → sourcecode-2.5.1.dist-info}/METADATA +4 -4
- {sourcecode-2.4.0.dist-info → sourcecode-2.5.1.dist-info}/RECORD +30 -12
- {sourcecode-2.4.0.dist-info → sourcecode-2.5.1.dist-info}/WHEEL +0 -0
- {sourcecode-2.4.0.dist-info → sourcecode-2.5.1.dist-info}/entry_points.txt +0 -0
- {sourcecode-2.4.0.dist-info → sourcecode-2.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""TRANSACTIONS & SECURITY pack step handlers (Fase 6).
|
|
2
|
+
|
|
3
|
+
Six intents (TRANSACTION_FLOW, TRANSACTION_ENTRYPOINTS, TRANSACTION_PROPAGATION,
|
|
4
|
+
SECURITY_SURFACE, SECURITY_CHAIN, VALIDATION_POINTS) expressed purely as compositions of
|
|
5
|
+
these projection steps. NO new analyzer: every step reuses an existing engine and projects
|
|
6
|
+
its already-computed output.
|
|
7
|
+
|
|
8
|
+
* Transactions → `SpringSemanticModel.tx_index` (structural boundaries) + `run_tx_audit`.
|
|
9
|
+
* Security → `run_security_audit` (+ the endpoint model for scoping).
|
|
10
|
+
* Validation → `build_validation_surface`.
|
|
11
|
+
|
|
12
|
+
No step opens source or parses: the ContextGraph / SpringSemanticModel are acquired once by
|
|
13
|
+
the shared `KnowledgeContext`; steps only read them. Ordering is deterministic; ranking
|
|
14
|
+
reuses the engines' own severity/confidence (`spring_findings.SEVERITY_ORDER`).
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from sourcecode.spring_findings import SEVERITY_ORDER
|
|
21
|
+
from sourcecode.retrieval.query import (
|
|
22
|
+
CollectSecurityEvidence,
|
|
23
|
+
RankByCriticality,
|
|
24
|
+
ResolvePropagation,
|
|
25
|
+
ResolveSecurityChain,
|
|
26
|
+
ResolveSecuritySurface,
|
|
27
|
+
ResolveTransactionEntrypoints,
|
|
28
|
+
ResolveTransactionFlow,
|
|
29
|
+
ResolveValidationPoints,
|
|
30
|
+
Step,
|
|
31
|
+
)
|
|
32
|
+
from sourcecode.retrieval.result import (
|
|
33
|
+
Explanation,
|
|
34
|
+
KnowledgeEntity,
|
|
35
|
+
Observation,
|
|
36
|
+
Provenance,
|
|
37
|
+
RetrievalEvidence,
|
|
38
|
+
)
|
|
39
|
+
from sourcecode.retrieval.steps import (
|
|
40
|
+
SLOT_CONFIDENCE,
|
|
41
|
+
SLOT_ENTITIES,
|
|
42
|
+
SLOT_EVIDENCE,
|
|
43
|
+
SLOT_EXPLANATION,
|
|
44
|
+
SLOT_OBSERVATIONS,
|
|
45
|
+
SLOT_PROVENANCE,
|
|
46
|
+
)
|
|
47
|
+
from sourcecode.retrieval.steps_impact import SLOT_TARGET
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from sourcecode.retrieval.runtime import StepContext, StepRegistry
|
|
51
|
+
|
|
52
|
+
SLOT_SERVICES = "txsec_services"
|
|
53
|
+
_PROPAGATION_PATTERNS = frozenset({"TX-002", "TX-003", "TX-004"})
|
|
54
|
+
_CONF_RANK = {"high": 0, "medium": 1, "low": 2}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ── transactions (structural: SpringSemanticModel.tx_index) ──────────────────────
|
|
58
|
+
def resolve_transaction_flow(step: Step, ctx: "StepContext") -> None:
|
|
59
|
+
assert isinstance(step, ResolveTransactionFlow)
|
|
60
|
+
tx = ctx.knowledge.spring_model.tx_index
|
|
61
|
+
boundaries = list(tx.all_declared)
|
|
62
|
+
ctx.put(SLOT_ENTITIES, [_boundary_entity(b, "transaction_boundary") for b in boundaries])
|
|
63
|
+
ctx.put(SLOT_EVIDENCE, [_boundary_evidence(b) for b in boundaries])
|
|
64
|
+
ctx.put(SLOT_OBSERVATIONS, _stats_observations(tx))
|
|
65
|
+
ctx.put(SLOT_CONFIDENCE, _agg_conf([b.confidence for b in boundaries]))
|
|
66
|
+
_add_service(ctx, "SpringSemanticModel")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resolve_transaction_entrypoints(step: Step, ctx: "StepContext") -> None:
|
|
70
|
+
"""Transactional boundaries whose enclosing class is a controller — where transactions
|
|
71
|
+
begin at the API surface. Reuses tx_index × the endpoint model's controller set."""
|
|
72
|
+
assert isinstance(step, ResolveTransactionEntrypoints)
|
|
73
|
+
model = ctx.knowledge.spring_model
|
|
74
|
+
controllers = set(model.endpoint_index.controller_fqns)
|
|
75
|
+
boundaries = [b for b in model.tx_index.all_declared if _enclosing_class(b.symbol) in controllers]
|
|
76
|
+
ctx.put(SLOT_ENTITIES, [_boundary_entity(b, "transaction_entrypoint") for b in boundaries])
|
|
77
|
+
ctx.put(SLOT_EVIDENCE, [_boundary_evidence(b) for b in boundaries])
|
|
78
|
+
ctx.put(SLOT_CONFIDENCE, _agg_conf([b.confidence for b in boundaries]))
|
|
79
|
+
_add_service(ctx, "SpringSemanticModel")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_propagation(step: Step, ctx: "StepContext") -> None:
|
|
83
|
+
"""Boundaries with non-default propagation + the propagation-risk tx audit findings.
|
|
84
|
+
Reuses tx_index + `run_tx_audit`."""
|
|
85
|
+
assert isinstance(step, ResolvePropagation)
|
|
86
|
+
from sourcecode.spring_tx_analyzer import run_tx_audit
|
|
87
|
+
|
|
88
|
+
model = ctx.knowledge.spring_model
|
|
89
|
+
non_default = [b for b in model.tx_index.all_declared if b.propagation != "REQUIRED"]
|
|
90
|
+
findings = [
|
|
91
|
+
f
|
|
92
|
+
for f in run_tx_audit(
|
|
93
|
+
ctx.knowledge.context_graph.cir, root=ctx.knowledge.root, model=model
|
|
94
|
+
).findings
|
|
95
|
+
if f.pattern_id in _PROPAGATION_PATTERNS
|
|
96
|
+
]
|
|
97
|
+
ctx.put(SLOT_ENTITIES, [_boundary_entity(b, "propagation_point") for b in non_default])
|
|
98
|
+
ctx.put(SLOT_EVIDENCE, [_boundary_evidence(b) for b in non_default] + [_finding_evidence(f) for f in findings])
|
|
99
|
+
ctx.put(SLOT_OBSERVATIONS, _stats_observations(model.tx_index))
|
|
100
|
+
ctx.put(SLOT_CONFIDENCE, _agg_conf([b.confidence for b in non_default] + [f.confidence for f in findings]))
|
|
101
|
+
_add_service(ctx, "SpringSemanticModel")
|
|
102
|
+
_add_service(ctx, "run_tx_audit")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ── security (reuse run_security_audit) ──────────────────────────────────────────
|
|
106
|
+
def resolve_security_surface(step: Step, ctx: "StepContext") -> None:
|
|
107
|
+
assert isinstance(step, ResolveSecuritySurface)
|
|
108
|
+
findings = _security_findings(ctx)
|
|
109
|
+
ctx.put(SLOT_ENTITIES, [_finding_entity(f) for f in findings])
|
|
110
|
+
ctx.put(SLOT_EVIDENCE, [_finding_evidence(f) for f in findings])
|
|
111
|
+
ctx.put(SLOT_OBSERVATIONS, _severity_observations(findings))
|
|
112
|
+
ctx.put(SLOT_CONFIDENCE, _agg_conf([f.confidence for f in findings]))
|
|
113
|
+
_add_service(ctx, "run_security_audit")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def resolve_security_chain(step: Step, ctx: "StepContext") -> None:
|
|
117
|
+
"""Security findings scoped to a target (symbol / related / enclosing class). Reuses
|
|
118
|
+
`run_security_audit`; the target comes from the shared ResolveTarget step."""
|
|
119
|
+
assert isinstance(step, ResolveSecurityChain)
|
|
120
|
+
target = ctx.require(SLOT_TARGET)
|
|
121
|
+
scoped = [f for f in _security_findings(ctx) if _touches(f, target)]
|
|
122
|
+
ctx.put(SLOT_ENTITIES, [_finding_entity(f) for f in scoped])
|
|
123
|
+
ctx.put(SLOT_EVIDENCE, [_finding_evidence(f) for f in scoped])
|
|
124
|
+
ctx.put(SLOT_OBSERVATIONS, _severity_observations(scoped))
|
|
125
|
+
ctx.put(SLOT_CONFIDENCE, _agg_conf([f.confidence for f in scoped]))
|
|
126
|
+
_add_service(ctx, "run_security_audit")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ── validation (reuse build_validation_surface) ──────────────────────────────────
|
|
130
|
+
def resolve_validation_points(step: Step, ctx: "StepContext") -> None:
|
|
131
|
+
assert isinstance(step, ResolveValidationPoints)
|
|
132
|
+
from sourcecode.validation_surface import build_validation_surface
|
|
133
|
+
|
|
134
|
+
surface = build_validation_surface(ctx.knowledge.root, graph=ctx.knowledge.context_graph)
|
|
135
|
+
endpoints = surface.get("endpoints", []) or []
|
|
136
|
+
gaps = surface.get("gaps", []) or []
|
|
137
|
+
entities = [
|
|
138
|
+
KnowledgeEntity(
|
|
139
|
+
identity=_route_id(ep),
|
|
140
|
+
kind="validation_point",
|
|
141
|
+
score=0.0,
|
|
142
|
+
attributes={k: v for k, v in ep.items() if k not in ("source_file", "line")},
|
|
143
|
+
)
|
|
144
|
+
for ep in endpoints
|
|
145
|
+
] + [
|
|
146
|
+
KnowledgeEntity(identity=_route_id(g), kind="validation_gap", score=0.0, attributes=dict(g))
|
|
147
|
+
for g in gaps
|
|
148
|
+
]
|
|
149
|
+
ctx.put(SLOT_ENTITIES, entities)
|
|
150
|
+
ctx.put(
|
|
151
|
+
SLOT_EVIDENCE,
|
|
152
|
+
[
|
|
153
|
+
RetrievalEvidence(
|
|
154
|
+
signal="validation",
|
|
155
|
+
detail=_route_id(ep),
|
|
156
|
+
weight=1.0,
|
|
157
|
+
strength=1.0,
|
|
158
|
+
mass=1.0,
|
|
159
|
+
contribution=1.0,
|
|
160
|
+
provider="build_validation_surface",
|
|
161
|
+
source_file=ep.get("source_file"),
|
|
162
|
+
source_line=ep.get("line"),
|
|
163
|
+
)
|
|
164
|
+
for ep in endpoints
|
|
165
|
+
],
|
|
166
|
+
)
|
|
167
|
+
summary = surface.get("summary", {}) or {}
|
|
168
|
+
ctx.put(
|
|
169
|
+
SLOT_OBSERVATIONS,
|
|
170
|
+
[Observation(name=str(k), value=v, detail="validation surface summary") for k, v in summary.items()],
|
|
171
|
+
)
|
|
172
|
+
ctx.put(SLOT_CONFIDENCE, "high" if endpoints or gaps else "medium")
|
|
173
|
+
_add_service(ctx, "build_validation_surface")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ── ranking + terminal evidence (generic across the pack) ────────────────────────
|
|
177
|
+
def rank_by_criticality(step: Step, ctx: "StepContext") -> None:
|
|
178
|
+
"""Order entities by the engines' OWN severity then confidence then identity. No new
|
|
179
|
+
score is computed — SEVERITY_ORDER is the audit engines' own severity ranking."""
|
|
180
|
+
assert isinstance(step, RankByCriticality)
|
|
181
|
+
entities = ctx.get(SLOT_ENTITIES, [])
|
|
182
|
+
|
|
183
|
+
def key(e: KnowledgeEntity):
|
|
184
|
+
sev = SEVERITY_ORDER.get(e.attributes.get("severity"), 9)
|
|
185
|
+
conf = _CONF_RANK.get(e.attributes.get("confidence"), 9)
|
|
186
|
+
return (sev, conf, e.identity)
|
|
187
|
+
|
|
188
|
+
ctx.put(SLOT_ENTITIES, sorted(entities, key=key))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def collect_security_evidence(step: Step, ctx: "StepContext") -> None:
|
|
192
|
+
"""Assemble confidence, structured explanation (no synthesized prose), and provenance
|
|
193
|
+
from what the resolve steps produced. GENERIC across every tx/security intent."""
|
|
194
|
+
confidence = ctx.get(SLOT_CONFIDENCE, "medium")
|
|
195
|
+
ctx.put(
|
|
196
|
+
SLOT_EXPLANATION,
|
|
197
|
+
Explanation(
|
|
198
|
+
winner=None,
|
|
199
|
+
score=0.0,
|
|
200
|
+
confidence=confidence,
|
|
201
|
+
top_contributions=(),
|
|
202
|
+
runner_up=None,
|
|
203
|
+
engine_explanation="",
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
ctx.put(
|
|
207
|
+
SLOT_PROVENANCE,
|
|
208
|
+
Provenance(
|
|
209
|
+
services=tuple(ctx.get(SLOT_SERVICES, [])),
|
|
210
|
+
signals_used=("tx_index", "endpoint_index", "security_audit", "validation_surface"),
|
|
211
|
+
signals_missing=(),
|
|
212
|
+
knowledge_source="SpringSemanticModel + audit/validation engines",
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ── helpers ──────────────────────────────────────────────────────────────────────
|
|
218
|
+
def _security_findings(ctx: "StepContext") -> list:
|
|
219
|
+
from sourcecode.spring_security_audit import run_security_audit
|
|
220
|
+
|
|
221
|
+
return run_security_audit(
|
|
222
|
+
ctx.knowledge.context_graph.cir, root=ctx.knowledge.root, model=ctx.knowledge.spring_model
|
|
223
|
+
).findings
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _boundary_entity(b, kind: str) -> KnowledgeEntity:
|
|
227
|
+
return KnowledgeEntity(
|
|
228
|
+
identity=b.symbol,
|
|
229
|
+
kind=kind,
|
|
230
|
+
score=0.0,
|
|
231
|
+
attributes={
|
|
232
|
+
"scope": b.scope,
|
|
233
|
+
"propagation": b.propagation,
|
|
234
|
+
"isolation": b.isolation,
|
|
235
|
+
"read_only": b.read_only,
|
|
236
|
+
"proxy_bypass_risk": b.is_proxy_bypass_risk,
|
|
237
|
+
"confidence": b.confidence,
|
|
238
|
+
},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _boundary_evidence(b) -> RetrievalEvidence:
|
|
243
|
+
return RetrievalEvidence(
|
|
244
|
+
signal="transactional",
|
|
245
|
+
detail=f"@Transactional({b.propagation}, readOnly={b.read_only})",
|
|
246
|
+
weight=1.0,
|
|
247
|
+
strength=1.0,
|
|
248
|
+
mass=1.0,
|
|
249
|
+
contribution=1.0,
|
|
250
|
+
provider="SpringSemanticModel",
|
|
251
|
+
source_file=b.source_file or None,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _finding_entity(f) -> KnowledgeEntity:
|
|
256
|
+
return KnowledgeEntity(
|
|
257
|
+
identity=f.symbol,
|
|
258
|
+
kind="security_finding",
|
|
259
|
+
score=0.0,
|
|
260
|
+
attributes={
|
|
261
|
+
"pattern_id": f.pattern_id,
|
|
262
|
+
"severity": f.severity,
|
|
263
|
+
"confidence": f.confidence,
|
|
264
|
+
"title": f.title,
|
|
265
|
+
},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _finding_evidence(f) -> RetrievalEvidence:
|
|
270
|
+
file = line = None
|
|
271
|
+
sf = getattr(f, "source_file", "") or ""
|
|
272
|
+
if sf:
|
|
273
|
+
file = sf
|
|
274
|
+
return RetrievalEvidence(
|
|
275
|
+
signal=f.pattern_id,
|
|
276
|
+
detail=f.title,
|
|
277
|
+
weight=1.0,
|
|
278
|
+
strength=1.0,
|
|
279
|
+
mass=1.0,
|
|
280
|
+
contribution=1.0,
|
|
281
|
+
provider=f"audit:{f.category}",
|
|
282
|
+
source_file=file,
|
|
283
|
+
source_line=line,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _stats_observations(tx) -> list:
|
|
288
|
+
stats = tx.stats() if hasattr(tx, "stats") else {}
|
|
289
|
+
out = []
|
|
290
|
+
for k, v in stats.items():
|
|
291
|
+
if isinstance(v, dict):
|
|
292
|
+
out.append(Observation(name=k, value=sum(v.values()), detail=f"{k}: {v}"))
|
|
293
|
+
else:
|
|
294
|
+
out.append(Observation(name=str(k), value=v, detail="tx_index stats"))
|
|
295
|
+
return out
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _severity_observations(findings) -> list:
|
|
299
|
+
hist: dict[str, int] = {}
|
|
300
|
+
for f in findings:
|
|
301
|
+
hist[f.severity] = hist.get(f.severity, 0) + 1
|
|
302
|
+
return [Observation(name=f"severity_{s}", value=n, detail="security finding count") for s, n in sorted(hist.items())]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _route_id(d: dict) -> str:
|
|
306
|
+
return (
|
|
307
|
+
d.get("route")
|
|
308
|
+
or d.get("path")
|
|
309
|
+
or f"{d.get('method', '')} {d.get('handler', '')}".strip()
|
|
310
|
+
or d.get("handler", "")
|
|
311
|
+
or "?"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _touches(f, target: str) -> bool:
|
|
316
|
+
return (
|
|
317
|
+
f.symbol == target
|
|
318
|
+
or target in (getattr(f, "related_symbols", []) or [])
|
|
319
|
+
or _enclosing_class(f.symbol) == target
|
|
320
|
+
or f.symbol.startswith(target)
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _enclosing_class(symbol: str) -> str:
|
|
325
|
+
return symbol.split("#", 1)[0]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _agg_conf(confs) -> str:
|
|
329
|
+
ranked = [c for c in confs if c in _CONF_RANK]
|
|
330
|
+
if not ranked:
|
|
331
|
+
return "high"
|
|
332
|
+
return max(ranked, key=lambda c: _CONF_RANK[c]) # most conservative (lowest)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _add_service(ctx: "StepContext", name: str) -> None:
|
|
336
|
+
services = list(ctx.get(SLOT_SERVICES, []))
|
|
337
|
+
if name not in services:
|
|
338
|
+
services.append(name)
|
|
339
|
+
ctx.put(SLOT_SERVICES, services)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def register_txsec_steps(registry: "StepRegistry") -> None:
|
|
343
|
+
"""Register the TRANSACTIONS & SECURITY handlers (the sanctioned extension seam)."""
|
|
344
|
+
registry.register(ResolveTransactionFlow.kind, resolve_transaction_flow)
|
|
345
|
+
registry.register(ResolveTransactionEntrypoints.kind, resolve_transaction_entrypoints)
|
|
346
|
+
registry.register(ResolvePropagation.kind, resolve_propagation)
|
|
347
|
+
registry.register(ResolveSecuritySurface.kind, resolve_security_surface)
|
|
348
|
+
registry.register(ResolveSecurityChain.kind, resolve_security_chain)
|
|
349
|
+
registry.register(ResolveValidationPoints.kind, resolve_validation_points)
|
|
350
|
+
registry.register(RankByCriticality.kind, rank_by_criticality)
|
|
351
|
+
registry.register(CollectSecurityEvidence.kind, collect_security_evidence)
|
sourcecode/security_config.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Custom security annotation configuration (BUG-3).
|
|
2
2
|
|
|
3
3
|
Enterprise Spring projects routinely guard endpoints with bespoke authorization
|
|
4
|
-
annotations (e.g. ``@
|
|
4
|
+
annotations (e.g. ``@CustomSecurityAnnotation(nombreRecurso=..., nivelRequerido=...)``)
|
|
5
5
|
instead of the standard ``@PreAuthorize`` / ``@Secured`` set. Without knowing
|
|
6
6
|
those names, the endpoint surface reports ``policy: none_detected`` for every
|
|
7
7
|
protected route, which makes ``endpoints`` / ``spring-audit`` blind in exactly
|
|
@@ -18,8 +18,8 @@ Config shape::
|
|
|
18
18
|
{
|
|
19
19
|
"customSecurityAnnotations": [
|
|
20
20
|
{
|
|
21
|
-
"fullyQualifiedName": "com.example.security.
|
|
22
|
-
"shortName": "
|
|
21
|
+
"fullyQualifiedName": "com.example.security.CustomSecurityAnnotation",
|
|
22
|
+
"shortName": "CustomSecurityAnnotation",
|
|
23
23
|
"resourceParam": "nombreRecurso",
|
|
24
24
|
"levelParam": "nivelRequerido",
|
|
25
25
|
"riskLevel": "custom"
|
|
@@ -41,7 +41,7 @@ CONFIG_FILENAME = "sourcecode.config.json"
|
|
|
41
41
|
class CustomSecuritySpec:
|
|
42
42
|
"""One custom security annotation the analyzer should recognize."""
|
|
43
43
|
|
|
44
|
-
short_name: str # e.g. "
|
|
44
|
+
short_name: str # e.g. "CustomSecurityAnnotation" (no leading @)
|
|
45
45
|
fqn: str = "" # fully-qualified name (optional)
|
|
46
46
|
resource_param: str = "" # annotation attribute naming the protected resource
|
|
47
47
|
level_param: str = "" # annotation attribute naming the required level
|
sourcecode/spring_impact.py
CHANGED
|
@@ -443,6 +443,7 @@ def _bfs_callers(
|
|
|
443
443
|
reverse_graph: dict[str, dict[str, list[str]]],
|
|
444
444
|
max_depth: int,
|
|
445
445
|
impl_graph: object | None = None,
|
|
446
|
+
method_scoped: bool = False,
|
|
446
447
|
) -> tuple[list[str], list[str], bool]:
|
|
447
448
|
"""BFS from seed FQNs through reverse_graph.
|
|
448
449
|
|
|
@@ -481,8 +482,22 @@ def _bfs_callers(
|
|
|
481
482
|
# into direct_callers and inflating the blast radius ~12×. Exclude any caller
|
|
482
483
|
# whose owning class is one of the seed classes.
|
|
483
484
|
seed_classes: set[str] = {normalize_owner_fqn(s) for s in seed_fqns}
|
|
485
|
+
seed_set: set[str] = set(seed_fqns)
|
|
484
486
|
self_excluded: int = 0
|
|
485
487
|
|
|
488
|
+
# P0-IMPACT-01 fix: distinguish self-recursion from same-class sibling calls.
|
|
489
|
+
# For a METHOD-scoped query ("Foo#doWork"), a *different* method on the same class
|
|
490
|
+
# (e.g. Foo#callWork → Foo#doWork) is a legitimate caller — the only true
|
|
491
|
+
# "self-reference" is the seed method invoking itself (recursion). The v1.70.0
|
|
492
|
+
# whole-class exclusion is correct ONLY for CLASS-scoped queries, where a class's
|
|
493
|
+
# own members are members, not external callers. Applying it to method queries
|
|
494
|
+
# silently discarded the intra-class caller edges built by _intra_class_call_edges,
|
|
495
|
+
# producing a false "0 callers / isolated" for private helpers.
|
|
496
|
+
def _is_self_reference(caller: str) -> bool:
|
|
497
|
+
if method_scoped:
|
|
498
|
+
return caller in seed_set # only genuine recursion of a seed method
|
|
499
|
+
return normalize_owner_fqn(caller) in seed_classes
|
|
500
|
+
|
|
486
501
|
# BUG-004: index class FQN → list of method-level keys in reverse_graph.
|
|
487
502
|
# Callers of Foo#doWork are stored under reverse_graph["Foo#doWork"], never
|
|
488
503
|
# under reverse_graph["Foo"]. Without this index, BFS silently terminates
|
|
@@ -519,7 +534,7 @@ def _bfs_callers(
|
|
|
519
534
|
for etype, fqn_list in _edges_for(seed):
|
|
520
535
|
if etype not in _SKIP_EDGE_TYPES:
|
|
521
536
|
unique_direct_callers.update(
|
|
522
|
-
c for c in fqn_list if
|
|
537
|
+
c for c in fqn_list if not _is_self_reference(c)
|
|
523
538
|
)
|
|
524
539
|
|
|
525
540
|
effective_depth = 1 if len(unique_direct_callers) > _BFS_CALLER_CAP else max_depth
|
|
@@ -534,7 +549,8 @@ def _bfs_callers(
|
|
|
534
549
|
if caller in visited:
|
|
535
550
|
return
|
|
536
551
|
# BUG #2: a member of a seed class is not an external caller — drop it.
|
|
537
|
-
|
|
552
|
+
# (Method-scoped queries drop only true self-recursion; see _is_self_reference.)
|
|
553
|
+
if _is_self_reference(caller):
|
|
538
554
|
visited.add(caller)
|
|
539
555
|
self_excluded += 1
|
|
540
556
|
return
|
|
@@ -855,6 +871,22 @@ class ImpactOrchestrator:
|
|
|
855
871
|
original_seed_classes: set[str] = {_class_of(s) for s in seed_fqns}
|
|
856
872
|
# Subtype classes surfaced to the caller as the impacted implementation set.
|
|
857
873
|
subtype_classes_added: set[str] = set()
|
|
874
|
+
|
|
875
|
+
# Method-scoped query (e.g. "Impl#foo"): impl/interface expansion must preserve
|
|
876
|
+
# the queried method's identity. A change to `foo` does not affect callers of a
|
|
877
|
+
# sibling method `bar`, so the expanded SEED symbols are restricted to the same
|
|
878
|
+
# method name(s). Class-level queries (a class node with no '#' in the seed)
|
|
879
|
+
# expand to all members, as before. This bound became observable once precise
|
|
880
|
+
# `field.method(...)` call edges exist: without it a method query inherited the
|
|
881
|
+
# callers of every sibling method on the interface (blast-radius inflation).
|
|
882
|
+
_queried_methods: set[str] = {s.rsplit("#", 1)[1] for s in seed_fqns if "#" in s}
|
|
883
|
+
_method_scoped = bool(_queried_methods) and all("#" in s for s in seed_fqns)
|
|
884
|
+
|
|
885
|
+
def _member_in_scope(sym: str) -> bool:
|
|
886
|
+
if not _method_scoped:
|
|
887
|
+
return True
|
|
888
|
+
return "#" in sym and sym.rsplit("#", 1)[1] in _queried_methods
|
|
889
|
+
|
|
858
890
|
if impl_graph is not None:
|
|
859
891
|
seed_classes_ch001 = {_class_of(s) for s in seed_fqns}
|
|
860
892
|
impl_seeds: list[str] = []
|
|
@@ -867,7 +899,8 @@ class ImpactOrchestrator:
|
|
|
867
899
|
continue
|
|
868
900
|
subtype_classes_added.add(impl_class)
|
|
869
901
|
for sym in cir.symbols:
|
|
870
|
-
if _class_of(sym) == impl_class and sym not in set(seed_fqns)
|
|
902
|
+
if (_class_of(sym) == impl_class and sym not in set(seed_fqns)
|
|
903
|
+
and _member_in_scope(sym)):
|
|
871
904
|
impl_seeds.append(sym)
|
|
872
905
|
if impl_seeds:
|
|
873
906
|
seed_fqns = list(dict.fromkeys(seed_fqns + impl_seeds))
|
|
@@ -896,7 +929,8 @@ class ImpactOrchestrator:
|
|
|
896
929
|
continue
|
|
897
930
|
iface_classes_added.add(iface_class)
|
|
898
931
|
for sym in cir.symbols:
|
|
899
|
-
if _class_of(sym) == iface_class and sym not in set(seed_fqns)
|
|
932
|
+
if (_class_of(sym) == iface_class and sym not in set(seed_fqns)
|
|
933
|
+
and _member_in_scope(sym)):
|
|
900
934
|
iface_seeds.append(sym)
|
|
901
935
|
if iface_seeds:
|
|
902
936
|
seed_fqns = list(dict.fromkeys(seed_fqns + iface_seeds))
|
|
@@ -909,14 +943,22 @@ class ImpactOrchestrator:
|
|
|
909
943
|
|
|
910
944
|
# ── 2. BFS through reverse graph ─────────────────────────────────
|
|
911
945
|
direct_callers, indirect_callers, truncated, self_excluded = _bfs_callers(
|
|
912
|
-
seed_fqns, cir.reverse_graph, depth, impl_graph=impl_graph
|
|
946
|
+
seed_fqns, cir.reverse_graph, depth, impl_graph=impl_graph,
|
|
947
|
+
method_scoped=_method_scoped,
|
|
913
948
|
)
|
|
914
949
|
if self_excluded:
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
950
|
+
if _method_scoped:
|
|
951
|
+
warnings.append(
|
|
952
|
+
f"Self-recursion excluded: {self_excluded} recursive self-call(s) of the "
|
|
953
|
+
f"queried method were dropped from callers (a method is not its own "
|
|
954
|
+
f"caller). Same-class sibling methods ARE counted as callers."
|
|
955
|
+
)
|
|
956
|
+
else:
|
|
957
|
+
warnings.append(
|
|
958
|
+
f"Self-referential exclusion (BUG #2): {self_excluded} member(s) of the "
|
|
959
|
+
f"analyzed class were dropped from callers — a class's own methods are "
|
|
960
|
+
f"members, not external callers (they do not count toward blast radius)."
|
|
961
|
+
)
|
|
920
962
|
if truncated:
|
|
921
963
|
warnings.append(
|
|
922
964
|
"Hub-class guard active: symbol has > 500 direct callers — "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.1
|
|
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`**
|
|
@@ -84,7 +84,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
84
84
|
# pip / pipx
|
|
85
85
|
pipx install sourcecode # or: pip install sourcecode
|
|
86
86
|
|
|
87
|
-
ask version # ask 2.
|
|
87
|
+
ask version # ask 2.5.1
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
> **Package vs. command.** The install package is named `sourcecode` this release
|
|
@@ -218,7 +218,7 @@ repo root (otherwise they report `policy: "none_detected"`):
|
|
|
218
218
|
```json
|
|
219
219
|
{
|
|
220
220
|
"customSecurityAnnotations": [
|
|
221
|
-
{ "fullyQualifiedName": "com.example.security.
|
|
221
|
+
{ "fullyQualifiedName": "com.example.security.CustomSecurityAnnotation", "shortName": "CustomSecurityAnnotation" }
|
|
222
222
|
]
|
|
223
223
|
}
|
|
224
224
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=KLuqjzw229DS3vhMmuCKlXFinpcH1QG9fhcn7C3J2xg,308
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
|
-
sourcecode/archetype.py,sha256=
|
|
3
|
+
sourcecode/archetype.py,sha256=d7yoN6Tj4OBj-VgSMPEKg4WF9H8FypnZ5A6AkUh5oIE,34484
|
|
4
4
|
sourcecode/architecture_analyzer.py,sha256=6_wC4TYuBYxaqckZS0I1vBSMRPeH2vzlDB48gXwOOd4,46627
|
|
5
5
|
sourcecode/architecture_summary.py,sha256=pkl6lIOnSy-poa9EzW43U0MkXhx2FDfAFh8pDXLpcTo,26630
|
|
6
6
|
sourcecode/ast_extractor.py,sha256=FEsYTsMCXbJfSYQZRk1k-I-PjUNGnKqO1IxMftsgA5U,51351
|
|
@@ -10,11 +10,11 @@ sourcecode/caller_metrics.py,sha256=HG8RCOkpzzsEGdnMRob6byrwjoj5__t0TEsBj4yR7ks,
|
|
|
10
10
|
sourcecode/canonical_ir.py,sha256=U69m8Vkr0p407xF1q5y1KguHXWEAubYw8NFHR9hDNJk,29178
|
|
11
11
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
12
12
|
sourcecode/classifier.py,sha256=RhLMDR031FAiUcT3bsf2EL3KrFwl7KsWh_wXXR_Bumo,18729
|
|
13
|
-
sourcecode/cli.py,sha256=
|
|
13
|
+
sourcecode/cli.py,sha256=46Chp0JsEWD_j0HUWOx4tOhrsXSWgWV8JhL5G8SyW4I,322078
|
|
14
14
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
15
15
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
16
16
|
sourcecode/context_cache.py,sha256=maws79rLKDwyZ7c9Q2LwlIxqZHRFlaKzErtO3mglWoA,25036
|
|
17
|
-
sourcecode/context_graph.py,sha256=
|
|
17
|
+
sourcecode/context_graph.py,sha256=WFtUvfxoE8XtQxtRJLvaF4SJuS00ICjNoTk0sH2_V5U,44454
|
|
18
18
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
19
19
|
sourcecode/context_summarizer.py,sha256=cI2TZMvEhl0BEma12VtPaX6z03ZVBAetVzuK5GaCOvg,6852
|
|
20
20
|
sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
|
|
@@ -55,12 +55,12 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
|
55
55
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
56
56
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
57
57
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
58
|
-
sourcecode/repository_ir.py,sha256=
|
|
58
|
+
sourcecode/repository_ir.py,sha256=Cyt7avInKNvayaOSgeSjJgWARcGnVXA-4EAmKSve_Jc,290150
|
|
59
59
|
sourcecode/ris.py,sha256=aG2D4159gtpg968yD3PylYbIXhGFOwIlFBI0DSr84yY,20412
|
|
60
60
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
61
61
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
62
62
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
63
|
-
sourcecode/security_config.py,sha256=
|
|
63
|
+
sourcecode/security_config.py,sha256=KblMEoRiEjrIE68YsPaUAFebxFp8UM7MS7lAk5CGD8U,3531
|
|
64
64
|
sourcecode/security_posture.py,sha256=CAJw4oJD0aAW2yRewvd_fonkzi9_HpUjBZlIDZle00s,26034
|
|
65
65
|
sourcecode/semantic_analyzer.py,sha256=bpgdC6m0_ftVtRf3rSdwhbhWjnZnGxRXaZVcfe4BbcQ,95414
|
|
66
66
|
sourcecode/semantic_impact_engine.py,sha256=HpgjenY6DLPHPomP7Hw850zA-EBokf1S4O2sh8NegdI,20351
|
|
@@ -69,7 +69,7 @@ sourcecode/semantic_services.py,sha256=AO_p7PRfjc-AUh6ThvLMfm4iUGi0issL_frCf8sxG
|
|
|
69
69
|
sourcecode/serializer.py,sha256=FmQo7eojA_z3ptLW-Vgze5mrEJ6FabcZqiwbQWWTgFI,130056
|
|
70
70
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
71
71
|
sourcecode/spring_findings.py,sha256=EX7kLZLN74CFyR9iZPm3CI115BfFKNd4WRPuErNljZM,5729
|
|
72
|
-
sourcecode/spring_impact.py,sha256=
|
|
72
|
+
sourcecode/spring_impact.py,sha256=tjTxKAtmAvBqDVSSsC4ttCzbLaO55Wg4FJdOd_8MtO0,60077
|
|
73
73
|
sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
|
|
74
74
|
sourcecode/spring_security_audit.py,sha256=Rk-aSohezdc7YDYbSoJquVnwpkDB8ty1BCD-4Hc4R5A,22832
|
|
75
75
|
sourcecode/spring_semantic.py,sha256=jteQ1PkY9ArFJv0embg_jBIdbOxqrk9mQ2Xz8OF_FKA,14214
|
|
@@ -112,14 +112,32 @@ sourcecode/mcp/onboarding/applier.py,sha256=Z2ZqzPh7dTTzT7tfxDbfjDJKiBNQ18CPQCaR
|
|
|
112
112
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
113
113
|
sourcecode/mcp/onboarding/detector.py,sha256=kDc0U6kXMuq_GivqwKrgJzIVLVeoLr3RQl63ksW10I8,3327
|
|
114
114
|
sourcecode/mcp/onboarding/planner.py,sha256=Fopg5f72FDiPfldF7NOxYjcBA_w8hi_jBJpSz39lPb8,1332
|
|
115
|
+
sourcecode/retrieval/__init__.py,sha256=h5pKDzXOlu_NQy-YxOaqMSD6UDNoYsmsYSoNLRRZ5uM,5478
|
|
116
|
+
sourcecode/retrieval/context.py,sha256=hjm_RxReZar6GZvVVg4XNR9sVMOUuilbi1Jt7iUoerU,11116
|
|
117
|
+
sourcecode/retrieval/errors.py,sha256=U-zqxzlzCWe3NYCNOBASTSPn1xS8j9D7LfwpGBiznL4,1452
|
|
118
|
+
sourcecode/retrieval/executor.py,sha256=H2-5by5UCYgX7lgJtmT8JJ73ldxp6xiBFEf6r__j_XA,2048
|
|
119
|
+
sourcecode/retrieval/planner.py,sha256=9DPoutVHEYaGodDTmihZjqeNS6vFwJKtANnmO-4cvlU,13375
|
|
120
|
+
sourcecode/retrieval/query.py,sha256=QoVKsuV-PYuunbw06MvV_HdHLAKbo_r-S9xEWFoAVB4,18406
|
|
121
|
+
sourcecode/retrieval/request.py,sha256=gKK5iL5X7ScMz_HQ53FX4G6lbFdkhh0vNkIuxGDLt74,4023
|
|
122
|
+
sourcecode/retrieval/resolution.py,sha256=boa9ybj3AQK5CEMYJn9jA8e0XRNyKKD9avRM5JAIwyA,7163
|
|
123
|
+
sourcecode/retrieval/result.py,sha256=mx6ioA6y6bkNbF6SC3n0N99NTuNEy1uz_5YZQ2euflA,4726
|
|
124
|
+
sourcecode/retrieval/retriever.py,sha256=YdnbaXaWhFKyShuuE6fj36GfgpuGetqOFzx7_D2A9CY,2654
|
|
125
|
+
sourcecode/retrieval/runtime.py,sha256=ODQlPqyS4OfMtCb1oEA_0t7vK0uKPBLhB4K2CnytPUU,10822
|
|
126
|
+
sourcecode/retrieval/steps.py,sha256=wyOiO84fuDgdd-HdugzGSEriN-lN891PTc3gU7lgcmI,9959
|
|
127
|
+
sourcecode/retrieval/steps_endpoint.py,sha256=yOrTA9mWoIrEBVc_-do8m_AAGqjyFqR7jJ9w5LQXycM,12368
|
|
128
|
+
sourcecode/retrieval/steps_graph.py,sha256=ZWA4i1hwCKiP0AOZ-4PJsLHH84abOYVVh6ERNkdE9v0,10050
|
|
129
|
+
sourcecode/retrieval/steps_impact.py,sha256=M0AwU5Ue7nkj-A7DYgQI2BP0BV1b0TFZRrYIydF-Ajw,11605
|
|
130
|
+
sourcecode/retrieval/steps_intf.py,sha256=Jx5Gnm7UT1jOE7YloD0ee3d8PuliX5FgpdvlHh36OBs,9341
|
|
131
|
+
sourcecode/retrieval/steps_struct.py,sha256=jO9L49hziTlS_xhuhCGUavgyAL3ZAnJSOZB-UA4GaRM,13893
|
|
132
|
+
sourcecode/retrieval/steps_txsec.py,sha256=ltyxFAFLHfUglGLGz7i3tY_bovBoKo1d6R2t1xZTcnk,13608
|
|
115
133
|
sourcecode/telemetry/__init__.py,sha256=6wsJnXsgsL_BtMK4hr5Ae1GToHeWej53F4o_-Tc23MQ,3406
|
|
116
134
|
sourcecode/telemetry/config.py,sha256=u_zctgmA1pVT3Ai5jm-pdLY_5kJTRg4Xq2bvIbGBQE4,3727
|
|
117
135
|
sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i5Q,2194
|
|
118
136
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
119
137
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
120
138
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
121
|
-
sourcecode-2.
|
|
122
|
-
sourcecode-2.
|
|
123
|
-
sourcecode-2.
|
|
124
|
-
sourcecode-2.
|
|
125
|
-
sourcecode-2.
|
|
139
|
+
sourcecode-2.5.1.dist-info/METADATA,sha256=eQZ2Bo5Okcz-pi14-mRu9KvrxZzIwkq7WExZEz0pjPE,10851
|
|
140
|
+
sourcecode-2.5.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
141
|
+
sourcecode-2.5.1.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
142
|
+
sourcecode-2.5.1.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
143
|
+
sourcecode-2.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|