sourcecode 5.8.29__py3-none-any.whl → 5.8.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sourcecode might be problematic. Click here for more details.
- sourcecode/__init__.py +1 -1
- sourcecode/_build_commit.py +1 -1
- sourcecode/_docs/DEFECT-LEDGER.md +12 -0
- sourcecode/_docs/USER_GUIDE.md +6 -5
- sourcecode/cache.py +7 -1
- sourcecode/cli.py +79 -18
- sourcecode/envelope.py +1 -1
- sourcecode/github_diff.py +51 -0
- sourcecode/license.py +11 -2
- sourcecode/mcp_nudge.py +11 -1
- sourcecode/migrate_check.py +4 -0
- sourcecode/packs.py +85 -21
- sourcecode/pr_impact.py +28 -3
- sourcecode/readonly.py +3 -1
- {sourcecode-5.8.29.dist-info → sourcecode-5.8.31.dist-info}/METADATA +6 -6
- {sourcecode-5.8.29.dist-info → sourcecode-5.8.31.dist-info}/RECORD +20 -20
- {sourcecode-5.8.29.dist-info → sourcecode-5.8.31.dist-info}/WHEEL +0 -0
- {sourcecode-5.8.29.dist-info → sourcecode-5.8.31.dist-info}/entry_points.txt +0 -0
- {sourcecode-5.8.29.dist-info → sourcecode-5.8.31.dist-info}/licenses/LICENSE +0 -0
- {sourcecode-5.8.29.dist-info → sourcecode-5.8.31.dist-info}/licenses/NOTICE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/_build_commit.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Generated at build time by hatch_build.py. Do not edit (AUD-592-D04)."""
|
|
2
2
|
|
|
3
|
-
BUILD_COMMIT = '
|
|
3
|
+
BUILD_COMMIT = '32e9b630f9276e5e481a33f0232c3a7876cd5751'
|
|
4
4
|
CLOSURE_PROVENANCE = {'Window': {'commit': '', 'status': 'uncited'}, '`AUD-588-F05`': {'commit': '470e4a4', 'status': 'present'}, '`AUD-590-B01` / `B-01`, `F-01`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B02` / `B-02`, `F-02`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B03` / `B-03`, `F-03`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B04` / `B-04`, `F-04`': {'commit': '', 'status': 'uncited'}, '`AUD-590-R01` / `R-01`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A01` / `A-1`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A02` / `A-2`, `A-4`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A03` / `A-3`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A05` / `A-5`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A06` / `A-6`, `A-7`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A08` / `A-8`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A09` / `A-9`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A10` / `A-10`': {'commit': '', 'status': 'uncited'}, '`AUD-592-A02` / `A-1` second half, `§12.5`': {'commit': '63f3d61', 'status': 'present'}, '`AUD-592-B01` / `B-8`': {'commit': '8329614', 'status': 'present'}, '`AUD-592-D01` / `D-1`': {'commit': '0f8e03f', 'status': 'present'}, '`AUD-592-D02` / `D-2`': {'commit': '1d3db4d', 'status': 'present'}, '`AUD-592-D03` / `D-3`': {'commit': 'f28fe40', 'status': 'present'}, '`AUD-592-R01` / `A-1`': {'commit': '216025f', 'status': 'present'}, '`AUD-592-R02` / `N-4`, reopens `AUD-513-N04`': {'commit': 'ec3050d', 'status': 'present'}, '`AUD-593-N01` / `N-01`': {'commit': 'dd6a706', 'status': 'present'}, '`AUD-593-N02` / `N-02`': {'commit': 'f539787', 'status': 'present'}, '`AUD-593-N03` / `N-03`': {'commit': '1dd0b1c', 'status': 'present'}, '`AUD-593-N05` / `N-05`': {'commit': 'aa89388', 'status': 'present'}, '`AUD-593-N06` / `N-06`': {'commit': 'aa584a6', 'status': 'present'}, '`AUD-594-N04` / `D-5`, disclosure half of `B-3`': {'commit': 'a0060b7', 'status': 'present'}, '`AUD-594-X01` / `A-1`, residual of `AUD-593-N03`': {'commit': 'dd222b9', 'status': 'present'}, '`AUD-594-X02` / `A-1` second half': {'commit': '1193937', 'status': 'present'}, '`AUD-594-X03` / `X-03`, residual of `AUD-593-N01`': {'commit': 'e89e8f0', 'status': 'present'}, '`AUD-594-X04`': {'commit': '05b8cc9', 'status': 'present'}, '`AUD-595-A02` / `N-5`, advisory half of `AUD-588-B11`': {'commit': '3e15227', 'status': 'present'}, '`AUD-595-A03` / `B-6` exit-code half': {'commit': 'ab7285f', 'status': 'present'}, '`AUD-595-A05`': {'commit': 'e0b3d10', 'status': 'present'}, '`AUD-595-A07`, narrow half of `AUD-592-A02`': {'commit': 'cc15177', 'status': 'present'}, '`AUD-595-B01` / §B': {'commit': 'b77a1ad', 'status': 'present'}, '`AUD-595-Q01`': {'commit': '91a6566', 'status': 'present'}, '`AUD-596-A01` / `ASK-DET-001`': {'commit': 'fa508b4', 'status': 'present'}, '`AUD-596-A02` / `ASK-SELF-001`': {'commit': '4dfadc8', 'status': 'present'}, '`AUD-596-A03` / `ASK-C1-001`': {'commit': 'bffa3a6', 'status': 'present'}, '`AUD-596-A04` / `ASK-UX-003` + `ASK-UX-004`': {'commit': 'f0aac3c', 'status': 'present'}, '`AUD-596-B01` / `B-01`, harder witness for `BUG-6`': {'commit': '567d09d', 'status': 'present'}, '`AUD-596-B02` / `B-02`, class residual of `R2`': {'commit': '', 'status': 'uncited'}, '`AUD-596-B03` / `B-03`': {'commit': 'a8cb814', 'status': 'present'}, '`AUD-596-B06` / `B-06`': {'commit': '8107927', 'status': 'present'}, '`AUD-596-B07` / `B-07`, class residual of `AUD-592-R02`': {'commit': 'e060485', 'status': 'present'}, '`AUD-596-B09` / `B-09`': {'commit': '5b8146c', 'status': 'present'}, '`AUD-596-B10` / `B-10`': {'commit': '5cb994d', 'status': 'present'}, '`AUD-596-B16` / `B-16`': {'commit': '1507f69', 'status': 'present'}, '`AUD-596-D01` / `B-13` + `B-14`': {'commit': '0b1bdf0', 'status': 'present'}, '`AUD-596-D02` / `B-08`, residual of `AUD-594-N04`': {'commit': 'e7f73c3', 'status': 'present'}, '`AUD-596-D03` / `ASK-AGT-001`': {'commit': '8c46967', 'status': 'present'}, '`AUD-596-D04` / `B-18`': {'commit': '040f027', 'status': 'present'}, '`AUD-596-D05` / `B-19`': {'commit': 'fd2bf68', 'status': 'present'}, '`AUD-596-D06` / `B-20`': {'commit': '784cd83', 'status': 'present'}, '`AUD-596-D07` / `B-17`, half refuted': {'commit': 'c3ff822', 'status': 'present'}, '`AUD-596-D08` / `B-22`, class residual of `AUD-591-A06`': {'commit': 'b396c6a', 'status': 'present'}, '`AUD-596-D09` / `B-23`, surviving half of `B26`': {'commit': 'fd3d63f', 'status': 'present'}, '`AUD-596-D10` / `B-29`': {'commit': '', 'status': 'uncited'}, '`AUD-596-D11` / `ASK-UX-005`': {'commit': 'f4b4f77', 'status': 'present'}, '`AUD-596-D12` — six hints that send the reader back to the error': {'commit': '498df44', 'status': 'present'}, '`AUD-596-D13` / `B-28`': {'commit': '1014c72', 'status': 'present'}, '`AUD-596-D14` / `B-15`, narrow half of `B20`': {'commit': '10f2827', 'status': 'present'}, '`AUD-596-R01` / `B-05`': {'commit': '', 'status': 'uncited'}, '`AUD-596-X01` / `ASK-GATE-001`, gating half of `B24`': {'commit': 'e59c655', 'status': 'present'}, '`AUD-596-X02` / `ASK-UX-001` + `ASK-UX-002` + `B-21`': {'commit': 'f6ebf50', 'status': 'present'}, '`AUD-597-A01` / `ASK-UX-001` + `N-01`': {'commit': 'f8cf061', 'status': 'present'}, '`AUD-597-A02` / `ASK-UX-008` + `ASK-DOC-002`': {'commit': '9d47a4c', 'status': 'present'}, '`AUD-597-A03` / `ASK-DET-001`': {'commit': '4c7622a', 'status': 'present'}, '`AUD-597-A04` / `ASK-LEDGER-001`': {'commit': '46fb870', 'status': 'present'}, '`AUD-597-D01` / `B-04`': {'commit': '1094e6a', 'status': 'present'}, '`AUD-597-D02` / `ASK-DOC-001`': {'commit': 'ecb1bb7', 'status': 'present'}, '`AUD-597-D03` / `N-06`': {'commit': '9784333', 'status': 'present'}, '`AUD-597-D04` / `N-05`': {'commit': 'e51c087', 'status': 'present'}, '`AUD-597-D05` / `N-04`, fourth appearance of `C4-27`': {'commit': '9cccc15', 'status': 'present'}, '`AUD-597-D06` / `ASK-CLI-001`': {'commit': '178aa6d', 'status': 'present'}, '`AUD-597-D07` / `N-03`': {'commit': '9245e35', 'status': 'present'}, '`AUD-597-R01` / `ASK-PERF-001`': {'commit': '6910054', 'status': 'present'}, '`AUD-597-R02` / `ASK-ENC-001`': {'commit': 'ce6dac1', 'status': 'present'}, '`AUD-597-R03` / `ASK-UX-002`': {'commit': 'c57e50b', 'status': 'present'}, '`AUD-597-X01` / `ASK-BUILD-001` + `ASK-META-001` / `B-12`': {'commit': '354338b', 'status': 'present'}, '`AUD-598-B01` / `ASK-META-001`': {'commit': 'b2f7c65', 'status': 'present'}, '`AUD-598-B02` / `ASK-UX-002`': {'commit': '56a4bf7', 'status': 'present'}, '`AUD-598-B03` / `B-11`': {'commit': 'c63cb24', 'status': 'present'}, '`AUD-598-B04` / `N-02`': {'commit': '187937f', 'status': 'present'}, '`AUD-598-R01` / `ASK-PERF-002`': {'commit': '', 'status': 'uncited'}, '`AUD-598-X01` / `N-01` + `B-13` + `B-14` + `B-29` + `N-06`': {'commit': '6f2f5a4', 'status': 'present'}, '`AUD-600-B01` / `P-01`': {'commit': '366ffed', 'status': 'present'}, '`AUD-600-B02` / `P-02` + `ASK-PACK-002`': {'commit': '2a814c3', 'status': 'present'}, '`AUD-600-B03` / `ASK-PACK-001`': {'commit': '452f2a0', 'status': 'present'}, '`AUD-600-D01`': {'commit': '1224e1b', 'status': 'present'}, '`AUD-600-D02`': {'commit': 'eab8172', 'status': 'present'}, '`AUD-600-R01` / `R-01`': {'commit': '88add64', 'status': 'present'}, '`AUD-601-B01` / `N-02`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-B02` / `ASK-PACK-004`': {'commit': '', 'status': 'uncited'}, '`AUD-601-D01` / `N-01`, `ASK-UX-001`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-D02` / `B-13`, `B-14`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-M01` / `B-26`, `B-27` retracted': {'commit': '', 'status': 'uncited'}, '`AUD-601-R01` / `R-02`': {'commit': '', 'status': 'uncited'}, '`B-3`': {'commit': '773268d', 'status': 'present'}, '`B-6`': {'commit': '17c6e06', 'status': 'present'}, '`B-8`': {'commit': '8329614', 'status': 'present'}, '`BUG-3` / `443b345`': {'commit': '', 'status': 'uncited'}, '`BUG-4a` / `AUD-588-B11` residual': {'commit': '24b4185', 'status': 'present'}, '`BUG-4b` / `AUD-588-B11` residual': {'commit': '0376350', 'status': 'present'}, '`E-39`, class residual of `AUD-593-N06`': {'commit': '25fc11f', 'status': 'present'}, '`N-5`': {'commit': 'c68083f', 'status': 'present'}, '`N-7`': {'commit': '082e9de', 'status': 'present'}, '`R-1`': {'commit': '424fb1c', 'status': 'present'}, '`R-2`': {'commit': '5158b92', 'status': 'present'}, '`R-3`': {'commit': '88aec04', 'status': 'present'}, '`R-4`': {'commit': '04fd04f', 'status': 'present'}}
|
|
5
5
|
CLOSURE_COVERAGE = {'closed_rows': 113, 'present': 88, 'absent': 0, 'unresolved': 0, 'uncited': 25, 'unknown': 0}
|
|
@@ -16,6 +16,18 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
|
|
|
16
16
|
|
|
17
17
|
## Current Synchronization
|
|
18
18
|
|
|
19
|
+
**External pack audit received, 2026-08-26 - `AUD-605`.**
|
|
20
|
+
[`AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md`](AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md)
|
|
21
|
+
adds two rows: `AUD-605-G01`, where an unresolved test file broadened pack-gate unknown
|
|
22
|
+
endpoints beyond its real production reach, and `AUD-605-U01`, where foreground `pack
|
|
23
|
+
assessment` did not forward component progress. Both are implemented in the current
|
|
24
|
+
checkout by `52bcc36` and `f89ae18`, with focused regression batteries passing; release
|
|
25
|
+
artefact verification remains pending. The pack's `--since` timing is additional evidence
|
|
26
|
+
for `AUD-604-P01`, not a duplicate performance finding.
|
|
27
|
+
|
|
28
|
+
**Current `5.8.31` queue:** artefact verification for `AUD-604-R01`; the two `AUD-605`
|
|
29
|
+
fixes are included in this release and await release-artefact verification.
|
|
30
|
+
|
|
19
31
|
**External `5.8.28` audit received, 2026-08-26 - `AUD-604`.**
|
|
20
32
|
[`AUDIT-2026-08-26-EXTERNAL-5.8.28.md`](AUDIT-2026-08-26-EXTERNAL-5.8.28.md) confirms the
|
|
21
33
|
semantic fixes from the previous round and adds three actionable rows: `AUD-604-O01`
|
sourcecode/_docs/USER_GUIDE.md
CHANGED
|
@@ -46,13 +46,14 @@ CLI commands — impact, endpoints, spring-audit, explain, … each a pro
|
|
|
46
46
|
The key idea: the extraction is **content-addressed**. Commands reuse the parse cache and,
|
|
47
47
|
where their analysed scope matches, the shared Canonical IR; `ask cache model` names what a
|
|
48
48
|
warm buys for each command rather than implying that every projection costs the same. In
|
|
49
|
-
5.8.
|
|
49
|
+
5.8.31, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
|
|
50
50
|
model across all declared label seeds. (The extraction and consumption contract is fixed in
|
|
51
51
|
the architecture ADRs 0001–0004 under `docs/architecture/`.)
|
|
52
52
|
|
|
53
|
-
The current external audit intake is tracked in
|
|
54
|
-
[`AUDIT-2026-08-26-EXTERNAL-5.8.
|
|
55
|
-
|
|
53
|
+
The current external pack audit intake is tracked in
|
|
54
|
+
[`AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md`](AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md);
|
|
55
|
+
the two pack fixes are included in release 5.8.31, with release-artifact verification
|
|
56
|
+
remaining for detached execution and pack evidence.
|
|
56
57
|
|
|
57
58
|
---
|
|
58
59
|
|
|
@@ -182,7 +183,7 @@ pipx install sourcecode # isolated install, no venv needed
|
|
|
182
183
|
|
|
183
184
|
# Verify
|
|
184
185
|
ask version
|
|
185
|
-
# ask 5.8.
|
|
186
|
+
# ask 5.8.31
|
|
186
187
|
```
|
|
187
188
|
|
|
188
189
|
Requires Python 3.9+.
|
sourcecode/cache.py
CHANGED
|
@@ -82,6 +82,12 @@ CORE_SCHEMA_VERSION: str = "1"
|
|
|
82
82
|
ANALYZER_CACHE_VERSION: str = "1"
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def _user_home() -> Path:
|
|
86
|
+
"""Honor an explicit HOME override before Windows' USERPROFILE fallback."""
|
|
87
|
+
configured = os.environ.get("HOME")
|
|
88
|
+
return Path(configured) if configured else Path.home()
|
|
89
|
+
|
|
90
|
+
|
|
85
91
|
def analyzer_fingerprint() -> str:
|
|
86
92
|
"""Content hash of the analysis code, memoised for the process.
|
|
87
93
|
|
|
@@ -325,7 +331,7 @@ def cache_dir(repo_root: Path) -> Path:
|
|
|
325
331
|
Override the base via ``SOURCECODE_CACHE_DIR``.
|
|
326
332
|
"""
|
|
327
333
|
env_base = os.environ.get("SOURCECODE_CACHE_DIR", "")
|
|
328
|
-
base: Path = Path(env_base) if env_base else
|
|
334
|
+
base: Path = Path(env_base) if env_base else _user_home() / ".sourcecode" / "cache"
|
|
329
335
|
return base / repo_id(repo_root)
|
|
330
336
|
|
|
331
337
|
|
sourcecode/cli.py
CHANGED
|
@@ -3415,7 +3415,6 @@ COMMANDS_WITHOUT_PROGRESS: "frozenset[str]" = frozenset({
|
|
|
3415
3415
|
"baseline trend", # same command under the group name
|
|
3416
3416
|
"regress", # reads two named JSON files; analyses no repository
|
|
3417
3417
|
"pack list", # fixed local catalog
|
|
3418
|
-
"pack assessment", # child progress is captured for aggregate JSON
|
|
3419
3418
|
"pack gate", # child progress is captured for aggregate JSON
|
|
3420
3419
|
})
|
|
3421
3420
|
|
|
@@ -4332,6 +4331,11 @@ def pack_assessment_cmd(
|
|
|
4332
4331
|
help="Deployment artefact for posture evidence (repeatable: k8s, Compose, Dockerfile, WebLogic, dotenv or launch script).",
|
|
4333
4332
|
),
|
|
4334
4333
|
compact: bool = typer.Option(False, "--compact", help="Keep component reports bounded where supported."),
|
|
4334
|
+
only: Optional[str] = typer.Option(
|
|
4335
|
+
None,
|
|
4336
|
+
"--only",
|
|
4337
|
+
help="Comma-separated assessment components to run; audit-report requires risk.",
|
|
4338
|
+
),
|
|
4335
4339
|
output_dir: Optional[Path] = typer.Option(
|
|
4336
4340
|
None,
|
|
4337
4341
|
"--output-dir",
|
|
@@ -4340,13 +4344,14 @@ def pack_assessment_cmd(
|
|
|
4340
4344
|
format: str = _format_option(help="Output format: json, yaml or markdown."),
|
|
4341
4345
|
copy: bool = _copy_option(),
|
|
4342
4346
|
output_path: Optional[Path] = _output_option(),
|
|
4347
|
+
progress_mode: Optional[str] = _progress_option(),
|
|
4343
4348
|
) -> None:
|
|
4344
4349
|
"""Modernization and Risk Assessment pack.
|
|
4345
4350
|
|
|
4346
4351
|
Composes existing commands into one consumable report. Individual commands
|
|
4347
4352
|
remain available and are the source of truth for every artifact.
|
|
4348
4353
|
"""
|
|
4349
|
-
from sourcecode.packs import render_assessment_markdown, run_assessment
|
|
4354
|
+
from sourcecode.packs import assessment_components, render_assessment_markdown, run_assessment
|
|
4350
4355
|
|
|
4351
4356
|
root = _admit_path(path)
|
|
4352
4357
|
if profile and deployment:
|
|
@@ -4357,28 +4362,51 @@ def pack_assessment_cmd(
|
|
|
4357
4362
|
)
|
|
4358
4363
|
raise typer.Exit(code=2)
|
|
4359
4364
|
_enforce_format("pack-assessment", format)
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4365
|
+
try:
|
|
4366
|
+
selected_components = assessment_components(only.split(",") if only is not None else None)
|
|
4367
|
+
except ValueError as exc:
|
|
4368
|
+
_emit_error_json(INVALID_INPUT_CODE, str(exc))
|
|
4369
|
+
raise typer.Exit(code=2)
|
|
4370
|
+
_progress = Progress().start("pack assessment")
|
|
4371
|
+
try:
|
|
4372
|
+
data = run_assessment(
|
|
4373
|
+
root, profile=profile, deployment=deployment, compact=compact, output_dir=output_dir,
|
|
4374
|
+
selected_components=selected_components,
|
|
4375
|
+
progress=lambda done, total, component: _progress.work(
|
|
4376
|
+
done, total, stage=f"pack assessment: {component}",
|
|
4377
|
+
unit="components", estimate=False,
|
|
4378
|
+
),
|
|
4379
|
+
)
|
|
4380
|
+
except BaseException:
|
|
4381
|
+
_progress.stop()
|
|
4382
|
+
raise
|
|
4363
4383
|
output = render_assessment_markdown(data) if format == "markdown" else _serialize_dict(data, format)
|
|
4364
4384
|
# AUD-600-D01: when the pack decided at t=0 that it may not write into the
|
|
4365
4385
|
# analysed tree, the aggregate must not be routed there either — the same
|
|
4366
4386
|
# decision, one destination later.
|
|
4367
4387
|
_dir = None if data.get("no_write_substitution") else output_dir
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4388
|
+
try:
|
|
4389
|
+
_emit_command_output(
|
|
4390
|
+
output,
|
|
4391
|
+
_pack_output_path(_dir, output_path, format),
|
|
4392
|
+
copy,
|
|
4393
|
+
stamp_envelope=(format != "markdown"),
|
|
4394
|
+
)
|
|
4395
|
+
if data["verdict"] == "UNVERIFIED":
|
|
4396
|
+
raise typer.Exit(code=2)
|
|
4397
|
+
finally:
|
|
4398
|
+
_progress.finish()
|
|
4376
4399
|
|
|
4377
4400
|
|
|
4378
4401
|
@pack_app.command("gate")
|
|
4379
4402
|
def pack_gate_cmd(
|
|
4380
4403
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)."),
|
|
4381
|
-
since: str = typer.Option(
|
|
4404
|
+
since: Optional[str] = typer.Option(None, "--since", help="Local Git ref used as the comparison base."),
|
|
4405
|
+
pr: Optional[int] = typer.Option(
|
|
4406
|
+
None,
|
|
4407
|
+
"--pr",
|
|
4408
|
+
help="GitHub pull request number; resolves changed files and requires its base commit locally.",
|
|
4409
|
+
),
|
|
4382
4410
|
profile: Optional[str] = typer.Option(None, "--profile", help="Profile set used for posture evidence."),
|
|
4383
4411
|
compact: bool = typer.Option(False, "--compact", help="Omit component payload bodies from the aggregate."),
|
|
4384
4412
|
allow_unverified: bool = typer.Option(False, "--allow-unverified", help="Allow an unverified component to exit 0."),
|
|
@@ -4404,7 +4432,37 @@ def pack_gate_cmd(
|
|
|
4404
4432
|
from sourcecode.packs import run_gate
|
|
4405
4433
|
|
|
4406
4434
|
root = _admit_path(path, exit_code=2)
|
|
4407
|
-
|
|
4435
|
+
if since is not None and pr is not None:
|
|
4436
|
+
_emit_error_json(
|
|
4437
|
+
INVALID_INPUT_CODE,
|
|
4438
|
+
"--since and --pr describe mutually exclusive comparison bases.",
|
|
4439
|
+
hint="Use --since REF for a local comparison or --pr NUMBER for GitHub pull-request mode.",
|
|
4440
|
+
)
|
|
4441
|
+
raise typer.Exit(code=2)
|
|
4442
|
+
if since is None and pr is None:
|
|
4443
|
+
_emit_error_json(
|
|
4444
|
+
INVALID_INPUT_CODE,
|
|
4445
|
+
"pack gate requires --since REF or --pr NUMBER.",
|
|
4446
|
+
hint="Use --since origin/main, or --pr 123 when the GitHub base commit is available locally.",
|
|
4447
|
+
)
|
|
4448
|
+
raise typer.Exit(code=2)
|
|
4449
|
+
comparison_ref = since
|
|
4450
|
+
if pr is not None:
|
|
4451
|
+
try:
|
|
4452
|
+
from sourcecode.github_diff import pull_request_base_ref
|
|
4453
|
+
|
|
4454
|
+
comparison_ref, _description = pull_request_base_ref(root, pr)
|
|
4455
|
+
except Exception as exc:
|
|
4456
|
+
from sourcecode.github_diff import GitHubDiffError
|
|
4457
|
+
|
|
4458
|
+
if isinstance(exc, GitHubDiffError):
|
|
4459
|
+
_emit_error_json(INVALID_INPUT_CODE, str(exc), pr=pr)
|
|
4460
|
+
raise typer.Exit(code=2)
|
|
4461
|
+
raise
|
|
4462
|
+
data = run_gate(
|
|
4463
|
+
root, since=comparison_ref or "", pr=pr, profile=profile,
|
|
4464
|
+
allow_unverified=allow_unverified, compact=compact, output_dir=output_dir,
|
|
4465
|
+
)
|
|
4408
4466
|
if format in {"github-actions", "gitlab"}:
|
|
4409
4467
|
from sourcecode.ci_output import render_pack_gate_github_actions, render_pack_gate_gitlab
|
|
4410
4468
|
output = (render_pack_gate_github_actions(data)
|
|
@@ -18670,7 +18728,6 @@ def cache_warm_cmd(
|
|
|
18670
18728
|
this command reports. Cache ~/.sourcecode/cache and ~/.sourcecode between jobs,
|
|
18671
18729
|
or budget the cold run explicitly.
|
|
18672
18730
|
"""
|
|
18673
|
-
import shutil as _shutil
|
|
18674
18731
|
import subprocess as _sub
|
|
18675
18732
|
import time as _warm_time
|
|
18676
18733
|
# Warm exactly the path given. Resolving up to the enclosing git root warmed
|
|
@@ -18702,7 +18759,11 @@ def cache_warm_cmd(
|
|
|
18702
18759
|
f"(scoped to {target}; the enclosing git repository is {_git_root} — "
|
|
18703
18760
|
"git-derived signals are computed within the scope above)"
|
|
18704
18761
|
)
|
|
18705
|
-
|
|
18762
|
+
# Launch the worker through this interpreter rather than resolving a second
|
|
18763
|
+
# console script from PATH. A stale global script can otherwise warm one
|
|
18764
|
+
# package installation while the caller reads another one, leaving a view
|
|
18765
|
+
# apparently fresh even though its cache entry was never written.
|
|
18766
|
+
_sc_bin = [sys.executable, "-m", "sourcecode"]
|
|
18706
18767
|
|
|
18707
18768
|
# AUD-600-R01: which views a warm builds is `cache_model`'s fact, not a pair
|
|
18708
18769
|
# of literals here. The warm used to run one child with `--compact` and an
|
|
@@ -18751,7 +18812,7 @@ def cache_warm_cmd(
|
|
|
18751
18812
|
# stderr: the line above says it started, the spinner says it is still going.
|
|
18752
18813
|
_warmed_ids: list[str] = []
|
|
18753
18814
|
for _view in _selected:
|
|
18754
|
-
cmd = [_sc_bin, str(target), *_warm_view_flags(_view.id)]
|
|
18815
|
+
cmd = [*_sc_bin, str(target), *_warm_view_flags(_view.id)]
|
|
18755
18816
|
_prog = Progress()
|
|
18756
18817
|
_prog.start(f"running full analysis ({_view.id} view)")
|
|
18757
18818
|
try:
|
sourcecode/envelope.py
CHANGED
|
@@ -81,7 +81,7 @@ def _tree_state(target: Path) -> "tuple[str, str, str]":
|
|
|
81
81
|
return "", "", ""
|
|
82
82
|
# Git for Windows emits forward slashes while Path resolves to native
|
|
83
83
|
# separators. Restore the platform-native spelling for this path contract.
|
|
84
|
-
root =
|
|
84
|
+
root = portable_path(Path(root))
|
|
85
85
|
prefix = _git(["rev-parse", "--show-prefix"], target)
|
|
86
86
|
head = _git(["rev-parse", "HEAD"], target) or "no-commit"
|
|
87
87
|
dirty = _git(["status", "--porcelain"], target)
|
sourcecode/github_diff.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Optional GitHub CLI adapter for obtaining a pull-request file list."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import json
|
|
4
5
|
import subprocess
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
@@ -32,3 +33,53 @@ def pull_request_files(root: Path, number: int) -> tuple[list[str], str]:
|
|
|
32
33
|
)
|
|
33
34
|
paths = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
|
34
35
|
return paths, f"gh pr diff {number} --name-only"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def pull_request_base_ref(root: Path, number: int) -> tuple[str, str]:
|
|
39
|
+
"""Return the exact local comparison commit for a GitHub pull request.
|
|
40
|
+
|
|
41
|
+
The gate's posture component compares two repository states, unlike
|
|
42
|
+
`pr-impact`, which only needs GitHub's changed-file list. Resolve the base
|
|
43
|
+
SHA through `gh`, then require that the checkout actually has that object;
|
|
44
|
+
a missing base is an actionable CI checkout problem, not a reason to compare
|
|
45
|
+
against an arbitrary branch name.
|
|
46
|
+
"""
|
|
47
|
+
if number < 1:
|
|
48
|
+
raise GitHubDiffError("pull request number must be a positive integer")
|
|
49
|
+
command = ["gh", "pr", "view", str(number), "--json", "baseRefName,baseRefOid"]
|
|
50
|
+
try:
|
|
51
|
+
result = subprocess.run(
|
|
52
|
+
command, cwd=root, capture_output=True, text=True, check=False,
|
|
53
|
+
)
|
|
54
|
+
except FileNotFoundError as exc:
|
|
55
|
+
raise GitHubDiffError(
|
|
56
|
+
"GitHub PR mode requires the `gh` CLI; install it or use --since"
|
|
57
|
+
) from exc
|
|
58
|
+
except OSError as exc:
|
|
59
|
+
raise GitHubDiffError(f"could not start `gh`: {exc}") from exc
|
|
60
|
+
if result.returncode != 0:
|
|
61
|
+
detail = result.stderr.strip() or "GitHub CLI returned a non-zero status"
|
|
62
|
+
raise GitHubDiffError(
|
|
63
|
+
f"could not read pull request #{number}: {detail}. "
|
|
64
|
+
"Check `gh auth status` and the repository remote."
|
|
65
|
+
)
|
|
66
|
+
try:
|
|
67
|
+
payload = json.loads(result.stdout)
|
|
68
|
+
except json.JSONDecodeError as exc:
|
|
69
|
+
raise GitHubDiffError("GitHub CLI returned invalid pull-request metadata") from exc
|
|
70
|
+
base_oid = payload.get("baseRefOid")
|
|
71
|
+
base_name = payload.get("baseRefName")
|
|
72
|
+
if not isinstance(base_oid, str) or not base_oid:
|
|
73
|
+
raise GitHubDiffError("GitHub pull-request metadata did not include baseRefOid")
|
|
74
|
+
check = subprocess.run(
|
|
75
|
+
["git", "cat-file", "-e", f"{base_oid}^{{commit}}"],
|
|
76
|
+
cwd=root, capture_output=True, text=True, check=False,
|
|
77
|
+
)
|
|
78
|
+
if check.returncode != 0:
|
|
79
|
+
label = base_name if isinstance(base_name, str) and base_name else base_oid
|
|
80
|
+
raise GitHubDiffError(
|
|
81
|
+
f"pull request #{number} base {label} ({base_oid}) is not present in this checkout. "
|
|
82
|
+
"Fetch the base commit first (for GitHub Actions, use actions/checkout with fetch-depth: 0), "
|
|
83
|
+
"or use --since with an available local ref."
|
|
84
|
+
)
|
|
85
|
+
return base_oid, f"gh pr view {number} base {base_name or base_oid} ({base_oid})"
|
sourcecode/license.py
CHANGED
|
@@ -89,11 +89,20 @@ if _SUPABASE_URL != _DEFAULT_SUPABASE_URL:
|
|
|
89
89
|
# which case prompts point to the documented Pro plans instead of a placeholder link.
|
|
90
90
|
_PURCHASE_URL: str = os.environ.get("SOURCECODE_PURCHASE_URL", "").strip()
|
|
91
91
|
|
|
92
|
+
|
|
93
|
+
def _user_home() -> Path:
|
|
94
|
+
"""Honor HOME overrides used by isolated subprocesses on every host."""
|
|
95
|
+
configured = os.environ.get("HOME") or os.environ.get("USERPROFILE")
|
|
96
|
+
if configured:
|
|
97
|
+
return Path(configured)
|
|
98
|
+
return Path.home()
|
|
99
|
+
|
|
100
|
+
|
|
92
101
|
# Config home: ~/.ask is canonical (product = ASK Engine). The legacy ~/.sourcecode
|
|
93
102
|
# stays READABLE so no existing user loses their license — reads prefer ~/.ask and
|
|
94
103
|
# fall back to ~/.sourcecode; writes always land in ~/.ask. Backward-compatible.
|
|
95
|
-
_LICENSE_DIR: Path =
|
|
96
|
-
_LEGACY_LICENSE_DIR: Path =
|
|
104
|
+
_LICENSE_DIR: Path = _user_home() / ".ask"
|
|
105
|
+
_LEGACY_LICENSE_DIR: Path = _user_home() / ".sourcecode"
|
|
97
106
|
_LICENSE_FILE: Path = _LICENSE_DIR / "license.json"
|
|
98
107
|
_DELTA_RUNS_FILE: Path = _LICENSE_DIR / "delta_runs.json"
|
|
99
108
|
|
sourcecode/mcp_nudge.py
CHANGED
|
@@ -18,11 +18,21 @@ Exit code of the calling command is unaffected.
|
|
|
18
18
|
"""
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
import os
|
|
21
22
|
import sys
|
|
22
23
|
from pathlib import Path
|
|
23
24
|
|
|
25
|
+
|
|
26
|
+
def _home_dir() -> Path:
|
|
27
|
+
"""Resolve the configured home even in minimal Windows subprocess envs."""
|
|
28
|
+
configured = os.environ.get("HOME") or os.environ.get("USERPROFILE")
|
|
29
|
+
if configured:
|
|
30
|
+
return Path(configured)
|
|
31
|
+
return Path.home()
|
|
32
|
+
|
|
33
|
+
|
|
24
34
|
# Stable path used as a session-level "already shown" sentinel.
|
|
25
|
-
_FLAG: Path =
|
|
35
|
+
_FLAG: Path = _home_dir() / ".sourcecode" / "nudge_shown"
|
|
26
36
|
|
|
27
37
|
_MSG = (
|
|
28
38
|
"→ Claude Desktop detected. "
|
sourcecode/migrate_check.py
CHANGED
|
@@ -3008,6 +3008,10 @@ def _scan_file(
|
|
|
3008
3008
|
*,
|
|
3009
3009
|
declared_types: "Optional[frozenset[str]]" = None,
|
|
3010
3010
|
) -> list[MigrationFinding]:
|
|
3011
|
+
# Findings are a portable payload contract. Callers may have obtained the
|
|
3012
|
+
# relative path with ``str(Path)`` on Windows, but the same repository file
|
|
3013
|
+
# must retain one identity across hosts and subprocess boundaries.
|
|
3014
|
+
rel_path = rel_path.replace("\\", "/")
|
|
3011
3015
|
findings: list[MigrationFinding] = []
|
|
3012
3016
|
type_deprecated = _primary_type_is_deprecated(source)
|
|
3013
3017
|
|
sourcecode/packs.py
CHANGED
|
@@ -24,7 +24,7 @@ import sys
|
|
|
24
24
|
import tempfile
|
|
25
25
|
import time
|
|
26
26
|
from pathlib import Path
|
|
27
|
-
from typing import Any, Optional
|
|
27
|
+
from typing import Any, Callable, Optional, Sequence
|
|
28
28
|
|
|
29
29
|
from sourcecode import pack_manifest
|
|
30
30
|
from sourcecode.partial_contract import PARTIAL_EXIT_CODE
|
|
@@ -306,6 +306,47 @@ def pack_non_coverage(pack: str) -> dict[str, Any]:
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
|
|
309
|
+
def assessment_components(selected: "Optional[Sequence[str]]" = None) -> list[str]:
|
|
310
|
+
"""Return requested assessment components in declared execution order."""
|
|
311
|
+
declared = list(PACKS["assessment"]["commands"])
|
|
312
|
+
if selected is None:
|
|
313
|
+
return declared
|
|
314
|
+
requested = [name.strip() for name in selected if name and name.strip()]
|
|
315
|
+
if not requested:
|
|
316
|
+
raise ValueError("--only requires at least one assessment component")
|
|
317
|
+
unknown = sorted(set(requested) - set(declared))
|
|
318
|
+
if unknown:
|
|
319
|
+
raise ValueError(
|
|
320
|
+
"unknown assessment component(s): " + ", ".join(unknown) + ". "
|
|
321
|
+
"Choose from: " + ", ".join(declared)
|
|
322
|
+
)
|
|
323
|
+
if len(set(requested)) != len(requested):
|
|
324
|
+
raise ValueError("--only must not name an assessment component more than once")
|
|
325
|
+
if "audit-report" in requested and "risk" not in requested:
|
|
326
|
+
raise ValueError("audit-report requires risk in --only")
|
|
327
|
+
return [name for name in declared if name in requested]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _assessment_non_coverage(commands: list[str]) -> dict[str, Any]:
|
|
331
|
+
"""Add caller-selected omissions to the pack's normal coverage declaration."""
|
|
332
|
+
block = pack_non_coverage("assessment")
|
|
333
|
+
omitted = [name for name in PACKS["assessment"]["commands"] if name not in commands]
|
|
334
|
+
if not omitted:
|
|
335
|
+
return block
|
|
336
|
+
items = list(block.get("items") or [])
|
|
337
|
+
items.append({
|
|
338
|
+
"id": "NC-PACK-SELECTION",
|
|
339
|
+
"surface": "pack assessment",
|
|
340
|
+
"not_covered": "Assessment components omitted by --only: " + ", ".join(omitted) + ".",
|
|
341
|
+
"why": (
|
|
342
|
+
"The caller selected a bounded component subset. Silence from an omitted "
|
|
343
|
+
"component is not evidence about its subject."
|
|
344
|
+
),
|
|
345
|
+
"instead": "Re-run ask pack assessment <repo> without --only, or name the component explicitly.",
|
|
346
|
+
})
|
|
347
|
+
return {**block, "items": items}
|
|
348
|
+
|
|
349
|
+
|
|
309
350
|
#: What `--compact` keeps from each component, as dotted paths into that
|
|
310
351
|
#: component's own payload.
|
|
311
352
|
#:
|
|
@@ -771,17 +812,23 @@ def _carrier(result: "dict[str, Any]") -> "dict[str, Any]":
|
|
|
771
812
|
def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
772
813
|
deployment: Optional[list[Path]] = None,
|
|
773
814
|
compact: bool = False,
|
|
774
|
-
output_dir: Optional[Path] = None
|
|
815
|
+
output_dir: Optional[Path] = None,
|
|
816
|
+
progress: Optional[Callable[[int, int, str], None]] = None,
|
|
817
|
+
selected_components: "Optional[Sequence[str]]" = None) -> dict[str, Any]:
|
|
775
818
|
"""Build the assessment pack from existing analysis commands."""
|
|
776
819
|
no_write = _no_write_substitution("assessment", output_dir)
|
|
777
820
|
if no_write is not None:
|
|
778
821
|
output_dir = None
|
|
779
|
-
commands =
|
|
822
|
+
commands = assessment_components(selected_components)
|
|
823
|
+
analysis_commands = [command for command in commands if command != "audit-report"]
|
|
780
824
|
results: dict[str, Any] = {}
|
|
781
825
|
artifacts: dict[str, Optional[str]] = {}
|
|
782
826
|
with tempfile.TemporaryDirectory(prefix="ask-assessment-") as temp:
|
|
783
827
|
temp_root = Path(temp)
|
|
784
|
-
|
|
828
|
+
total_components = len(commands)
|
|
829
|
+
for index, command in enumerate(analysis_commands, start=1):
|
|
830
|
+
if progress:
|
|
831
|
+
progress(index, total_components, command)
|
|
785
832
|
extra: list[str] = []
|
|
786
833
|
if compact and command in {"migrate-check", "posture", "endpoints", "spring-audit"}:
|
|
787
834
|
extra.append("--compact")
|
|
@@ -797,19 +844,22 @@ def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
|
797
844
|
# that is not bounded by construction.
|
|
798
845
|
artifacts[command] = _write_artifact(output_dir, command, _carrier(result))
|
|
799
846
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
847
|
+
if "audit-report" in commands:
|
|
848
|
+
report_extra = ["--from-risk", str(temp_root / "risk.json")]
|
|
849
|
+
if profile:
|
|
850
|
+
report_extra.extend(["--profile", profile])
|
|
851
|
+
if progress:
|
|
852
|
+
progress(total_components, total_components, "audit-report")
|
|
853
|
+
report = _run_command("audit-report", root, temp_root / "audit-report.json", report_extra)
|
|
854
|
+
# The composed `--from-risk` path is a temporary file; what the manifest
|
|
855
|
+
# publishes is the reproducible form, which is `risk`'s own artifact.
|
|
856
|
+
report["invocation"] = [
|
|
857
|
+
"ask", "audit-report", "--from-risk", "risk.json",
|
|
858
|
+
*(["--profile", profile] if profile else []),
|
|
859
|
+
str(root), "--format", "json",
|
|
860
|
+
]
|
|
861
|
+
results["audit-report"] = report
|
|
862
|
+
artifacts["audit-report"] = _write_artifact(output_dir, "audit-report", _carrier(report))
|
|
813
863
|
|
|
814
864
|
components = _index(results, artifacts)
|
|
815
865
|
failures = [name for name, item in results.items() if item["exit_code"] not in (0,)]
|
|
@@ -836,7 +886,7 @@ def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
|
836
886
|
"exit_code": exit_code,
|
|
837
887
|
"commands": list(results),
|
|
838
888
|
"failures": failures,
|
|
839
|
-
"non_coverage":
|
|
889
|
+
"non_coverage": _assessment_non_coverage(commands),
|
|
840
890
|
"aggregation_rule": PACK_AGGREGATION["assessment"],
|
|
841
891
|
"manifest": pack_manifest.manifest(
|
|
842
892
|
pack="assessment",
|
|
@@ -847,16 +897,28 @@ def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
|
847
897
|
inputs={"profile": profile,
|
|
848
898
|
"deployment": [str(path) for path in (deployment or [])],
|
|
849
899
|
"compact": compact,
|
|
900
|
+
"selected_components": commands,
|
|
850
901
|
"output_dir": str(output_dir.resolve()) if output_dir else None},
|
|
851
902
|
),
|
|
852
903
|
"artifacts": artifact_payloads,
|
|
853
904
|
"intersections": build_intersections(artifact_payloads),
|
|
854
905
|
"output_dir": str(output_dir.resolve()) if output_dir else None,
|
|
906
|
+
"selection": {
|
|
907
|
+
"commands": commands,
|
|
908
|
+
"omitted_components": [
|
|
909
|
+
name for name in PACKS["assessment"]["commands"] if name not in commands
|
|
910
|
+
],
|
|
911
|
+
"basis": (
|
|
912
|
+
"All declared assessment components ran."
|
|
913
|
+
if len(commands) == len(PACKS["assessment"]["commands"])
|
|
914
|
+
else "The caller selected this component subset with --only."
|
|
915
|
+
),
|
|
916
|
+
},
|
|
855
917
|
**({"no_write_substitution": no_write} if no_write else {}),
|
|
856
918
|
}
|
|
857
919
|
|
|
858
920
|
|
|
859
|
-
def run_gate(root: Path, *, since: str, profile: Optional[str] = None,
|
|
921
|
+
def run_gate(root: Path, *, since: str, pr: Optional[int] = None, profile: Optional[str] = None,
|
|
860
922
|
allow_unverified: bool = False, compact: bool = False,
|
|
861
923
|
output_dir: Optional[Path] = None) -> dict[str, Any]:
|
|
862
924
|
"""Build one CI verdict from the existing gate-capable commands."""
|
|
@@ -885,7 +947,8 @@ def run_gate(root: Path, *, since: str, profile: Optional[str] = None,
|
|
|
885
947
|
if profile:
|
|
886
948
|
posture_extra.extend(["--profile", profile])
|
|
887
949
|
results["posture"] = _run_command("posture", root, temp_root / "posture.json", posture_extra)
|
|
888
|
-
|
|
950
|
+
pr_impact_args = ["--pr", str(pr)] if pr is not None else ["--since", since]
|
|
951
|
+
pr_impact = _run_command("pr-impact", root, temp_root / "pr-impact.json", pr_impact_args)
|
|
889
952
|
# A clean comparison is a valid no-op, not a failed safety check. The
|
|
890
953
|
# standalone command refuses an empty change set; the pack can state
|
|
891
954
|
# that condition explicitly because it is the workflow orchestrator.
|
|
@@ -947,6 +1010,7 @@ def run_gate(root: Path, *, since: str, profile: Optional[str] = None,
|
|
|
947
1010
|
"pack": "gate",
|
|
948
1011
|
"repository": str(root.resolve()),
|
|
949
1012
|
"since": since,
|
|
1013
|
+
"pr": pr,
|
|
950
1014
|
"profile": profile,
|
|
951
1015
|
"verdict": verdict,
|
|
952
1016
|
"exit_code": exit_code,
|
|
@@ -973,7 +1037,7 @@ def run_gate(root: Path, *, since: str, profile: Optional[str] = None,
|
|
|
973
1037
|
components=components,
|
|
974
1038
|
verdict=verdict,
|
|
975
1039
|
exit_code=exit_code,
|
|
976
|
-
inputs={"since": since, "profile": profile, "compact": compact,
|
|
1040
|
+
inputs={"since": since, "pr": pr, "profile": profile, "compact": compact,
|
|
977
1041
|
"allow_unverified": allow_unverified,
|
|
978
1042
|
"output_dir": str(output_dir.resolve()) if output_dir else None},
|
|
979
1043
|
),
|
sourcecode/pr_impact.py
CHANGED
|
@@ -43,6 +43,15 @@ _RISK_LABEL: dict[int, str] = {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def _split_unresolved_files(paths: list[str]) -> tuple[list[str], list[str]]:
|
|
47
|
+
"""Separate unresolved production files from test-only evidence."""
|
|
48
|
+
from sourcecode.repository_ir import is_test_source_file
|
|
49
|
+
|
|
50
|
+
productive = [path for path in paths if not is_test_source_file(path)]
|
|
51
|
+
test_sources = [path for path in paths if is_test_source_file(path)]
|
|
52
|
+
return productive, test_sources
|
|
53
|
+
|
|
54
|
+
|
|
46
55
|
# ---------------------------------------------------------------------------
|
|
47
56
|
# Output model
|
|
48
57
|
# ---------------------------------------------------------------------------
|
|
@@ -669,6 +678,17 @@ def _run_pr_impact_internal(
|
|
|
669
678
|
changed_files, file_class_index, root
|
|
670
679
|
)
|
|
671
680
|
warnings.extend(file_warnings)
|
|
681
|
+
# A changed test source can be absent from the production CIR by design. It
|
|
682
|
+
# remains evidence in `unresolved_files`, but it must not turn every
|
|
683
|
+
# production endpoint into `unknown` or inflate the blast-radius verdict.
|
|
684
|
+
# Keep the classification in the same authority used by the source walk.
|
|
685
|
+
unresolved_productive, unresolved_excluded = _split_unresolved_files(unresolved_java)
|
|
686
|
+
if unresolved_excluded:
|
|
687
|
+
warnings.append(
|
|
688
|
+
f"{len(unresolved_excluded)} changed test-source file(s) were not mapped "
|
|
689
|
+
"to production classes by design; they do not broaden endpoint "
|
|
690
|
+
"uncertainty."
|
|
691
|
+
)
|
|
672
692
|
non_java_changes = _classify_non_java(non_java)
|
|
673
693
|
|
|
674
694
|
if not class_fqns:
|
|
@@ -694,6 +714,8 @@ def _run_pr_impact_internal(
|
|
|
694
714
|
"changed_files_count": len(changed_files),
|
|
695
715
|
"classes_analyzed": 0,
|
|
696
716
|
"unresolved_java_count": len(unresolved_java),
|
|
717
|
+
"unresolved_productive_count": len(unresolved_productive),
|
|
718
|
+
"unresolved_test_source_count": len(unresolved_excluded),
|
|
697
719
|
"non_java_count": len(non_java_changes),
|
|
698
720
|
},
|
|
699
721
|
)
|
|
@@ -785,7 +807,7 @@ def _run_pr_impact_internal(
|
|
|
785
807
|
event_consumers,
|
|
786
808
|
tx_methods,
|
|
787
809
|
individual_risks,
|
|
788
|
-
|
|
810
|
+
unresolved_productive,
|
|
789
811
|
container_wired=wired,
|
|
790
812
|
risk_by_class=risk_by_class,
|
|
791
813
|
)
|
|
@@ -841,11 +863,12 @@ def _run_pr_impact_internal(
|
|
|
841
863
|
"outside it is undetermined rather than excluded"
|
|
842
864
|
),
|
|
843
865
|
))
|
|
844
|
-
if
|
|
866
|
+
if unresolved_productive:
|
|
845
867
|
disposition_blockers.append(_disposition.Blocker(
|
|
846
868
|
reason=_disposition.REASON_UNRESOLVED_CHANGED_FILE,
|
|
847
869
|
detail=(
|
|
848
|
-
f"{len(
|
|
870
|
+
f"{len(unresolved_productive)} changed production Java file(s) mapped "
|
|
871
|
+
"to no class, "
|
|
849
872
|
"so part of the diff was never analysed and the surface it might "
|
|
850
873
|
"reach is unknown by construction"
|
|
851
874
|
),
|
|
@@ -904,6 +927,8 @@ def _run_pr_impact_internal(
|
|
|
904
927
|
"route population (`endpoints.total`)"
|
|
905
928
|
),
|
|
906
929
|
"unresolved_java_count": len(unresolved_java),
|
|
930
|
+
"unresolved_productive_count": len(unresolved_productive),
|
|
931
|
+
"unresolved_test_source_count": len(unresolved_excluded),
|
|
907
932
|
"non_java_count": len(non_java_changes),
|
|
908
933
|
"analysis_time_ms": elapsed_ms,
|
|
909
934
|
},
|
sourcecode/readonly.py
CHANGED
|
@@ -34,6 +34,8 @@ from __future__ import annotations
|
|
|
34
34
|
import os
|
|
35
35
|
import sys
|
|
36
36
|
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
from sourcecode.path_display import portable_path
|
|
37
39
|
from typing import Optional
|
|
38
40
|
|
|
39
41
|
#: Reads the mode for a process that was not given a flag.
|
|
@@ -197,7 +199,7 @@ def as_dict() -> dict:
|
|
|
197
199
|
"""The mode as data, for a payload that wants to state what it ran under."""
|
|
198
200
|
return {
|
|
199
201
|
"no_write": is_readonly(),
|
|
200
|
-
"scope":
|
|
202
|
+
"scope": portable_path(_SCOPE) if _SCOPE else None,
|
|
201
203
|
"refused": refusals(),
|
|
202
204
|
"guarantee": (
|
|
203
205
|
"Nothing is created or modified inside the analysed repository. Shared caches "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 5.8.
|
|
3
|
+
Version: 5.8.31
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/HarounDominique/sourcecode
|
|
6
6
|
Project-URL: Repository, https://github.com/HarounDominique/sourcecode
|
|
@@ -59,7 +59,7 @@ Description-Content-Type: text/markdown
|
|
|
59
59
|
> question. All analysis is local and deterministic; see [Privacy](#configuration--privacy)
|
|
60
60
|
> before inspecting a repository whose telemetry policy you need to control.
|
|
61
61
|
|
|
62
|
-

|
|
63
63
|

|
|
64
64
|
|
|
65
65
|
Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
|
|
@@ -177,14 +177,14 @@ set `ASK_MAX_RSS_MB=<megabytes>`; if the process exceeds it, ASK refuses the
|
|
|
177
177
|
answer with `MEMORY_TOO_LARGE`, including the observed peak and limit. Leave it
|
|
178
178
|
unset, or set it to `0`, to disable the ceiling explicitly.
|
|
179
179
|
|
|
180
|
-
**Current performance status.** Release 5.8.
|
|
180
|
+
**Current performance status.** Release 5.8.31 retains the shared Canonical IR
|
|
181
181
|
path used by `validation` and now passes that same CIR into labelled `data-exposure`
|
|
182
182
|
runs; the no-label path remains parse-free. These are implementation properties,
|
|
183
183
|
not duration promises. A controlled external warm-path audit has opened performance
|
|
184
184
|
and bounded-output regressions for this release; use `ask cache model <repo>` and
|
|
185
185
|
`--progress stderr` for supervised runs, and consult the
|
|
186
|
-
[active audit intake](docs/AUDIT-2026-08-26-EXTERNAL-5.8.
|
|
187
|
-
historical timing or bounded flag as a current guarantee.
|
|
186
|
+
[active pack audit intake](docs/AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md) before
|
|
187
|
+
treating a historical timing or bounded flag as a current guarantee.
|
|
188
188
|
|
|
189
189
|
**What a warm actually covers.** `ask cache warm` runs the compact analysis: it rebuilds the
|
|
190
190
|
shared structural layers (L1/L2 + the Repository Intelligence Snapshot + the shared Canonical
|
|
@@ -210,7 +210,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
210
210
|
# pip / pipx
|
|
211
211
|
pipx install sourcecode # or: pip install sourcecode
|
|
212
212
|
|
|
213
|
-
ask version # ask 5.8.
|
|
213
|
+
ask version # ask 5.8.31 — and, on a build that has aged,
|
|
214
214
|
# how many releases have probably shipped since
|
|
215
215
|
```
|
|
216
216
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=x6huFekCWuzbMyWgV9DNW6WDTwRfZV3q9UlfspK-7AY,309
|
|
2
2
|
sourcecode/__main__.py,sha256=qDFVkobwsdZrX0B3stiRqsUNivVpybKO-egzAwCW4xw,165
|
|
3
|
-
sourcecode/_build_commit.py,sha256=
|
|
3
|
+
sourcecode/_build_commit.py,sha256=gOxdbVEG8QZSIEAPML8CaTzP3DaBaqHqkVEV25ceY4U,8740
|
|
4
4
|
sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
|
|
5
5
|
sourcecode/analysis_budget.py,sha256=ZuNEboPF8A6GK-_V7WNt92ezgriga7_Rbr-vvSN4UEE,4183
|
|
6
6
|
sourcecode/answer_bounds.py,sha256=_51_DCuYEUmI_xwUFxDMiprSavkNakkLiIBbPjevQ3M,9793
|
|
@@ -19,7 +19,7 @@ sourcecode/breaking_changes.py,sha256=iaTEkegVGnR_DEQP45U66GQKMkq-_qNM60nv7XEPm7
|
|
|
19
19
|
sourcecode/build_modules.py,sha256=rsT8reXoeOGztX28SNwem3yGdBhAC5VJx9RL2vRp_d0,15935
|
|
20
20
|
sourcecode/build_stamp.py,sha256=YWlnT8CEFzMre5pacRQkc244BfZNG5owySIyP9Dq4pw,7677
|
|
21
21
|
sourcecode/bundled_docs.py,sha256=KymEkB7P8oeg0-PjcD8eRSDhqQbwQxDUbHv1i00LQOc,2663
|
|
22
|
-
sourcecode/cache.py,sha256=
|
|
22
|
+
sourcecode/cache.py,sha256=cCvSNJvm897M_i_nGHZYW-_T9TvXwCBnT5EYqFXkMSY,45799
|
|
23
23
|
sourcecode/cache_model.py,sha256=fI5dEvK2-dA246a_-DHyq8zumBPR0rjYvyxx397SNW4,73716
|
|
24
24
|
sourcecode/cache_observation.py,sha256=5E2BeqiYE5wBCbvDglkc5A9fTNhXkT_OhcDuuCqRu4Q,5622
|
|
25
25
|
sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
|
|
@@ -31,7 +31,7 @@ sourcecode/change_plan.py,sha256=drAoDsqFplutCLc6Y0xue7sYkTls0SpYh7aOXvttv3I,129
|
|
|
31
31
|
sourcecode/ci_output.py,sha256=X4NRokWWMRF9Ex8OQrEO2g04GzJ00GDP5zYIoqI9YCc,4625
|
|
32
32
|
sourcecode/cir_graphs.py,sha256=RytqSv1WF8kdlfXj2fPQz16LY4akS2kyThbdWL8beCQ,14835
|
|
33
33
|
sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
|
|
34
|
-
sourcecode/cli.py,sha256=
|
|
34
|
+
sourcecode/cli.py,sha256=kA6mou1cf-9nQ90E2K9oGNTFplI2BReBcTPjPUcTDVs,869605
|
|
35
35
|
sourcecode/client_calls.py,sha256=YOPnfRZzRyqmdHvXSVeiunW8cuiuCuWXacNIQRgKMCw,22640
|
|
36
36
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
37
37
|
sourcecode/compare.py,sha256=Lnfmm9I3k0H1jP1tV6ZJ-R7znjQPLPLCe5TzV1Jb_PM,15323
|
|
@@ -64,7 +64,7 @@ sourcecode/endpoint_literals.py,sha256=Qf4gTZzvNSFDGDuOvF0YRL9NvaYKKj24klhR5LIOa
|
|
|
64
64
|
sourcecode/endpoint_metrics.py,sha256=sLSLUIgiIyvNdOynaxVgHd9SjRF_DbN3QxevaquykaU,2840
|
|
65
65
|
sourcecode/entrypoint_classifier.py,sha256=jhTYlyqDJH2AtdEcLVaRU3lYRTJuF8DkxVzl4-W3zWE,5322
|
|
66
66
|
sourcecode/env_analyzer.py,sha256=tU2CcW-Lkl__60DMd4ibYnXnxfmPbV5Z9GrToSWLdSA,22029
|
|
67
|
-
sourcecode/envelope.py,sha256=
|
|
67
|
+
sourcecode/envelope.py,sha256=rISj4NXJZTX0scP7O01fmxl6xnCr_D_c482LkpPzVc0,17407
|
|
68
68
|
sourcecode/environment_resolution.py,sha256=e86HT3RNbIIUIRChvz1Pu9Um81UvYu22BkpHNnwbUQI,30650
|
|
69
69
|
sourcecode/error_schema.py,sha256=uAWEknt3Z-6pNoNaiaSVeiDHdBe-bmt08BDlkS1YaAs,2951
|
|
70
70
|
sourcecode/evidence_provider.py,sha256=GSSL44JEaouO5AHks2sB3d1YvC9xIKIld1yBYxZpXxo,4277
|
|
@@ -81,18 +81,18 @@ sourcecode/functional_routes.py,sha256=KXMrDoBwj_GC80w10FvgA3zddIGE1iW9KjpDtCllS
|
|
|
81
81
|
sourcecode/gate_anchors.py,sha256=26v8wUtzOk1K1Un5uXdUxAu20mqSPX3jo82zuM05ByE,2066
|
|
82
82
|
sourcecode/git_analyzer.py,sha256=irvEwddbd5_tbA_eYUZebf4mH7_nFhpjwaWAV1f7jUY,15755
|
|
83
83
|
sourcecode/git_checkout.py,sha256=gCnkMwHpU6yLyc10FoGzE8W9-tI8KyYxKTgpbWPjayo,6009
|
|
84
|
-
sourcecode/github_diff.py,sha256=
|
|
84
|
+
sourcecode/github_diff.py,sha256=H1woYKuCjb6S9sofBPpMlOpn0L4LoVxuVP7ddnhsNaA,3848
|
|
85
85
|
sourcecode/graph_analyzer.py,sha256=Gw2MzLp0P990TGx6D6Ie-WIPnjWVE6X6dNBQnoM9xPU,65857
|
|
86
86
|
sourcecode/graph_evidence.py,sha256=rENNsYRZeNstX_ExNCLlbHJAruFQwxo5d00x6wO3xwI,15030
|
|
87
87
|
sourcecode/hibernate_strat.py,sha256=kLAa5GMwYFZgOI7edl-nDcJTr4SFVYAoxtMy-fj2BsA,77954
|
|
88
88
|
sourcecode/identity_fallback.py,sha256=uRC_j_80uaI4Ue4tf-OhKX4h472dmufYcjEFGSxY2jk,9190
|
|
89
89
|
sourcecode/integration_coordinates.py,sha256=rvuApnS7WdRUbeQLg_oKj-Gi0koO2RKdw6HenXNgrDo,12443
|
|
90
90
|
sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
|
|
91
|
-
sourcecode/license.py,sha256=
|
|
91
|
+
sourcecode/license.py,sha256=N5EpGnX3nL2x2goZFyyCMSFKj4gfUse5nigRIuPN0Ek,28049
|
|
92
92
|
sourcecode/manifest_cache.py,sha256=aPRHUs781d00fVbtlTiSaq0-AS9bAqS7_zJ0snZvlJs,2244
|
|
93
|
-
sourcecode/mcp_nudge.py,sha256=
|
|
93
|
+
sourcecode/mcp_nudge.py,sha256=beNjfyb7z09T1rnS4t6Q4qfbLzZQNaP6ddCA4XpmV0s,3256
|
|
94
94
|
sourcecode/metrics_analyzer.py,sha256=gLoRWKygF18jLgwsqmGXSWopw-f5iO1VbM7Jjdif6ag,22809
|
|
95
|
-
sourcecode/migrate_check.py,sha256=
|
|
95
|
+
sourcecode/migrate_check.py,sha256=BJj_nTnZ0fAIh5X_buL9SR2i5YdilQR3a2mkTgJFwJc,171075
|
|
96
96
|
sourcecode/migration_apply.py,sha256=36E9IUq7RbpGVt6RZ8PlSZKgDoEMHorZrXw6UVWPnpw,8692
|
|
97
97
|
sourcecode/migration_blast.py,sha256=OPryWkM6-PbvcG3eJFdcWUnlpXBje41nu2BkrZnE2hg,9359
|
|
98
98
|
sourcecode/non_coverage.py,sha256=x-P6nRWc3cfUgHLUjjFWkxiyENASlksUhVHJYwd-a_8,30024
|
|
@@ -104,7 +104,7 @@ sourcecode/output_ceiling.py,sha256=rMLDHbXeC6C_uP8iQSVbbCGiJ_n0gMr2YI9zt5nltFA,
|
|
|
104
104
|
sourcecode/output_encoding.py,sha256=OxiZQtr7uWBDUtblNQviNHsTH1Xo9AZEpsegPlQWKFk,2139
|
|
105
105
|
sourcecode/output_sink.py,sha256=Eya-PnXxqc3RGulJoLhuC9LUF73SZD0aO5O2B0xAMXU,8762
|
|
106
106
|
sourcecode/pack_manifest.py,sha256=Qm6et6SQsFCLc-HJ-Uhz1XlcOa77ZDHgks4JVDEENss,20820
|
|
107
|
-
sourcecode/packs.py,sha256=
|
|
107
|
+
sourcecode/packs.py,sha256=y5IGuzF4LYzZl9z5WtkxjWFAmUNphDvmbHUFGkuUieE,48879
|
|
108
108
|
sourcecode/parallel.py,sha256=QocwdZoUabdxzd_jEz7f6B2XMsYWFaCFaIl0tIEQs3g,10546
|
|
109
109
|
sourcecode/parse_cache.py,sha256=BaPzh-KygmY4OpJQm2YGh6PWztYsj58vHWTu2eSF4s8,38769
|
|
110
110
|
sourcecode/partial_contract.py,sha256=FZUBACkClvRAmPeYVjxNWeQmP6Y22KW4fPeeTfmT80A,5951
|
|
@@ -116,14 +116,14 @@ sourcecode/phased_run.py,sha256=MOLH5f3ZMyfpSQ-tLGRy1qzL4ETE_46qaS3G0O2QsPM,2347
|
|
|
116
116
|
sourcecode/pipe_contract.py,sha256=PML0Er5d8uDyec4OrdUXuyqHbGPUYndjeaKUjkFz2u4,8369
|
|
117
117
|
sourcecode/posture.py,sha256=OQX3r0wBzdRNwMs6fuJcpFKPuyoC_uAkAVoKsYuwqgE,87190
|
|
118
118
|
sourcecode/pr_comment_renderer.py,sha256=239PmJdf95av_ZW236C7_tvq_ahECeuF9AZxrheJ4OQ,15573
|
|
119
|
-
sourcecode/pr_impact.py,sha256=
|
|
119
|
+
sourcecode/pr_impact.py,sha256=8RgTJgvEThezh-Vm65eiFvJBdb45VjcbewEk5P144-0,39184
|
|
120
120
|
sourcecode/prepare_context.py,sha256=U-iAQaLDc7qSjf2IKM4IznZFFZBDwWlFTRp_MBaALXQ,245016
|
|
121
121
|
sourcecode/product_info.py,sha256=rpuCDf0KM2SD-WP8vw2tjdcbhWPsOGEVjSWqS8fkzlo,4966
|
|
122
122
|
sourcecode/progress.py,sha256=naKbWFqf-eTE3vtLsi-eHYILz0p3pxCASAzMmvxUIAI,21643
|
|
123
123
|
sourcecode/provenance.py,sha256=gLbsLv0gze7k7ChM4i5M3MwKfFPzs8wzM72rUd2C5sM,7342
|
|
124
124
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
125
125
|
sourcecode/readiness_timeline.py,sha256=KjghC1MWH7Tzto97TBR3N0F8FkR87w5z7lZ1QsWvAQE,8506
|
|
126
|
-
sourcecode/readonly.py,sha256=
|
|
126
|
+
sourcecode/readonly.py,sha256=8MJfg5E5gjwHN2f2qw0RM7fdzxTFeOsdGwLNKHCh-xc,7462
|
|
127
127
|
sourcecode/reconciliation.py,sha256=pmqc0JNcAagKGWDcjH2rLDlKlAbfJCVUj86vV--YEXQ,35689
|
|
128
128
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
129
129
|
sourcecode/reference_facts.py,sha256=mx7H-Ax5y8sKAQHrnJCXlgjkUzqZtQTByv9Zy2lxKQQ,15500
|
|
@@ -257,13 +257,13 @@ sourcecode/telemetry/consent.py,sha256=-Wb8BFGXEexOs8M0Eja7F-T6ZtLNPeRoZnHcHvlP7
|
|
|
257
257
|
sourcecode/telemetry/events.py,sha256=l3Z2SQUO4_C2D2ItXOOdVNimAmHedUHnUxqYFTxnsAE,2900
|
|
258
258
|
sourcecode/telemetry/filters.py,sha256=-lqT2oorECuqEIdeHb--7AZ6BCaUx0bya720a-Z5x6w,7335
|
|
259
259
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
260
|
-
sourcecode/_docs/DEFECT-LEDGER.md,sha256=
|
|
260
|
+
sourcecode/_docs/DEFECT-LEDGER.md,sha256=K5k1J1JDBBeXnB50o9KxIFnFJRHBillTJNoKoiQmLXY,1108377
|
|
261
261
|
sourcecode/_docs/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
|
|
262
262
|
sourcecode/_docs/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
|
|
263
|
-
sourcecode/_docs/USER_GUIDE.md,sha256=
|
|
264
|
-
sourcecode-5.8.
|
|
265
|
-
sourcecode-5.8.
|
|
266
|
-
sourcecode-5.8.
|
|
267
|
-
sourcecode-5.8.
|
|
268
|
-
sourcecode-5.8.
|
|
269
|
-
sourcecode-5.8.
|
|
263
|
+
sourcecode/_docs/USER_GUIDE.md,sha256=w8YGMcBHQIRLwnFzqodOZhvP5eF8pfYMtw5FyUlRpNk,93284
|
|
264
|
+
sourcecode-5.8.31.dist-info/METADATA,sha256=8XTeIYslvJubUEPmt2L6EObS9m9aw4BVKL6BfBDYN0w,55428
|
|
265
|
+
sourcecode-5.8.31.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
266
|
+
sourcecode-5.8.31.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
267
|
+
sourcecode-5.8.31.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
|
|
268
|
+
sourcecode-5.8.31.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
|
|
269
|
+
sourcecode-5.8.31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|