sourcecode 5.8.31__py3-none-any.whl → 5.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sourcecode might be problematic. Click here for more details.

sourcecode/__init__.py CHANGED
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "5.8.31"
7
+ __version__ = "5.9.0"
@@ -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 = '32e9b630f9276e5e481a33f0232c3a7876cd5751'
3
+ BUILD_COMMIT = 'b4c3691c4110d863fedcd54a61a08fd2997e400c'
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,94 @@ 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.32` audit received, 2026-08-26 — `AUD-607`.** Two independent
20
+ reports ([`AUDIT-2026-08-26-EXTERNAL-5.8.32.md`](AUDIT-2026-08-26-EXTERNAL-5.8.32.md)):
21
+ a reverification round (9.7/10) and a comparison round against `5.8.31` (8/10).
22
+ Six previously closed findings hold on their third or fourth consecutive
23
+ re-check, and **no regression is reported by either round**.
24
+
25
+ One new defect is confirmed and closed in `5.9.0`: `AUD-607-C01`
26
+ (P1), where `chunk-file` emitted chunk content that did not match its own
27
+ declared `start_line`/`end_line`. Controlled reproduction found the mechanism
28
+ **wider than reported** — two defects, not one: `_flush_chunk` serialised the
29
+ untruncated pending buffer while declaring a truncated end line, and the
30
+ annotation buffer's clearing guard was **unreachable**, so a field's
31
+ `@Autowired` was carried onto the following method while the field's own
32
+ declaration line dropped out of that chunk. The report's "deliberate undeclared
33
+ overlap" hypothesis is refuted; no overlap contract is published. Its bounding
34
+ battery now asserts content against the declared range, which the previous
35
+ `include_content=False` coverage structurally could not see.
36
+
37
+ `AUD-607-E01` (P2, `explain` returned "No stereotype detected" while listing that
38
+ same class's endpoints) and `AUD-607-I01` (P3, `--files -` asserted "stdin is a
39
+ terminal" for a Git Bash/MSYS redirect it cannot verify) are also closed in
40
+ `5.9.0`. The first is fixed vendor-agnostically by reading the endpoint
41
+ evidence the answer already computed, never by adding JAX-RS to a Spring
42
+ annotation list; the second names the outcome instead of an unverifiable cause,
43
+ the same rule `_confirm` follows for C3-106.
44
+
45
+ **`AUD-607-O01` is not reproduced — second independent refutation, and it
46
+ supersedes `AUD-606-O01`.** The two reports disagreed; measurement resolved it.
47
+ The reverification round re-ran the command itself and confirmed the vendor
48
+ position, and controlled reproduction on the current checkout finds no list
49
+ exceeding the cap on `spring-petclinic` or `BroadleafCommerce`, with the
50
+ `total`/`shown`/`omitted` envelope published on both sides of the diff. The
51
+ `estimated_tokens` figure the second report cites is **not emitted by this
52
+ command's payload**; it belongs to the output-ceiling spill path and describes a
53
+ run that overflowed the ceiling. This row must not be reopened from that figure
54
+ alone.
55
+
56
+ `AUD-606-P01` is closed: the cold-cache improvement held for a second
57
+ consecutive release (`137.8s` then `130s`), so it is no longer a single sample.
58
+ `AUD-607-P01` (`pack gate --since` timing) is inconclusive for the fourth round
59
+ under confirmed process contamination and stays bound to the existing
60
+ measurement protocol — a contaminated sample is evidence of neither a regression
61
+ nor its absence.
62
+
63
+ **Feature intake `AUD-607-X01`…`X09` is CLOSED, one commit per row.** All nine
64
+ are released in `5.9.0` with bounding batteries and full registry entries
65
+ (`b7df333`, `182d95e`, `da3e2da`, `d35b7f8`, `3e42c2c`, `0fc3a15`, `55b93c2`,
66
+ `6463fdf`, `f1fa913`): `cve`, `intent`, `remediate`, `pr-risk`,
67
+ `export --c4 --render`, `watch`, `baseline --kind findings`, `drift` and
68
+ `self-audit`. Six schemas were registered and published alongside them.
69
+ ⚠**Three defects were found by building these, not by the audit:** the C4
70
+ renderer's first version drew report metadata (`summary`, `coverage_note`) as
71
+ system architecture; the watch loop stat-ed repository-relative paths against
72
+ the process CWD and so reported "nothing changed" forever; and `self-audit`
73
+ found its own schema identifier unregistered on its first run. Each is covered
74
+ by an assertion now. `X09` ships with its "not recommended" verdict inside the
75
+ payload rather than dropped: the semantic engine does not apply to this
76
+ product's own Python source, and those figures are declared absent rather than
77
+ reported as zeros.
78
+
79
+ **Current `5.9.0` queue:** `AUD-607-U01` (P3, new commands do not name their
80
+ applicable bounding flags in `--help`) and `AUD-607-U02` (P2, unconfirmed:
81
+ `cache warm` reported a component `exit 143` under a global exit `0`). Feature
82
+ intake `AUD-607-X01`…`X09` is recorded in the audit note and the development
83
+ roadmap; **two proposed features were withdrawn on inspection** because they
84
+ already ship as `spring-audit --clusters` (CODEOWNERS ownership) and
85
+ `spring-audit --since --fail-on-new` (new-versus-historical findings). The
86
+ existing `AUD-604-R01` and `AUD-605` release-artifact verification items remain
87
+ unchanged.
88
+
89
+ **External `5.8.31` audit received, 2026-08-26 - `AUD-606`.**
90
+ [`AUDIT-2026-08-26-EXTERNAL-5.8.31.md`](AUDIT-2026-08-26-EXTERNAL-5.8.31.md)
91
+ confirms the new progress and shared-cache controls, and records one actionable
92
+ external-subject follow-up `AUD-606-O01`, where `posture --diff --compact --limit`
93
+ was effectively unchanged on the auditor's nested output; the current checkout
94
+ reproduces no such gap and its bounding batteries pass. It also opens the CLI
95
+ consistency row `AUD-606-R01` for heterogeneous `retrieve` target flags, fixed
96
+ in the current checkout by `be16c04` with help-parity coverage.
97
+ The 137.8s cold run is a single-sample improvement and remains `AUD-606-P01`
98
+ benchmark follow-up, not a parser regression. `AUD-606-P02` (`pack gate` timing)
99
+ and `AUD-606-I01` (`pr-impact --files -` with `/dev/null`) are inconclusive due
100
+ to process contamination and shell-specific stdin behaviour respectively.
101
+
102
+ **Current `5.8.31` queue:** external-subject follow-up `AUD-606-O01`; controlled
103
+ benchmark/portability follow-ups `AUD-606-P01`, `AUD-606-P02` and `AUD-606-I01`.
104
+ The existing `AUD-604-R01` and `AUD-605` release-artifact verification items
105
+ remain unchanged.
106
+
19
107
  **External pack audit received, 2026-08-26 - `AUD-605`.**
20
108
  [`AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md`](AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md)
21
109
  adds two rows: `AUD-605-G01`, where an unresolved test file broadened pack-gate unknown
@@ -25,7 +113,7 @@ checkout by `52bcc36` and `f89ae18`, with focused regression batteries passing;
25
113
  artefact verification remains pending. The pack's `--since` timing is additional evidence
26
114
  for `AUD-604-P01`, not a duplicate performance finding.
27
115
 
28
- **Current `5.8.31` queue:** artefact verification for `AUD-604-R01`; the two `AUD-605`
116
+ **Previous `5.8.31` pack queue:** artefact verification for `AUD-604-R01`; the two `AUD-605`
29
117
  fixes are included in this release and await release-artefact verification.
30
118
 
31
119
  **External `5.8.28` audit received, 2026-08-26 - `AUD-604`.**
@@ -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.31, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
49
+ 5.9.0, `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.31, with release-artifact verification
55
+ the two pack fixes are included in release 5.9.0, 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.31
209
+ # ask 5.9.0
187
210
  ```
188
211
 
189
212
  Requires Python 3.9+.
@@ -228,7 +251,7 @@ estimate scaled by file count would be wrong in the direction that costs you a s
228
251
  and an anchor printed without its release goes on recommending a nightly job for a command
229
252
  that has come to finish in seconds (C3-97).
230
253
 
231
- 41 commands and seven command groups exist. Four of them carry most of the measured
254
+ 48 commands and seven command groups exist. Four of them carry most of the measured
232
255
  value in field use, and they are the ones to learn first:
233
256
 
234
257
  | Start with | Because |
@@ -1275,16 +1298,24 @@ explaining. The rest do one thing; `ask <command> --help` is the whole story.
1275
1298
  | Command | Tier | What it does |
1276
1299
  |---|---|---|
1277
1300
  | `ask migrate-check` | core | Spring Boot 2→3 readiness plus Java LTS/licensing inventory: blockers, effort range, target JDK evidence and explicit licensing-review signals. Full reference: [migrate-check.md](migrate-check.md) |
1301
+ | `ask drift` | supported | What changed in the effective access — since the last capture (`--capture` records; the default compares) or between two profile sets (`--against`). One arithmetic for both axes. Separates `newly_unmatched` from `newly_matched`, and a `removed` route is NOT reported as having become safe. |
1302
+ | `ask watch` | supported | Warm the index once, then answer what each save affected — one JSON line per tick. `--once` is the pre-commit shape; `--ticks`/`--for` bound the loop. A file added since the warm is reported `unresolved`, never `unreachable`, and a failed analysis is an `error` tick rather than an empty change list. |
1303
+ | `ask pr-risk` | supported | One verdict for one diff — does this change make things worse? Joins `pr-impact` reach, endpoint access, the shared risk model and CODEOWNERS. `--since` differences route inventories so `new_endpoints` is a fact rather than a guess. An unresolved changed file caps the verdict at `UNKNOWN`, which fails every gate. |
1304
+ | `ask remediate` | supported | One loop from finding to verified change: audit → recipe → apply in a dedicated worktree → `verify-edit`. Dry by default; `--write` applies. Publishes `mechanisable` against `manual_remainder` with the reason each family cannot be rewritten, and reports `unverified` — never `applied` — when the result could not be checked. |
1305
+ | `ask cve` | supported | Cross the dependency inventory against a **local** OSV advisory feed (`--feed`; no network is taken), then report whether a route can reach the vulnerable package: `reachable`, `imported_not_exposed`, `not_observed` or `unknown`. `not_observed` is not a statement of safety, and nothing here answers "not reachable". |
1278
1306
  | `ask explain <Class>` | supported | Human-readable architectural summary for one class (text by design, not JSON) |
1279
- | `ask export` | supported | Tool-agnostic views for downstream tooling: `--c4`, `--module-graph`, `--by-directory`, `--integrations` |
1307
+ | `ask export` | supported | Tool-agnostic views for downstream tooling: `--c4`, `--module-graph`, `--by-directory`, `--integrations`. `--c4 --render mermaid\|svg` draws the same model instead of emitting it — presentation over the existing payload, with the node cap written on the diagram. |
1280
1308
  | `ask repo-ir` | supported | Deterministic symbol-level IR for the whole repository |
1281
1309
  | `ask cold-start` | supported | Repository Intelligence Snapshot bootstrap context, returned from the persisted RIS |
1310
+ | `ask baseline … --kind findings` | supported | The defect population as a persisted series, in the same store as the architectural baseline: `capture` records it, `diff` reports new/resolved/persisting against the last capture, and `trend` says how long each still-open defect has been open. `first_seen` is the earliest STORED capture — not the day it was introduced. |
1282
1311
  | `ask prepare-context <task>` | supported | Task-shaped context: `onboard`, `delta`, `fix-bug`, `generate-tests`. See [Typical workflows](#typical-workflows) |
1283
1312
  | `ask chunk-file <file>` | supported | Split a large Java file into semantic chunks an agent can consume |
1313
+ | `ask intent "<question>"` | supported | Resolve a plain-language question to the typed `retrieve` query that answers it (scored lexical match against a declared vocabulary — no model, no generation). Prints the query; `--run` executes it. A question fitting two intents too closely is refused with both named. |
1284
1314
  | `ask rename-class` | supported | Word-boundary Java rename across the repository |
1285
1315
  | `ask schema <name>` | supported | Print a published JSON Schema for this tool's output |
1316
+ | `ask self-audit` | supported | Check the SHIPPED build's own coherence — cache model, format contract, schema registry and output affordances. Declares explicitly that the semantic engine does not apply to this product's own source (ASK analyses Java; this build is Python), rather than reporting zeros. `ask selftest` is the different question: the defect ledger run against a real repository. |
1286
1317
  | `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 |
1318
+ | `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
1319
  | `ask baseline capture\|diff\|trend` | supported | Versioned architectural metrics over time; see [`ask trend`](#ask-trend-dir--how-the-architecture-moved) |
1289
1320
  | `ask cache status\|warm\|model\|clear\|freshness` | supported | Cache inspection; `ask cache model` states what a warm buys each command |
1290
1321
  | `ask config` · `ask version` · `ask activate` · `ask auth` · `ask telemetry` · `ask mcp` | supported | Configuration, version, licence, authentication, telemetry (on by default), MCP integration |
@@ -1359,6 +1390,51 @@ ask endpoints /repo --compact # the census without the rows: coun
1359
1390
  ask endpoints /repo --output endpoints.json
1360
1391
  ```
1361
1392
 
1393
+ ### Closing the loop: find it, judge it, fix it, prove it
1394
+
1395
+ The seven newest commands are joins of pieces that already shipped, and they share one
1396
+ rule: where the answer cannot be decided, they say so rather than return a reassuring zero.
1397
+
1398
+ ```bash
1399
+ # 1. Which advisories a route can actually REACH — not which are on the classpath.
1400
+ # The feed is a local file; no network is taken during analysis, so a CI gate can
1401
+ # never pass because a fetch timed out.
1402
+ ask cve . --feed ./osv-maven.json --reachable-only --fail-on reachable
1403
+
1404
+ # 2. Ask in your own words. It SELECTS one of retrieve's typed queries and prints it;
1405
+ # --run executes it. It never invents an answer.
1406
+ ask intent "what breaks if I change OrderService?"
1407
+
1408
+ # 3. Judge the change, not the repository. UNKNOWN outranks PASS and fails the gate.
1409
+ git diff --name-only main | ask pr-risk . --files - --since main --fail-on block
1410
+
1411
+ # 4. Fix the mechanical part, and prove the tree still holds.
1412
+ ask remediate . # the plan; writes nothing
1413
+ ask remediate . --write --allow-network
1414
+
1415
+ # 5. Keep the receipts, so "how long has this been open?" has an answer.
1416
+ ask baseline capture . --kind findings
1417
+ ask baseline trend . --kind findings
1418
+ ```
1419
+
1420
+ Two of them run on a clock rather than on demand:
1421
+
1422
+ ```bash
1423
+ # In CI, after each deploy: what opened up since the last capture?
1424
+ ask drift . --profile prod --capture
1425
+ ask drift . --profile prod --fail-on-exposure
1426
+
1427
+ # In the editor: what did this save affect?
1428
+ ask watch . --once
1429
+ ```
1430
+
1431
+ And two answer about the tool rather than the repository:
1432
+
1433
+ ```bash
1434
+ ask self-audit --fail-on fail # does the INSTALLED build's registry agree with itself?
1435
+ ask selftest . # the published defect ledger, run against your repo
1436
+ ```
1437
+
1362
1438
  ### MCP server (AI agent integration)
1363
1439
 
1364
1440
  ```bash
@@ -1443,6 +1519,16 @@ the **RIS**, builds the **shared Canonical IR** and fills the **parse cache**. I
1443
1519
  `--exclude` change *what is analysed*, so they miss the warmed core and rescan — that is the
1444
1520
  171s the field measured after a 103s warm.
1445
1521
 
1522
+ Select the root views needed by a workflow explicitly:
1523
+
1524
+ ```bash
1525
+ ask cache warm . --views compact
1526
+ ask cache warm . --views compact,agent
1527
+ ```
1528
+
1529
+ Without `--views`, all declared root views are warmed. `cache model` reports the
1530
+ same per-view contract and names any view that was skipped.
1531
+
1446
1532
  What invalidates what:
1447
1533
 
1448
1534
  - **Any change to the analysed files** → every layer keys on one signature of the tree state:
@@ -0,0 +1,232 @@
1
+ """access_drift.py — what changed in the effective access, and when.
2
+
3
+ `AUD-607-X08`. `posture --diff dev:prod` already answers *"do these two
4
+ environments expose different things?"*, and `baseline trend` already keeps a
5
+ series. Neither answers the question an operator actually gets paged about:
6
+ *what changed in prod's effective access since the last deploy?* — because the
7
+ first is a comparison of two profile sets at one instant, and nobody was
8
+ recording the instants.
9
+
10
+ One diff serves both axes. A snapshot is `route → access class` for a profile
11
+ set, so comparing two profile sets and comparing one profile set across time are
12
+ the same operation on different pairs, and the two answers can never disagree
13
+ about what "newly exposed" means.
14
+
15
+ **Direction is not symmetry.** A route that moved from a matched rule to
16
+ `no_rule_matched` is not the same event as the reverse, and collapsing both into
17
+ "changed" is how an alert that matters gets read as noise. The payload separates
18
+ `newly_unmatched` from `newly_matched`.
19
+
20
+ **A route that vanished did not become safe.** It may have been deleted, renamed
21
+ or moved behind a profile that is no longer active, and this analysis cannot tell
22
+ those apart — so `removed` is its own bucket and says so.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ from dataclasses import dataclass
29
+ from datetime import datetime, timezone
30
+ from pathlib import Path
31
+ from typing import Any, Optional
32
+
33
+ SCHEMA_VERSION = "1.0"
34
+ SCHEMA_ID = "access-drift-v1"
35
+
36
+ #: Stored snapshots share the baseline directory; the prefix keeps the three
37
+ #: series (architecture, findings, access) from reading each other's documents.
38
+ FILENAME_PREFIX = "access-"
39
+
40
+ #: The access class published when the posture found no rule deciding a route.
41
+ UNMATCHED = "no_rule_matched"
42
+
43
+ #: Keys of `effective_access` that are not access classes.
44
+ _NOT_A_CLASS = frozenset({"chains", "summary"})
45
+
46
+
47
+ def _utc_now() -> str:
48
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
49
+
50
+
51
+ def routes_from_posture(posture: "dict") -> "dict[str, str]":
52
+ """`"VERB path" → access class`, from a posture payload.
53
+
54
+ The lists under `effective_access` are keyed by access class and hold
55
+ `VERB:path:controller:handler` strings. Only the verb and path identify the
56
+ route across two runs: a handler that moved class is the same route to a
57
+ caller, and keying on the handler would report a refactor as a new exposure.
58
+ """
59
+ access = ((posture or {}).get("endpoints") or {}).get("effective_access") or {}
60
+ routes: "dict[str, str]" = {}
61
+ for access_class, value in access.items():
62
+ if access_class in _NOT_A_CLASS or not isinstance(value, list):
63
+ continue
64
+ for entry in value:
65
+ parts = str(entry).split(":")
66
+ if len(parts) < 2:
67
+ continue
68
+ verb, path = parts[0].strip().upper(), parts[1].strip()
69
+ if not path:
70
+ continue
71
+ routes[f"{verb} {path}"] = access_class
72
+ return routes
73
+
74
+
75
+ def build_snapshot(
76
+ root: Path,
77
+ profiles: "set[str]",
78
+ *,
79
+ posture: "Optional[dict]" = None,
80
+ properties: "Optional[dict]" = None,
81
+ commit: "Optional[str]" = None,
82
+ ) -> dict:
83
+ """One capture of the effective access under a profile set."""
84
+ from sourcecode.architectural_baseline import git_commit
85
+
86
+ if posture is None:
87
+ from sourcecode.posture import build_posture
88
+
89
+ posture = build_posture(Path(root), set(profiles or ()), properties)
90
+
91
+ routes = routes_from_posture(posture)
92
+ by_class: "dict[str, int]" = {}
93
+ for access_class in routes.values():
94
+ by_class[access_class] = by_class.get(access_class, 0) + 1
95
+
96
+ return {
97
+ "schema_version": SCHEMA_VERSION,
98
+ "schema": SCHEMA_ID,
99
+ "kind": "access",
100
+ "captured_at": _utc_now(),
101
+ "commit": commit if commit is not None else git_commit(Path(root)),
102
+ "repo": Path(root).name,
103
+ "profiles": sorted(profiles or ()),
104
+ "counts": {
105
+ "routes": len(routes),
106
+ "by_access_class": dict(sorted(by_class.items())),
107
+ },
108
+ "routes": dict(sorted(routes.items())),
109
+ }
110
+
111
+
112
+ def snapshot_filename(snapshot: "dict") -> str:
113
+ profiles = "-".join(snapshot.get("profiles") or ()) or "default"
114
+ stem = snapshot.get("commit") or snapshot.get("captured_at", "capture")
115
+ safe = "".join(c if (c.isalnum() or c in "-_.") else "-" for c in f"{profiles}-{stem}")
116
+ return f"{FILENAME_PREFIX}{safe}.json"
117
+
118
+
119
+ def write_snapshot(snapshot: "dict", out_dir: Path) -> Path:
120
+ from sourcecode import readonly as _readonly
121
+ from sourcecode.architectural_baseline import protect_ask_dir
122
+
123
+ out_dir = Path(out_dir)
124
+ _readonly.guard(out_dir, what="access history directory")
125
+ out_dir.mkdir(parents=True, exist_ok=True)
126
+ protect_ask_dir(out_dir)
127
+ path = out_dir / snapshot_filename(snapshot)
128
+ path.write_text(
129
+ json.dumps(snapshot, indent=2, sort_keys=True, ensure_ascii=False) + "\n",
130
+ encoding="utf-8",
131
+ )
132
+ return path
133
+
134
+
135
+ def load_series(out_dir: Path, *, profiles: "Optional[set]" = None) -> "list[dict]":
136
+ """Stored access snapshots, oldest first, optionally for one profile set."""
137
+ out_dir = Path(out_dir)
138
+ if not out_dir.is_dir():
139
+ return []
140
+ series: "list[dict]" = []
141
+ wanted = sorted(profiles) if profiles is not None else None
142
+ for path in sorted(out_dir.glob(f"{FILENAME_PREFIX}*.json")):
143
+ try:
144
+ payload = json.loads(path.read_text(encoding="utf-8"))
145
+ except (OSError, ValueError):
146
+ continue
147
+ if not isinstance(payload, dict) or payload.get("kind") != "access":
148
+ continue
149
+ if wanted is not None and list(payload.get("profiles") or ()) != wanted:
150
+ continue
151
+ payload["_source_file"] = path.name
152
+ series.append(payload)
153
+ series.sort(key=lambda s: str(s.get("captured_at") or ""))
154
+ return series
155
+
156
+
157
+ def diff_snapshots(base: "dict", head: "dict", *, axis: str = "time") -> dict:
158
+ """What moved between two access snapshots.
159
+
160
+ `axis` only labels the answer: `time` for one profile set across captures,
161
+ `environment` for two profile sets at one instant. The arithmetic is identical,
162
+ which is the point — the two can never disagree about "newly exposed".
163
+ """
164
+ base_routes: "dict[str, str]" = dict(base.get("routes") or {})
165
+ head_routes: "dict[str, str]" = dict(head.get("routes") or {})
166
+
167
+ added = sorted(set(head_routes) - set(base_routes))
168
+ removed = sorted(set(base_routes) - set(head_routes))
169
+ shared = sorted(set(base_routes) & set(head_routes))
170
+
171
+ newly_unmatched: "list[dict]" = []
172
+ newly_matched: "list[dict]" = []
173
+ reclassified: "list[dict]" = []
174
+ for route in shared:
175
+ before, after = base_routes[route], head_routes[route]
176
+ if before == after:
177
+ continue
178
+ row = {"route": route, "from": before, "to": after}
179
+ if after == UNMATCHED:
180
+ newly_unmatched.append(row)
181
+ elif before == UNMATCHED:
182
+ newly_matched.append(row)
183
+ else:
184
+ reclassified.append(row)
185
+
186
+ added_rows = [{"route": r, "access": head_routes[r]} for r in added]
187
+ added_unmatched = [r for r in added_rows if r["access"] == UNMATCHED]
188
+
189
+ changed = (
190
+ len(added) + len(removed) + len(newly_unmatched)
191
+ + len(newly_matched) + len(reclassified)
192
+ )
193
+
194
+ return {
195
+ "schema_version": SCHEMA_VERSION,
196
+ "schema": SCHEMA_ID,
197
+ "axis": axis,
198
+ "base": {
199
+ "captured_at": base.get("captured_at"),
200
+ "commit": base.get("commit"),
201
+ "profiles": list(base.get("profiles") or ()),
202
+ "routes": len(base_routes),
203
+ },
204
+ "head": {
205
+ "captured_at": head.get("captured_at"),
206
+ "commit": head.get("commit"),
207
+ "profiles": list(head.get("profiles") or ()),
208
+ "routes": len(head_routes),
209
+ },
210
+ "counts": {
211
+ "added": len(added),
212
+ "added_without_a_matching_rule": len(added_unmatched),
213
+ "removed": len(removed),
214
+ "newly_unmatched": len(newly_unmatched),
215
+ "newly_matched": len(newly_matched),
216
+ "reclassified": len(reclassified),
217
+ "changed_total": changed,
218
+ },
219
+ "drifted": bool(changed),
220
+ "added": added_rows,
221
+ "removed": [{"route": r, "access": base_routes[r]} for r in removed],
222
+ "newly_unmatched": newly_unmatched,
223
+ "newly_matched": newly_matched,
224
+ "reclassified": reclassified,
225
+ "non_coverage": [
226
+ "a route in `removed` did not become safe: it may have been deleted, "
227
+ "renamed, or moved behind a profile that is no longer active, and this "
228
+ "comparison cannot tell those apart",
229
+ "routes are identified by verb and path, so a handler that moved class "
230
+ "is the same route — a refactor is not reported as a new exposure",
231
+ ],
232
+ }