sourcecode 5.8.30__py3-none-any.whl → 5.8.32__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/_build_commit.py +1 -1
- sourcecode/_docs/DEFECT-LEDGER.md +19 -1
- sourcecode/_docs/USER_GUIDE.md +37 -4
- sourcecode/cache.py +7 -1
- sourcecode/cli.py +71 -12
- sourcecode/envelope.py +1 -1
- sourcecode/github_diff.py +51 -0
- sourcecode/license.py +11 -2
- sourcecode/mcp/server.py +66 -8
- sourcecode/mcp_nudge.py +11 -1
- sourcecode/migrate_check.py +4 -0
- sourcecode/packs.py +82 -24
- sourcecode/readonly.py +3 -1
- {sourcecode-5.8.30.dist-info → sourcecode-5.8.32.dist-info}/METADATA +20 -8
- {sourcecode-5.8.30.dist-info → sourcecode-5.8.32.dist-info}/RECORD +20 -20
- {sourcecode-5.8.30.dist-info → sourcecode-5.8.32.dist-info}/WHEEL +0 -0
- {sourcecode-5.8.30.dist-info → sourcecode-5.8.32.dist-info}/entry_points.txt +0 -0
- {sourcecode-5.8.30.dist-info → sourcecode-5.8.32.dist-info}/licenses/LICENSE +0 -0
- {sourcecode-5.8.30.dist-info → sourcecode-5.8.32.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 = '4fcf033d0261c8c67fbce36a70f42a6456f90a69'
|
|
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,24 @@ 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 `5.8.31` audit received, 2026-08-26 - `AUD-606`.**
|
|
20
|
+
[`AUDIT-2026-08-26-EXTERNAL-5.8.31.md`](AUDIT-2026-08-26-EXTERNAL-5.8.31.md)
|
|
21
|
+
confirms the new progress and shared-cache controls, and records one actionable
|
|
22
|
+
external-subject follow-up `AUD-606-O01`, where `posture --diff --compact --limit`
|
|
23
|
+
was effectively unchanged on the auditor's nested output; the current checkout
|
|
24
|
+
reproduces no such gap and its bounding batteries pass. It also opens the CLI
|
|
25
|
+
consistency row `AUD-606-R01` for heterogeneous `retrieve` target flags, fixed
|
|
26
|
+
in the current checkout by `be16c04` with help-parity coverage.
|
|
27
|
+
The 137.8s cold run is a single-sample improvement and remains `AUD-606-P01`
|
|
28
|
+
benchmark follow-up, not a parser regression. `AUD-606-P02` (`pack gate` timing)
|
|
29
|
+
and `AUD-606-I01` (`pr-impact --files -` with `/dev/null`) are inconclusive due
|
|
30
|
+
to process contamination and shell-specific stdin behaviour respectively.
|
|
31
|
+
|
|
32
|
+
**Current `5.8.31` queue:** external-subject follow-up `AUD-606-O01`; controlled
|
|
33
|
+
benchmark/portability follow-ups `AUD-606-P01`, `AUD-606-P02` and `AUD-606-I01`.
|
|
34
|
+
The existing `AUD-604-R01` and `AUD-605` release-artifact verification items
|
|
35
|
+
remain unchanged.
|
|
36
|
+
|
|
19
37
|
**External pack audit received, 2026-08-26 - `AUD-605`.**
|
|
20
38
|
[`AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md`](AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md)
|
|
21
39
|
adds two rows: `AUD-605-G01`, where an unresolved test file broadened pack-gate unknown
|
|
@@ -25,7 +43,7 @@ checkout by `52bcc36` and `f89ae18`, with focused regression batteries passing;
|
|
|
25
43
|
artefact verification remains pending. The pack's `--since` timing is additional evidence
|
|
26
44
|
for `AUD-604-P01`, not a duplicate performance finding.
|
|
27
45
|
|
|
28
|
-
**
|
|
46
|
+
**Previous `5.8.31` pack queue:** artefact verification for `AUD-604-R01`; the two `AUD-605`
|
|
29
47
|
fixes are included in this release and await release-artefact verification.
|
|
30
48
|
|
|
31
49
|
**External `5.8.28` audit received, 2026-08-26 - `AUD-604`.**
|
sourcecode/_docs/USER_GUIDE.md
CHANGED
|
@@ -46,13 +46,13 @@ 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.32, `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
53
|
The current external pack audit intake is tracked in
|
|
54
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.
|
|
55
|
+
the two pack fixes are included in release 5.8.32, with release-artifact verification
|
|
56
56
|
remaining for detached execution and pack evidence.
|
|
57
57
|
|
|
58
58
|
---
|
|
@@ -115,6 +115,8 @@ ask pack list
|
|
|
115
115
|
ask pack assessment . --profile prod --output-dir .ask/packs/assessment
|
|
116
116
|
ask pack gate . --since origin/main --compact --output-dir .ask/packs/gate
|
|
117
117
|
ask pack assessment . --format markdown -o assessment.md
|
|
118
|
+
ask pack assessment . --only risk,posture --output-dir .ask/packs/assessment
|
|
119
|
+
ask pack gate . --pr 123 --output-dir .ask/packs/gate
|
|
118
120
|
```
|
|
119
121
|
|
|
120
122
|
To include deployment evidence, pass the environment artefacts that describe
|
|
@@ -129,8 +131,25 @@ The gate can emit native annotations for CI logs:
|
|
|
129
131
|
```bash
|
|
130
132
|
ask pack gate . --since origin/main --format github-actions
|
|
131
133
|
ask pack gate . --since origin/main --format gitlab
|
|
134
|
+
ask pack gate . --since origin/main --format sarif -o gate.sarif
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
`pack assessment --only` selects a comma-separated subset of the declared
|
|
138
|
+
assessment components. The manifest records omitted components;
|
|
139
|
+
`audit-report` may be selected only together with `risk`.
|
|
140
|
+
|
|
141
|
+
MCP `get_assessment_pack` exposes the same capability through its `only`
|
|
142
|
+
`list[str]` input. It uses the declared component order and validation rules;
|
|
143
|
+
the reproducible CLI command in the `mcp` projection includes the selection.
|
|
144
|
+
|
|
145
|
+
`pack gate --pr NUMBER` resolves changed files through the local `gh` CLI and
|
|
146
|
+
uses the pull request's exact base commit. The base commit must already exist
|
|
147
|
+
locally; the command does not fetch or modify the repository and cannot be
|
|
148
|
+
combined with `--since`.
|
|
149
|
+
|
|
150
|
+
The `sarif` gate format is a bounded SARIF 2.1.0 export of ASK-owned gate
|
|
151
|
+
decisions. It does not re-emit findings produced by third-party scanners.
|
|
152
|
+
|
|
134
153
|
The pack JSON preserves `manifest`, identity, fingerprints, evidence and each
|
|
135
154
|
component's partial status. `PASS`, `BLOCK` and `UNVERIFIED` are data
|
|
136
155
|
decisions; the process exit code is the pipeline signal. When `--output-dir` is
|
|
@@ -139,6 +158,10 @@ component remains a separate JSON artifact. An explicit `--output FILE` takes
|
|
|
139
158
|
precedence. See the
|
|
140
159
|
[Product Route contract](PRODUCT-ROUTE.md) for the boundaries.
|
|
141
160
|
|
|
161
|
+
The MCP `get_change_safety_gate` tool accepts the same two comparison modes:
|
|
162
|
+
`since` for a local Git ref or `pr` for GitHub pull-request resolution through
|
|
163
|
+
the local `gh` CLI. Pass exactly one; PR mode requires the base SHA locally.
|
|
164
|
+
|
|
142
165
|
See [Product Route](PRODUCT-ROUTE.md) for the evidence contract and scope.
|
|
143
166
|
|
|
144
167
|
A distinct set of commands exists to feed **AI coding agents** rather than to be read by a
|
|
@@ -183,7 +206,7 @@ pipx install sourcecode # isolated install, no venv needed
|
|
|
183
206
|
|
|
184
207
|
# Verify
|
|
185
208
|
ask version
|
|
186
|
-
# ask 5.8.
|
|
209
|
+
# ask 5.8.32
|
|
187
210
|
```
|
|
188
211
|
|
|
189
212
|
Requires Python 3.9+.
|
|
@@ -1284,7 +1307,7 @@ explaining. The rest do one thing; `ask <command> --help` is the whole story.
|
|
|
1284
1307
|
| `ask rename-class` | supported | Word-boundary Java rename across the repository |
|
|
1285
1308
|
| `ask schema <name>` | supported | Print a published JSON Schema for this tool's output |
|
|
1286
1309
|
| `ask archetype` | experimental | Evidence-based architectural archetype across four dimensions |
|
|
1287
|
-
| `ask retrieve` | parked | Typed knowledge queries over the model — kept working, no longer developed |
|
|
1310
|
+
| `ask retrieve` | parked | Typed knowledge queries over the model — kept working, no longer developed. Symbol-oriented queries accept `--target/-t`; `--module/-m`, `--subsystem/-s` and `--endpoint/-e` remain compatible domain aliases. |
|
|
1288
1311
|
| `ask baseline capture\|diff\|trend` | supported | Versioned architectural metrics over time; see [`ask trend`](#ask-trend-dir--how-the-architecture-moved) |
|
|
1289
1312
|
| `ask cache status\|warm\|model\|clear\|freshness` | supported | Cache inspection; `ask cache model` states what a warm buys each command |
|
|
1290
1313
|
| `ask config` · `ask version` · `ask activate` · `ask auth` · `ask telemetry` · `ask mcp` | supported | Configuration, version, licence, authentication, telemetry (on by default), MCP integration |
|
|
@@ -1443,6 +1466,16 @@ the **RIS**, builds the **shared Canonical IR** and fills the **parse cache**. I
|
|
|
1443
1466
|
`--exclude` change *what is analysed*, so they miss the warmed core and rescan — that is the
|
|
1444
1467
|
171s the field measured after a 103s warm.
|
|
1445
1468
|
|
|
1469
|
+
Select the root views needed by a workflow explicitly:
|
|
1470
|
+
|
|
1471
|
+
```bash
|
|
1472
|
+
ask cache warm . --views compact
|
|
1473
|
+
ask cache warm . --views compact,agent
|
|
1474
|
+
```
|
|
1475
|
+
|
|
1476
|
+
Without `--views`, all declared root views are warmed. `cache model` reports the
|
|
1477
|
+
same per-view contract and names any view that was skipped.
|
|
1478
|
+
|
|
1446
1479
|
What invalidates what:
|
|
1447
1480
|
|
|
1448
1481
|
- **Any change to the analysed files** → every layer keys on one signature of the tree state:
|
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,10 +4362,16 @@ def pack_assessment_cmd(
|
|
|
4357
4362
|
)
|
|
4358
4363
|
raise typer.Exit(code=2)
|
|
4359
4364
|
_enforce_format("pack-assessment", format)
|
|
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)
|
|
4360
4370
|
_progress = Progress().start("pack assessment")
|
|
4361
4371
|
try:
|
|
4362
4372
|
data = run_assessment(
|
|
4363
4373
|
root, profile=profile, deployment=deployment, compact=compact, output_dir=output_dir,
|
|
4374
|
+
selected_components=selected_components,
|
|
4364
4375
|
progress=lambda done, total, component: _progress.work(
|
|
4365
4376
|
done, total, stage=f"pack assessment: {component}",
|
|
4366
4377
|
unit="components", estimate=False,
|
|
@@ -4390,7 +4401,12 @@ def pack_assessment_cmd(
|
|
|
4390
4401
|
@pack_app.command("gate")
|
|
4391
4402
|
def pack_gate_cmd(
|
|
4392
4403
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)."),
|
|
4393
|
-
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
|
+
),
|
|
4394
4410
|
profile: Optional[str] = typer.Option(None, "--profile", help="Profile set used for posture evidence."),
|
|
4395
4411
|
compact: bool = typer.Option(False, "--compact", help="Omit component payload bodies from the aggregate."),
|
|
4396
4412
|
allow_unverified: bool = typer.Option(False, "--allow-unverified", help="Allow an unverified component to exit 0."),
|
|
@@ -4416,7 +4432,37 @@ def pack_gate_cmd(
|
|
|
4416
4432
|
from sourcecode.packs import run_gate
|
|
4417
4433
|
|
|
4418
4434
|
root = _admit_path(path, exit_code=2)
|
|
4419
|
-
|
|
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
|
+
)
|
|
4420
4466
|
if format in {"github-actions", "gitlab"}:
|
|
4421
4467
|
from sourcecode.ci_output import render_pack_gate_github_actions, render_pack_gate_gitlab
|
|
4422
4468
|
output = (render_pack_gate_github_actions(data)
|
|
@@ -17465,7 +17511,9 @@ def retrieve_subsystem_inventory_cmd(
|
|
|
17465
17511
|
|
|
17466
17512
|
@retrieve_app.command("subsystem-members")
|
|
17467
17513
|
def retrieve_subsystem_members_cmd(
|
|
17468
|
-
subsystem: str = typer.Option(
|
|
17514
|
+
subsystem: str = typer.Option(
|
|
17515
|
+
..., "--subsystem", "--target", "-s", "-t", help="Subsystem label or package prefix."
|
|
17516
|
+
),
|
|
17469
17517
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
17470
17518
|
output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
|
|
17471
17519
|
trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
|
|
@@ -17484,7 +17532,9 @@ def retrieve_subsystem_members_cmd(
|
|
|
17484
17532
|
|
|
17485
17533
|
@retrieve_app.command("module-dependents")
|
|
17486
17534
|
def retrieve_module_dependents_cmd(
|
|
17487
|
-
module: str = typer.Option(
|
|
17535
|
+
module: str = typer.Option(
|
|
17536
|
+
..., "--module", "--target", "-m", "-t", help="Module path or package prefix."
|
|
17537
|
+
),
|
|
17488
17538
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
17489
17539
|
output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
|
|
17490
17540
|
trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
|
|
@@ -17503,7 +17553,9 @@ def retrieve_module_dependents_cmd(
|
|
|
17503
17553
|
|
|
17504
17554
|
@retrieve_app.command("module-dependencies")
|
|
17505
17555
|
def retrieve_module_dependencies_cmd(
|
|
17506
|
-
module: str = typer.Option(
|
|
17556
|
+
module: str = typer.Option(
|
|
17557
|
+
..., "--module", "--target", "-m", "-t", help="Module path or package prefix."
|
|
17558
|
+
),
|
|
17507
17559
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
17508
17560
|
output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
|
|
17509
17561
|
trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
|
|
@@ -17541,7 +17593,9 @@ def retrieve_type_dependents_cmd(
|
|
|
17541
17593
|
|
|
17542
17594
|
@retrieve_app.command("endpoint-contract")
|
|
17543
17595
|
def retrieve_endpoint_contract_cmd(
|
|
17544
|
-
ref: str = typer.Option(
|
|
17596
|
+
ref: str = typer.Option(
|
|
17597
|
+
..., "--endpoint", "--target", "-e", "-t", help="Endpoint path or handler symbol."
|
|
17598
|
+
),
|
|
17545
17599
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
17546
17600
|
output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
|
|
17547
17601
|
trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
|
|
@@ -17560,7 +17614,9 @@ def retrieve_endpoint_contract_cmd(
|
|
|
17560
17614
|
|
|
17561
17615
|
@retrieve_app.command("endpoint-security")
|
|
17562
17616
|
def retrieve_endpoint_security_cmd(
|
|
17563
|
-
ref: str = typer.Option(
|
|
17617
|
+
ref: str = typer.Option(
|
|
17618
|
+
..., "--endpoint", "--target", "-e", "-t", help="Endpoint path or handler symbol."
|
|
17619
|
+
),
|
|
17564
17620
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
17565
17621
|
output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Write output to a file."),
|
|
17566
17622
|
trace: bool = typer.Option(False, "--trace", help="Include the Query Plan Runtime trace."),
|
|
@@ -18682,7 +18738,6 @@ def cache_warm_cmd(
|
|
|
18682
18738
|
this command reports. Cache ~/.sourcecode/cache and ~/.sourcecode between jobs,
|
|
18683
18739
|
or budget the cold run explicitly.
|
|
18684
18740
|
"""
|
|
18685
|
-
import shutil as _shutil
|
|
18686
18741
|
import subprocess as _sub
|
|
18687
18742
|
import time as _warm_time
|
|
18688
18743
|
# Warm exactly the path given. Resolving up to the enclosing git root warmed
|
|
@@ -18714,7 +18769,11 @@ def cache_warm_cmd(
|
|
|
18714
18769
|
f"(scoped to {target}; the enclosing git repository is {_git_root} — "
|
|
18715
18770
|
"git-derived signals are computed within the scope above)"
|
|
18716
18771
|
)
|
|
18717
|
-
|
|
18772
|
+
# Launch the worker through this interpreter rather than resolving a second
|
|
18773
|
+
# console script from PATH. A stale global script can otherwise warm one
|
|
18774
|
+
# package installation while the caller reads another one, leaving a view
|
|
18775
|
+
# apparently fresh even though its cache entry was never written.
|
|
18776
|
+
_sc_bin = [sys.executable, "-m", "sourcecode"]
|
|
18718
18777
|
|
|
18719
18778
|
# AUD-600-R01: which views a warm builds is `cache_model`'s fact, not a pair
|
|
18720
18779
|
# of literals here. The warm used to run one child with `--compact` and an
|
|
@@ -18763,7 +18822,7 @@ def cache_warm_cmd(
|
|
|
18763
18822
|
# stderr: the line above says it started, the spinner says it is still going.
|
|
18764
18823
|
_warmed_ids: list[str] = []
|
|
18765
18824
|
for _view in _selected:
|
|
18766
|
-
cmd = [_sc_bin, str(target), *_warm_view_flags(_view.id)]
|
|
18825
|
+
cmd = [*_sc_bin, str(target), *_warm_view_flags(_view.id)]
|
|
18767
18826
|
_prog = Progress()
|
|
18768
18827
|
_prog.start(f"running full analysis ({_view.id} view)")
|
|
18769
18828
|
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/server.py
CHANGED
|
@@ -300,7 +300,9 @@ def _pack_mcp_projection(
|
|
|
300
300
|
*,
|
|
301
301
|
repo_path: str,
|
|
302
302
|
compact: bool,
|
|
303
|
+
selected_components: list[str] | None = None,
|
|
303
304
|
since: str | None = None,
|
|
305
|
+
pr: int | None = None,
|
|
304
306
|
) -> dict[str, Any]:
|
|
305
307
|
"""Cap a pack for MCP without dropping its decision or evidence identity."""
|
|
306
308
|
# MCP adds adapter metadata; never mutate the canonical pack returned by
|
|
@@ -310,8 +312,12 @@ def _pack_mcp_projection(
|
|
|
310
312
|
return result
|
|
311
313
|
command = "ask pack assessment" if tool_name == "get_assessment_pack" else "ask pack gate"
|
|
312
314
|
args = f"{command} <repo>"
|
|
315
|
+
if selected_components:
|
|
316
|
+
args += f" --only {','.join(selected_components)}"
|
|
313
317
|
if since:
|
|
314
318
|
args += f" --since {since}"
|
|
319
|
+
elif pr is not None:
|
|
320
|
+
args += f" --pr {pr}"
|
|
315
321
|
result["mcp"] = {
|
|
316
322
|
"schema_version": MCP_PACK_PROJECTION_SCHEMA,
|
|
317
323
|
"compact": compact,
|
|
@@ -385,6 +391,7 @@ def get_assessment_pack(
|
|
|
385
391
|
repo_path: str = ".",
|
|
386
392
|
profile: str = "",
|
|
387
393
|
deployment: list[str] | None = None,
|
|
394
|
+
only: list[str] | None = None,
|
|
388
395
|
compact: bool = True,
|
|
389
396
|
) -> dict:
|
|
390
397
|
"""Modernization and Risk Assessment pack for agents.
|
|
@@ -398,6 +405,10 @@ def get_assessment_pack(
|
|
|
398
405
|
``deployment`` is repeatable and names the deployment artefact(s) used to
|
|
399
406
|
resolve the active profile: Kubernetes, Compose, Dockerfile, WebLogic,
|
|
400
407
|
dotenv or launch script. It cannot be combined with ``profile``.
|
|
408
|
+
|
|
409
|
+
``only`` selects a bounded subset of assessment components using the same
|
|
410
|
+
validation and execution order as the CLI ``--only`` option. Omitted
|
|
411
|
+
components are recorded as non-covered in the pack evidence.
|
|
401
412
|
"""
|
|
402
413
|
_raw = repo_path
|
|
403
414
|
try:
|
|
@@ -414,6 +425,15 @@ def get_assessment_pack(
|
|
|
414
425
|
INVALID_INPUT_CODE,
|
|
415
426
|
expected="list[str]",
|
|
416
427
|
)
|
|
428
|
+
if only is not None and (
|
|
429
|
+
not isinstance(only, list)
|
|
430
|
+
or any(not isinstance(item, str) or not item.strip() for item in only)
|
|
431
|
+
):
|
|
432
|
+
return _err(
|
|
433
|
+
"only must be a list of non-empty component names",
|
|
434
|
+
INVALID_INPUT_CODE,
|
|
435
|
+
expected="list[str]",
|
|
436
|
+
)
|
|
417
437
|
if profile.strip() and deployment:
|
|
418
438
|
return _err(
|
|
419
439
|
"profile and deployment cannot be combined",
|
|
@@ -424,16 +444,23 @@ def get_assessment_pack(
|
|
|
424
444
|
path_err = _check_repo_path(repo_path)
|
|
425
445
|
if path_err is not None:
|
|
426
446
|
return path_err
|
|
427
|
-
from sourcecode.packs import run_assessment
|
|
447
|
+
from sourcecode.packs import assessment_components, run_assessment
|
|
448
|
+
|
|
449
|
+
try:
|
|
450
|
+
selected_components = assessment_components(only)
|
|
451
|
+
except ValueError as exc:
|
|
452
|
+
return _err(str(exc), INVALID_INPUT_CODE, expected="Declared assessment components.")
|
|
428
453
|
|
|
429
454
|
data = run_assessment(
|
|
430
455
|
Path(repo_path),
|
|
431
456
|
profile=profile.strip() or None,
|
|
432
457
|
deployment=[Path(item) for item in (deployment or [])] or None,
|
|
433
458
|
compact=compact,
|
|
459
|
+
selected_components=selected_components,
|
|
434
460
|
)
|
|
435
461
|
return _ok(_pack_mcp_projection(
|
|
436
462
|
"get_assessment_pack", data, repo_path=repo_path, compact=compact,
|
|
463
|
+
selected_components=selected_components if only is not None else None,
|
|
437
464
|
))
|
|
438
465
|
except Exception as exc:
|
|
439
466
|
return _err(
|
|
@@ -446,6 +473,7 @@ def get_assessment_pack(
|
|
|
446
473
|
def get_change_safety_gate(
|
|
447
474
|
repo_path: str = ".",
|
|
448
475
|
since: str = "",
|
|
476
|
+
pr: int | None = None,
|
|
449
477
|
profile: str = "",
|
|
450
478
|
allow_unverified: bool = False,
|
|
451
479
|
compact: bool = True,
|
|
@@ -455,18 +483,32 @@ def get_change_safety_gate(
|
|
|
455
483
|
Returns the structured ``PASS``, ``BLOCK`` or ``UNVERIFIED`` pack with its
|
|
456
484
|
component evidence and manifest. The response is report-only for MCP: a
|
|
457
485
|
blocking or unverified result is data the agent must inspect, not a tool
|
|
458
|
-
transport error. ``since`` is required
|
|
486
|
+
transport error. Exactly one of ``since`` or ``pr`` is required. ``pr``
|
|
487
|
+
resolves the GitHub pull request through the local ``gh`` CLI and requires
|
|
488
|
+
its base commit to exist locally.
|
|
459
489
|
"""
|
|
460
490
|
_raw = repo_path
|
|
461
491
|
try:
|
|
462
492
|
if not isinstance(repo_path, str):
|
|
463
493
|
return _err("repo_path must be a string", INVALID_INPUT_CODE)
|
|
464
|
-
if not isinstance(since, str)
|
|
494
|
+
if not isinstance(since, str):
|
|
465
495
|
return _err(
|
|
466
|
-
"since must be a
|
|
496
|
+
"since must be a string",
|
|
467
497
|
INVALID_INPUT_CODE,
|
|
468
|
-
|
|
469
|
-
|
|
498
|
+
expected="A git ref string or a pull request number.",
|
|
499
|
+
)
|
|
500
|
+
if pr is not None and (not isinstance(pr, int) or isinstance(pr, bool) or pr <= 0):
|
|
501
|
+
return _err(
|
|
502
|
+
"pr must be a positive pull request number",
|
|
503
|
+
INVALID_INPUT_CODE,
|
|
504
|
+
expected="Positive integer.",
|
|
505
|
+
)
|
|
506
|
+
if bool(since.strip()) == (pr is not None):
|
|
507
|
+
return _err(
|
|
508
|
+
"exactly one of since or pr is required",
|
|
509
|
+
INVALID_INPUT_CODE,
|
|
510
|
+
hint="Pass since for a local ref, or pr for GitHub pull-request mode.",
|
|
511
|
+
expected="One non-empty git ref or one positive pull request number.",
|
|
470
512
|
)
|
|
471
513
|
if not isinstance(profile, str):
|
|
472
514
|
return _err("profile must be a string", INVALID_INPUT_CODE)
|
|
@@ -476,16 +518,32 @@ def get_change_safety_gate(
|
|
|
476
518
|
return path_err
|
|
477
519
|
from sourcecode.packs import run_gate
|
|
478
520
|
|
|
521
|
+
comparison_ref = since.strip()
|
|
522
|
+
if pr is not None:
|
|
523
|
+
try:
|
|
524
|
+
from sourcecode.github_diff import pull_request_base_ref
|
|
525
|
+
|
|
526
|
+
comparison_ref, _description = pull_request_base_ref(repo_path, pr)
|
|
527
|
+
except Exception as exc:
|
|
528
|
+
return _err(
|
|
529
|
+
str(exc),
|
|
530
|
+
INVALID_INPUT_CODE,
|
|
531
|
+
hint="Ensure gh is installed/authenticated and the PR base commit is local.",
|
|
532
|
+
expected="A resolvable GitHub pull request with a local base commit.",
|
|
533
|
+
)
|
|
534
|
+
|
|
479
535
|
data = run_gate(
|
|
480
536
|
Path(repo_path),
|
|
481
|
-
since=
|
|
537
|
+
since=comparison_ref,
|
|
538
|
+
pr=pr,
|
|
482
539
|
profile=profile.strip() or None,
|
|
483
540
|
allow_unverified=allow_unverified,
|
|
484
541
|
compact=compact,
|
|
485
542
|
)
|
|
486
543
|
return _ok(_pack_mcp_projection(
|
|
487
544
|
"get_change_safety_gate", data, repo_path=repo_path, compact=compact,
|
|
488
|
-
since=since.strip(),
|
|
545
|
+
since=since.strip() or None,
|
|
546
|
+
pr=pr,
|
|
489
547
|
))
|
|
490
548
|
except Exception as exc:
|
|
491
549
|
return _err(
|
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, Callable, 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
|
#:
|
|
@@ -772,18 +813,20 @@ def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
|
772
813
|
deployment: Optional[list[Path]] = None,
|
|
773
814
|
compact: bool = False,
|
|
774
815
|
output_dir: Optional[Path] = None,
|
|
775
|
-
progress: Optional[Callable[[int, int, str], None]] = None
|
|
816
|
+
progress: Optional[Callable[[int, int, str], None]] = None,
|
|
817
|
+
selected_components: "Optional[Sequence[str]]" = None) -> dict[str, Any]:
|
|
776
818
|
"""Build the assessment pack from existing analysis commands."""
|
|
777
819
|
no_write = _no_write_substitution("assessment", output_dir)
|
|
778
820
|
if no_write is not None:
|
|
779
821
|
output_dir = None
|
|
780
|
-
commands =
|
|
822
|
+
commands = assessment_components(selected_components)
|
|
823
|
+
analysis_commands = [command for command in commands if command != "audit-report"]
|
|
781
824
|
results: dict[str, Any] = {}
|
|
782
825
|
artifacts: dict[str, Optional[str]] = {}
|
|
783
826
|
with tempfile.TemporaryDirectory(prefix="ask-assessment-") as temp:
|
|
784
827
|
temp_root = Path(temp)
|
|
785
|
-
total_components = len(commands)
|
|
786
|
-
for index, command in enumerate(
|
|
828
|
+
total_components = len(commands)
|
|
829
|
+
for index, command in enumerate(analysis_commands, start=1):
|
|
787
830
|
if progress:
|
|
788
831
|
progress(index, total_components, command)
|
|
789
832
|
extra: list[str] = []
|
|
@@ -801,21 +844,22 @@ def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
|
801
844
|
# that is not bounded by construction.
|
|
802
845
|
artifacts[command] = _write_artifact(output_dir, command, _carrier(result))
|
|
803
846
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
progress
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
"
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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))
|
|
819
863
|
|
|
820
864
|
components = _index(results, artifacts)
|
|
821
865
|
failures = [name for name, item in results.items() if item["exit_code"] not in (0,)]
|
|
@@ -842,7 +886,7 @@ def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
|
842
886
|
"exit_code": exit_code,
|
|
843
887
|
"commands": list(results),
|
|
844
888
|
"failures": failures,
|
|
845
|
-
"non_coverage":
|
|
889
|
+
"non_coverage": _assessment_non_coverage(commands),
|
|
846
890
|
"aggregation_rule": PACK_AGGREGATION["assessment"],
|
|
847
891
|
"manifest": pack_manifest.manifest(
|
|
848
892
|
pack="assessment",
|
|
@@ -853,16 +897,28 @@ def run_assessment(root: Path, *, profile: Optional[str] = None,
|
|
|
853
897
|
inputs={"profile": profile,
|
|
854
898
|
"deployment": [str(path) for path in (deployment or [])],
|
|
855
899
|
"compact": compact,
|
|
900
|
+
"selected_components": commands,
|
|
856
901
|
"output_dir": str(output_dir.resolve()) if output_dir else None},
|
|
857
902
|
),
|
|
858
903
|
"artifacts": artifact_payloads,
|
|
859
904
|
"intersections": build_intersections(artifact_payloads),
|
|
860
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
|
+
},
|
|
861
917
|
**({"no_write_substitution": no_write} if no_write else {}),
|
|
862
918
|
}
|
|
863
919
|
|
|
864
920
|
|
|
865
|
-
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,
|
|
866
922
|
allow_unverified: bool = False, compact: bool = False,
|
|
867
923
|
output_dir: Optional[Path] = None) -> dict[str, Any]:
|
|
868
924
|
"""Build one CI verdict from the existing gate-capable commands."""
|
|
@@ -891,7 +947,8 @@ def run_gate(root: Path, *, since: str, profile: Optional[str] = None,
|
|
|
891
947
|
if profile:
|
|
892
948
|
posture_extra.extend(["--profile", profile])
|
|
893
949
|
results["posture"] = _run_command("posture", root, temp_root / "posture.json", posture_extra)
|
|
894
|
-
|
|
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)
|
|
895
952
|
# A clean comparison is a valid no-op, not a failed safety check. The
|
|
896
953
|
# standalone command refuses an empty change set; the pack can state
|
|
897
954
|
# that condition explicitly because it is the workflow orchestrator.
|
|
@@ -953,6 +1010,7 @@ def run_gate(root: Path, *, since: str, profile: Optional[str] = None,
|
|
|
953
1010
|
"pack": "gate",
|
|
954
1011
|
"repository": str(root.resolve()),
|
|
955
1012
|
"since": since,
|
|
1013
|
+
"pr": pr,
|
|
956
1014
|
"profile": profile,
|
|
957
1015
|
"verdict": verdict,
|
|
958
1016
|
"exit_code": exit_code,
|
|
@@ -979,7 +1037,7 @@ def run_gate(root: Path, *, since: str, profile: Optional[str] = None,
|
|
|
979
1037
|
components=components,
|
|
980
1038
|
verdict=verdict,
|
|
981
1039
|
exit_code=exit_code,
|
|
982
|
-
inputs={"since": since, "profile": profile, "compact": compact,
|
|
1040
|
+
inputs={"since": since, "pr": pr, "profile": profile, "compact": compact,
|
|
983
1041
|
"allow_unverified": allow_unverified,
|
|
984
1042
|
"output_dir": str(output_dir.resolve()) if output_dir else None},
|
|
985
1043
|
),
|
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.32
|
|
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
|
|
@@ -88,6 +88,8 @@ workflow-shaped packs for users who want one consumable result:
|
|
|
88
88
|
ask pack assessment . --profile prod --output-dir .ask/packs/assessment
|
|
89
89
|
ask pack gate . --since origin/main --output-dir .ask/packs/gate
|
|
90
90
|
ask pack assessment . --format markdown -o assessment.md
|
|
91
|
+
ask pack assessment . --only risk,posture --output-dir .ask/packs/assessment
|
|
92
|
+
ask pack gate . --pr 123 --output-dir .ask/packs/gate
|
|
91
93
|
```
|
|
92
94
|
|
|
93
95
|
For deployment-aware posture evidence, pass the artefacts that describe the
|
|
@@ -104,8 +106,17 @@ contract:
|
|
|
104
106
|
```bash
|
|
105
107
|
ask pack gate . --since origin/main --format github-actions
|
|
106
108
|
ask pack gate . --since origin/main --format gitlab
|
|
109
|
+
ask pack gate . --since origin/main --format sarif -o gate.sarif
|
|
107
110
|
```
|
|
108
111
|
|
|
112
|
+
`pack gate --pr NUMBER` uses the local `gh` CLI to resolve changed files and
|
|
113
|
+
the exact base commit; the base SHA must already exist locally. It does not
|
|
114
|
+
fetch or modify the repository. `pack assessment --only` runs named assessment
|
|
115
|
+
components and records omissions in the manifest. MCP's
|
|
116
|
+
`get_assessment_pack` accepts the same selection as `only: list[str]` and
|
|
117
|
+
returns the reproducible CLI command in its `mcp` projection. SARIF is a bounded
|
|
118
|
+
export of ASK-owned gate decisions, not a re-emission of third-party findings.
|
|
119
|
+
|
|
109
120
|
`PASS`, `BLOCK` and `UNVERIFIED` are data decisions. The process exit code is
|
|
110
121
|
the CI signal; `--allow-unverified` can change only that exit code, never the
|
|
111
122
|
reported verdict. Every pack carries a manifest with component invocations,
|
|
@@ -177,18 +188,19 @@ set `ASK_MAX_RSS_MB=<megabytes>`; if the process exceeds it, ASK refuses the
|
|
|
177
188
|
answer with `MEMORY_TOO_LARGE`, including the observed peak and limit. Leave it
|
|
178
189
|
unset, or set it to `0`, to disable the ceiling explicitly.
|
|
179
190
|
|
|
180
|
-
**Current performance status.** Release 5.8.
|
|
191
|
+
**Current performance status.** Release 5.8.32 retains the shared Canonical IR
|
|
181
192
|
path used by `validation` and now passes that same CIR into labelled `data-exposure`
|
|
182
193
|
runs; the no-label path remains parse-free. These are implementation properties,
|
|
183
194
|
not duration promises. A controlled external warm-path audit has opened performance
|
|
184
195
|
and bounded-output regressions for this release; use `ask cache model <repo>` and
|
|
185
196
|
`--progress stderr` for supervised runs, and consult the
|
|
186
|
-
[
|
|
197
|
+
[latest external audit intake](docs/AUDIT-2026-08-26-EXTERNAL-5.8.31.md) before
|
|
187
198
|
treating a historical timing or bounded flag as a current guarantee.
|
|
188
199
|
|
|
189
|
-
**What a warm actually covers.** `ask cache warm`
|
|
200
|
+
**What a warm actually covers.** `ask cache warm` rebuilds the declared root views and their
|
|
190
201
|
shared structural layers (L1/L2 + the Repository Intelligence Snapshot + the shared Canonical
|
|
191
|
-
IR)
|
|
202
|
+
IR). Pass `--views compact` or `--views compact,agent` to warm only selected views; without
|
|
203
|
+
`--views`, all declared views are warmed. Deeper projections
|
|
192
204
|
are separate keys and are *not* covered by either — `--full`, `--env-map` and a raised `--depth`
|
|
193
205
|
recompute on first use, as do most `prepare-context` tasks. Measured on a 3,342-file Spring
|
|
194
206
|
monolith: `cache warm` 103s → `--compact --git-context` 1s (hit), but `--agent --full --env-map
|
|
@@ -210,7 +222,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
210
222
|
# pip / pipx
|
|
211
223
|
pipx install sourcecode # or: pip install sourcecode
|
|
212
224
|
|
|
213
|
-
ask version # ask 5.8.
|
|
225
|
+
ask version # ask 5.8.32 — and, on a build that has aged,
|
|
214
226
|
# how many releases have probably shipped since
|
|
215
227
|
```
|
|
216
228
|
|
|
@@ -479,7 +491,7 @@ from it.
|
|
|
479
491
|
| `plan` / `compare` / `delta` / `contract-diff` | supported | what to review for a change; candidates by measured cost; outcome of a change; public-contract break | no verdicts, measured cost only |
|
|
480
492
|
| `validation` | supported | request-body validation coverage and gaps | |
|
|
481
493
|
| `baseline capture\|diff` · `trend` | supported | versioned architectural metrics over time; `ask trend .` reads the series (a repository path or a baselines directory), `ask baseline diff .` compares the tree against its newest point | trend reporting, not gating. Baselines land in `.ask/baselines` **inside the repository** — the history travels with the code, not with a vendor; an audit that must not write passes `--dir`. `capture --auto` is the unattended form (one baseline per commit, never rewrites one, never fails the build) — automate it: [baseline-ci.yml](docs/baseline-ci.yml) |
|
|
482
|
-
| `retrieve` | parked | typed knowledge queries over the model | |
|
|
494
|
+
| `retrieve` | parked | typed knowledge queries over the model | symbol-oriented queries accept `--target/-t`; domain aliases such as `--module/-m`, `--subsystem/-s` and `--endpoint/-e` remain supported |
|
|
483
495
|
| `timeline` | experimental | since when a number moved, and in which commit window | walks the commit series and re-runs the shipped analyses (`posture`, `data-exposure`, `endpoints`, `spring-audit`) on a read-only materialisation of each sampled tree; publishes transitions, not a per-commit dump. A sample that could not be analysed is `null` with a reason, never 0, and a transition under sampling carries the window it could have happened in |
|
|
484
496
|
| `regress` | supported | what moved between two payloads of the same command, and which declaration accounts for it | attributes each delta to a declaration the answers carry (`test_source_excluded`, `symbols_excluded`, `coverage.gaps`, `*_cap`, `waivers`); a delta nothing explains is `unattributed`. Refuses the comparison when `cache_layers`, `base_env` or the analyzer fingerprint differ |
|
|
485
497
|
| `explain-endpoint` | supported | the derivation behind one route's verdict | six steps — match, declaration, handler, chain, profiles, verdict — each naming the authority that produced it and the file it can be checked against; a step that could not be derived is `undetermined` with the reason, never absent |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=N1r9fCES7GZKl-MnDEPXkIXKvlci7HGDYyd9T2K3GrQ,309
|
|
2
2
|
sourcecode/__main__.py,sha256=qDFVkobwsdZrX0B3stiRqsUNivVpybKO-egzAwCW4xw,165
|
|
3
|
-
sourcecode/_build_commit.py,sha256=
|
|
3
|
+
sourcecode/_build_commit.py,sha256=zfltecl0ZxCjQsc4q7xcTp8qCwPBFSsHSPdUQeKA_u4,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=F32dO1oD7NPdyS6LfkKQhnbjDfYj_VzJknBWjm8joVU,869765
|
|
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
|
|
@@ -123,7 +123,7 @@ 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
|
|
@@ -221,7 +221,7 @@ sourcecode/mcp/dependency_import.py,sha256=wfvw1rmDE3n2mHDj-yHnCuy9HZ7TtPqsHG2BU
|
|
|
221
221
|
sourcecode/mcp/orchestrator.py,sha256=RUzx2uRfwk-rnEy1RKeXjTBPfUaQiBldo-b2FryL6yI,36826
|
|
222
222
|
sourcecode/mcp/registry.py,sha256=kydzpUGGKckWM_F_GeGkWG1Z_qU3R8mZSbBWYKtDewU,69326
|
|
223
223
|
sourcecode/mcp/runner.py,sha256=dRvI0xS2wa8DH0diYjJiAoaGeVdtf389PekWwwh8wRI,3083
|
|
224
|
-
sourcecode/mcp/server.py,sha256=
|
|
224
|
+
sourcecode/mcp/server.py,sha256=L3xxLaG-bLe3Asr4ZESiEIYldhvKHkfpCMoucndvLvY,76070
|
|
225
225
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
226
226
|
sourcecode/mcp/onboarding/applier.py,sha256=Sx9vHTaXr_M1Bs4uMjO7qa_a2_p4J9oz93oJsKQ7ds4,3298
|
|
227
227
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -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=Jux3d0qM_L58-tJZwX6ret7HTK9R7QAVDmfohZfKZUs,1109556
|
|
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=O5lO0REhpaaC-dfwwkHtfdr4_zrEnXO4eXhBYA00ufI,94962
|
|
264
|
+
sourcecode-5.8.32.dist-info/METADATA,sha256=nWMOqw-qy7BSXhnuO_CWY_xVz_du-216HYbPfE9nvXs,56341
|
|
265
|
+
sourcecode-5.8.32.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
266
|
+
sourcecode-5.8.32.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
267
|
+
sourcecode-5.8.32.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
|
|
268
|
+
sourcecode-5.8.32.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
|
|
269
|
+
sourcecode-5.8.32.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|