sourcebound 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clean_docs/__init__.py +26 -0
- clean_docs/__main__.py +3 -0
- clean_docs/accessibility.py +182 -0
- clean_docs/adapters/__init__.py +1 -0
- clean_docs/adapters/event_capture.py +56 -0
- clean_docs/adapters/mdx_dependencies.json +714 -0
- clean_docs/adapters/mdx_parser.mjs +29992 -0
- clean_docs/applicability.py +330 -0
- clean_docs/audit.py +1120 -0
- clean_docs/bootstrap.py +507 -0
- clean_docs/capabilities.py +200 -0
- clean_docs/changed.py +452 -0
- clean_docs/claims.py +840 -0
- clean_docs/cli.py +1612 -0
- clean_docs/context.py +307 -0
- clean_docs/corpus.py +377 -0
- clean_docs/demo.py +369 -0
- clean_docs/doctor.py +184 -0
- clean_docs/emit/__init__.py +4 -0
- clean_docs/emit/llms_txt.py +102 -0
- clean_docs/emit/stepwise.py +168 -0
- clean_docs/engine.py +324 -0
- clean_docs/errors.py +20 -0
- clean_docs/evaluation.py +867 -0
- clean_docs/execution.py +138 -0
- clean_docs/explain.py +123 -0
- clean_docs/extractors/__init__.py +19 -0
- clean_docs/extractors/command.py +51 -0
- clean_docs/extractors/inventory.py +176 -0
- clean_docs/extractors/json_pointer.py +84 -0
- clean_docs/extractors/python_literal.py +104 -0
- clean_docs/extractors/static.py +111 -0
- clean_docs/feedback.py +1390 -0
- clean_docs/impact.py +1624 -0
- clean_docs/improvements.py +1178 -0
- clean_docs/inventory.py +474 -0
- clean_docs/isolation.py +157 -0
- clean_docs/manifest.py +898 -0
- clean_docs/mdx.py +272 -0
- clean_docs/migration.py +121 -0
- clean_docs/models.py +194 -0
- clean_docs/outcomes.py +296 -0
- clean_docs/performance.py +123 -0
- clean_docs/phrasing.py +448 -0
- clean_docs/plugins.py +249 -0
- clean_docs/policy.py +536 -0
- clean_docs/projections.py +255 -0
- clean_docs/regions.py +75 -0
- clean_docs/release.py +232 -0
- clean_docs/renderers.py +57 -0
- clean_docs/residue.py +311 -0
- clean_docs/review_contracts.py +862 -0
- clean_docs/review_ledger.py +297 -0
- clean_docs/review_limits.py +9 -0
- clean_docs/sensitivity.py +602 -0
- clean_docs/snapshot.py +212 -0
- clean_docs/standard.py +281 -0
- clean_docs/standards/default.json +309 -0
- clean_docs/standards/exemplars.md +87 -0
- clean_docs/standards/v0-migrations.json +26 -0
- clean_docs/symbols.py +47 -0
- clean_docs/templates.py +96 -0
- clean_docs/verdict.py +1397 -0
- clean_docs/visuals.py +346 -0
- clean_docs/write_gate.py +170 -0
- sourcebound-1.2.1.dist-info/LICENSE +21 -0
- sourcebound-1.2.1.dist-info/METADATA +109 -0
- sourcebound-1.2.1.dist-info/RECORD +71 -0
- sourcebound-1.2.1.dist-info/WHEEL +5 -0
- sourcebound-1.2.1.dist-info/entry_points.txt +2 -0
- sourcebound-1.2.1.dist-info/top_level.txt +1 -0
clean_docs/outcomes.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""Build one local outcome receipt from deterministic repository checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from clean_docs import __version__
|
|
9
|
+
from clean_docs.audit import AuditReport, audit
|
|
10
|
+
from clean_docs.changed import ChangedReport, check_changed
|
|
11
|
+
from clean_docs.claims import SourceClaimReport, scan_source_claims
|
|
12
|
+
from clean_docs.engine import evaluate
|
|
13
|
+
from clean_docs.errors import ConfigurationError
|
|
14
|
+
from clean_docs.execution import ExecutionPolicy
|
|
15
|
+
from clean_docs.manifest import load_manifest
|
|
16
|
+
from clean_docs.inventory import InventoryReport, scan_inventory
|
|
17
|
+
from clean_docs.models import BindingResult, Manifest
|
|
18
|
+
from clean_docs.projections import evaluate_projections
|
|
19
|
+
from clean_docs.snapshot import RepositorySnapshot
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RepositoryEvidence:
|
|
24
|
+
manifest: Manifest
|
|
25
|
+
audit: AuditReport
|
|
26
|
+
inventory: InventoryReport
|
|
27
|
+
bindings: tuple[BindingResult, ...]
|
|
28
|
+
projections: tuple[BindingResult, ...]
|
|
29
|
+
source_claims: SourceClaimReport | None
|
|
30
|
+
changed: ChangedReport | None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class OutcomeReceipt:
|
|
35
|
+
ref: str
|
|
36
|
+
documents: int
|
|
37
|
+
archived_documents: int
|
|
38
|
+
hygiene_findings: int
|
|
39
|
+
baselined_hygiene_findings: int
|
|
40
|
+
bindings: int
|
|
41
|
+
current_bindings: int
|
|
42
|
+
drifted_bindings: int
|
|
43
|
+
skipped_bindings: int
|
|
44
|
+
regions: int
|
|
45
|
+
command_pins: int
|
|
46
|
+
command_pins_with_prose: int
|
|
47
|
+
symbols: int
|
|
48
|
+
projections: int
|
|
49
|
+
current_projections: int
|
|
50
|
+
stale_projections: int
|
|
51
|
+
inventory_items: int
|
|
52
|
+
covered_inventory_items: int
|
|
53
|
+
cataloged_inventory_items: int
|
|
54
|
+
ignored_inventory_items: int
|
|
55
|
+
standard_gaps: int
|
|
56
|
+
source_claims: int
|
|
57
|
+
current_source_claims: int
|
|
58
|
+
drifted_source_claims: int
|
|
59
|
+
missing_source_claims: int
|
|
60
|
+
execution_mode: str = ExecutionPolicy.TRUSTED.value
|
|
61
|
+
manifest_deprecations: tuple[str, ...] = ()
|
|
62
|
+
changed: ChangedReport | None = None
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def ok(self) -> bool:
|
|
66
|
+
return (
|
|
67
|
+
self.hygiene_findings == 0
|
|
68
|
+
and self.drifted_bindings == 0
|
|
69
|
+
and self.skipped_bindings == 0
|
|
70
|
+
and self.stale_projections == 0
|
|
71
|
+
and self.drifted_source_claims == 0
|
|
72
|
+
and self.missing_source_claims == 0
|
|
73
|
+
and (self.changed is None or self.changed.ok)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def as_dict(self) -> dict[str, object]:
|
|
77
|
+
changed = None
|
|
78
|
+
if self.changed is not None:
|
|
79
|
+
changed = {
|
|
80
|
+
"base": self.changed.base,
|
|
81
|
+
"head": self.changed.head,
|
|
82
|
+
"required": len(self.changed.required),
|
|
83
|
+
"coverage_gaps": len(self.changed.gaps),
|
|
84
|
+
"reasoned_ignores": len(self.changed.ignored),
|
|
85
|
+
"ok": self.changed.ok,
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
"schema": "sourcebound.outcome.v2",
|
|
89
|
+
"version": __version__,
|
|
90
|
+
"ref": self.ref,
|
|
91
|
+
"ok": self.ok,
|
|
92
|
+
"assurance": {
|
|
93
|
+
"scope": "configured-contract",
|
|
94
|
+
"region_bytes_checked": self.regions > 0,
|
|
95
|
+
"command_pin_output_checked": self.command_pins > 0,
|
|
96
|
+
"command_pin_prose_checked": (
|
|
97
|
+
self.command_pins > 0
|
|
98
|
+
and self.command_pins == self.command_pins_with_prose
|
|
99
|
+
),
|
|
100
|
+
"symbol_existence_checked": self.symbols > 0,
|
|
101
|
+
"accepted_source_claim_prose_checked": self.source_claims > 0,
|
|
102
|
+
"cataloged_surfaces_check_prose": False,
|
|
103
|
+
"judgment_prose_certified": False,
|
|
104
|
+
},
|
|
105
|
+
"outcomes": {
|
|
106
|
+
"protected_baseline_current": (
|
|
107
|
+
self.ok and self.changed is None and self.standard_gaps == 0
|
|
108
|
+
),
|
|
109
|
+
"coverage_complete": self.standard_gaps == 0,
|
|
110
|
+
"direct_coverage_complete": (
|
|
111
|
+
self.standard_gaps == 0 and self.cataloged_inventory_items == 0
|
|
112
|
+
),
|
|
113
|
+
"drift_caught_before_merge": (
|
|
114
|
+
0
|
|
115
|
+
if self.changed is None
|
|
116
|
+
else len(self.changed.required) + len(self.changed.gaps)
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
"documentation": {
|
|
120
|
+
"active": self.documents,
|
|
121
|
+
"archived": self.archived_documents,
|
|
122
|
+
"hygiene_findings": self.hygiene_findings,
|
|
123
|
+
"baselined_hygiene_findings": self.baselined_hygiene_findings,
|
|
124
|
+
},
|
|
125
|
+
"coverage": {
|
|
126
|
+
"total": self.inventory_items,
|
|
127
|
+
"bound": self.covered_inventory_items,
|
|
128
|
+
"cataloged": self.cataloged_inventory_items,
|
|
129
|
+
"ignored": self.ignored_inventory_items,
|
|
130
|
+
"standard_gaps": self.standard_gaps,
|
|
131
|
+
},
|
|
132
|
+
"bindings": {
|
|
133
|
+
"total": self.bindings,
|
|
134
|
+
"current": self.current_bindings,
|
|
135
|
+
"drifted": self.drifted_bindings,
|
|
136
|
+
"skipped": self.skipped_bindings,
|
|
137
|
+
"regions": self.regions,
|
|
138
|
+
"command_pins": self.command_pins,
|
|
139
|
+
"symbols": self.symbols,
|
|
140
|
+
},
|
|
141
|
+
"projections": {
|
|
142
|
+
"total": self.projections,
|
|
143
|
+
"current": self.current_projections,
|
|
144
|
+
"stale": self.stale_projections,
|
|
145
|
+
},
|
|
146
|
+
"source_claims": {
|
|
147
|
+
"total": self.source_claims,
|
|
148
|
+
"current": self.current_source_claims,
|
|
149
|
+
"drifted": self.drifted_source_claims,
|
|
150
|
+
"missing": self.missing_source_claims,
|
|
151
|
+
},
|
|
152
|
+
"changed": changed,
|
|
153
|
+
"execution": {
|
|
154
|
+
"mode": self.execution_mode,
|
|
155
|
+
"declared_processes": (
|
|
156
|
+
"skipped"
|
|
157
|
+
if self.execution_mode == ExecutionPolicy.STATIC_ONLY.value
|
|
158
|
+
else "permitted-by-manifest"
|
|
159
|
+
),
|
|
160
|
+
"network_isolation": "not-provided",
|
|
161
|
+
"network_observation": "not-instrumented",
|
|
162
|
+
},
|
|
163
|
+
"deprecations": list(self.manifest_deprecations),
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def build_outcome_receipt(
|
|
168
|
+
root: Path,
|
|
169
|
+
manifest_path: Path,
|
|
170
|
+
*,
|
|
171
|
+
base: str | None = None,
|
|
172
|
+
head: str | None = None,
|
|
173
|
+
project: Path = Path("."),
|
|
174
|
+
execution_policy: ExecutionPolicy = ExecutionPolicy.TRUSTED,
|
|
175
|
+
use_cache: bool = True,
|
|
176
|
+
) -> OutcomeReceipt:
|
|
177
|
+
evidence = collect_repository_evidence(
|
|
178
|
+
root,
|
|
179
|
+
manifest_path,
|
|
180
|
+
base=base,
|
|
181
|
+
head=head,
|
|
182
|
+
project=project,
|
|
183
|
+
execution_policy=execution_policy,
|
|
184
|
+
use_cache=use_cache,
|
|
185
|
+
)
|
|
186
|
+
root = root.resolve()
|
|
187
|
+
inventory = evidence.inventory
|
|
188
|
+
audit_report = evidence.audit
|
|
189
|
+
bindings = evidence.bindings
|
|
190
|
+
projections = evidence.projections
|
|
191
|
+
source_claim_report = evidence.source_claims
|
|
192
|
+
changed = evidence.changed
|
|
193
|
+
manifest = evidence.manifest
|
|
194
|
+
covered_items = sum(item.coverage == "bound" for item in inventory.items)
|
|
195
|
+
cataloged_items = sum(item.coverage == "cataloged" for item in inventory.items)
|
|
196
|
+
ignored_items = sum(item.coverage == "ignored" for item in inventory.items)
|
|
197
|
+
standard_gaps = sum(item.coverage == "standard-gap" for item in inventory.items)
|
|
198
|
+
return OutcomeReceipt(
|
|
199
|
+
RepositorySnapshot(root).label,
|
|
200
|
+
len(audit_report.documents),
|
|
201
|
+
len(audit_report.ignored_documents),
|
|
202
|
+
len(audit_report.findings) + len(audit_report.stale_baseline),
|
|
203
|
+
len(audit_report.baselined_findings),
|
|
204
|
+
len(bindings),
|
|
205
|
+
sum(not item.changed for item in bindings),
|
|
206
|
+
sum(
|
|
207
|
+
item.changed and item.state != "skipped-untrusted-execution"
|
|
208
|
+
for item in bindings
|
|
209
|
+
),
|
|
210
|
+
sum(item.state == "skipped-untrusted-execution" for item in bindings),
|
|
211
|
+
sum(item.binding_type == "region" for item in bindings),
|
|
212
|
+
sum(
|
|
213
|
+
item.binding_type == "command-pin"
|
|
214
|
+
and item.state != "skipped-untrusted-execution"
|
|
215
|
+
for item in bindings
|
|
216
|
+
),
|
|
217
|
+
sum(
|
|
218
|
+
item.binding_type == "command-pin"
|
|
219
|
+
and item.state != "skipped-untrusted-execution"
|
|
220
|
+
and item.prose_checked
|
|
221
|
+
for item in bindings
|
|
222
|
+
),
|
|
223
|
+
sum(item.binding_type == "symbol" for item in bindings),
|
|
224
|
+
len(projections),
|
|
225
|
+
sum(not item.changed for item in projections),
|
|
226
|
+
sum(item.changed for item in projections),
|
|
227
|
+
len(inventory.items),
|
|
228
|
+
covered_items,
|
|
229
|
+
cataloged_items,
|
|
230
|
+
ignored_items,
|
|
231
|
+
standard_gaps,
|
|
232
|
+
0 if source_claim_report is None else len(source_claim_report.results),
|
|
233
|
+
0
|
|
234
|
+
if source_claim_report is None
|
|
235
|
+
else sum(item.status == "current" for item in source_claim_report.results),
|
|
236
|
+
0
|
|
237
|
+
if source_claim_report is None
|
|
238
|
+
else sum(item.status == "drift" for item in source_claim_report.results),
|
|
239
|
+
0 if source_claim_report is None else len(source_claim_report.missing),
|
|
240
|
+
execution_policy.value,
|
|
241
|
+
manifest.deprecations,
|
|
242
|
+
changed,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def collect_repository_evidence(
|
|
247
|
+
root: Path,
|
|
248
|
+
manifest_path: Path,
|
|
249
|
+
*,
|
|
250
|
+
base: str | None = None,
|
|
251
|
+
head: str | None = None,
|
|
252
|
+
project: Path = Path("."),
|
|
253
|
+
execution_policy: ExecutionPolicy = ExecutionPolicy.TRUSTED,
|
|
254
|
+
use_cache: bool = True,
|
|
255
|
+
) -> RepositoryEvidence:
|
|
256
|
+
if (base is None) != (head is None):
|
|
257
|
+
raise ConfigurationError("verify requires both --base and --head")
|
|
258
|
+
root = root.resolve()
|
|
259
|
+
manifest = load_manifest(manifest_path)
|
|
260
|
+
audit_report = audit(root)
|
|
261
|
+
inventory = scan_inventory(root)
|
|
262
|
+
bindings = tuple(evaluate(root, manifest_path, execution_policy=execution_policy))
|
|
263
|
+
projections = (
|
|
264
|
+
tuple(evaluate_projections(root, manifest))
|
|
265
|
+
if manifest.projections is not None
|
|
266
|
+
else ()
|
|
267
|
+
)
|
|
268
|
+
source_claim_report = (
|
|
269
|
+
scan_source_claims(
|
|
270
|
+
root,
|
|
271
|
+
manifest.source_claim_checks,
|
|
272
|
+
discover=False,
|
|
273
|
+
)
|
|
274
|
+
if manifest.source_claim_checks
|
|
275
|
+
else None
|
|
276
|
+
)
|
|
277
|
+
changed = None
|
|
278
|
+
if base is not None and head is not None:
|
|
279
|
+
changed = check_changed(
|
|
280
|
+
root,
|
|
281
|
+
manifest_path,
|
|
282
|
+
base=base,
|
|
283
|
+
head=head,
|
|
284
|
+
project=project,
|
|
285
|
+
execution_policy=execution_policy,
|
|
286
|
+
use_cache=use_cache,
|
|
287
|
+
)
|
|
288
|
+
return RepositoryEvidence(
|
|
289
|
+
manifest,
|
|
290
|
+
audit_report,
|
|
291
|
+
inventory,
|
|
292
|
+
bindings,
|
|
293
|
+
projections,
|
|
294
|
+
source_claim_report,
|
|
295
|
+
changed,
|
|
296
|
+
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Measure changed-check latency and peak process memory against published budgets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import resource
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from clean_docs.changed import CHANGED_CHECK_BUDGET_SECONDS, ChangedReport, check_changed
|
|
15
|
+
from clean_docs.errors import ConfigurationError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
CHANGED_CHECK_MEMORY_BUDGET_MB = 256.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class PerformanceReceipt:
|
|
23
|
+
base: str
|
|
24
|
+
head: str
|
|
25
|
+
project: str
|
|
26
|
+
iterations: int
|
|
27
|
+
p95_seconds: float
|
|
28
|
+
peak_memory_mb: float
|
|
29
|
+
time_budget_seconds: float
|
|
30
|
+
memory_budget_mb: float
|
|
31
|
+
result_sha256: str
|
|
32
|
+
required: int
|
|
33
|
+
gaps: int
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def ok(self) -> bool:
|
|
37
|
+
return (
|
|
38
|
+
self.p95_seconds <= self.time_budget_seconds
|
|
39
|
+
and self.peak_memory_mb <= self.memory_budget_mb
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def as_dict(self) -> dict[str, object]:
|
|
43
|
+
return {
|
|
44
|
+
"schema": "sourcebound.performance.v2",
|
|
45
|
+
"ok": self.ok,
|
|
46
|
+
"base": self.base,
|
|
47
|
+
"head": self.head,
|
|
48
|
+
"project": self.project,
|
|
49
|
+
"iterations": self.iterations,
|
|
50
|
+
"p95_seconds": self.p95_seconds,
|
|
51
|
+
"peak_memory_mb": self.peak_memory_mb,
|
|
52
|
+
"budgets": {
|
|
53
|
+
"p95_seconds": self.time_budget_seconds,
|
|
54
|
+
"peak_memory_mb": self.memory_budget_mb,
|
|
55
|
+
},
|
|
56
|
+
"normalized_result_sha256": self.result_sha256,
|
|
57
|
+
"required": self.required,
|
|
58
|
+
"coverage_gaps": self.gaps,
|
|
59
|
+
"execution": {
|
|
60
|
+
"declared_processes": "may-run-when-required-by-changed-scope",
|
|
61
|
+
"network_isolation": "not-provided",
|
|
62
|
+
"network_observation": "not-instrumented",
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _digest(report: ChangedReport) -> str:
|
|
68
|
+
payload = json.dumps(report.as_dict(), sort_keys=True, separators=(",", ":"))
|
|
69
|
+
return hashlib.sha256(payload.encode()).hexdigest()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def benchmark_changed_check(
|
|
73
|
+
root: Path,
|
|
74
|
+
manifest_path: Path,
|
|
75
|
+
*,
|
|
76
|
+
base: str,
|
|
77
|
+
head: str,
|
|
78
|
+
project: Path = Path("."),
|
|
79
|
+
iterations: int = 7,
|
|
80
|
+
time_budget_seconds: float = CHANGED_CHECK_BUDGET_SECONDS,
|
|
81
|
+
memory_budget_mb: float = CHANGED_CHECK_MEMORY_BUDGET_MB,
|
|
82
|
+
) -> PerformanceReceipt:
|
|
83
|
+
if not 3 <= iterations <= 50:
|
|
84
|
+
raise ConfigurationError("benchmark iterations must be 3..50")
|
|
85
|
+
if time_budget_seconds <= 0 or memory_budget_mb <= 0:
|
|
86
|
+
raise ConfigurationError("benchmark budgets must be positive")
|
|
87
|
+
durations: list[float] = []
|
|
88
|
+
reports: list[ChangedReport] = []
|
|
89
|
+
for _index in range(iterations):
|
|
90
|
+
started = time.perf_counter()
|
|
91
|
+
report = check_changed(
|
|
92
|
+
root,
|
|
93
|
+
manifest_path,
|
|
94
|
+
base=base,
|
|
95
|
+
head=head,
|
|
96
|
+
use_cache=False,
|
|
97
|
+
project=project,
|
|
98
|
+
)
|
|
99
|
+
durations.append(time.perf_counter() - started)
|
|
100
|
+
reports.append(report)
|
|
101
|
+
digests = {_digest(report) for report in reports}
|
|
102
|
+
if len(digests) != 1:
|
|
103
|
+
raise ConfigurationError("benchmark changed-check results varied across iterations")
|
|
104
|
+
ordered = sorted(durations)
|
|
105
|
+
p95 = ordered[max(0, math.ceil(len(ordered) * 0.95) - 1)]
|
|
106
|
+
peak_rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
107
|
+
peak_memory_mb = (
|
|
108
|
+
peak_rss / (1024 * 1024) if sys.platform == "darwin" else peak_rss / 1024
|
|
109
|
+
)
|
|
110
|
+
last = reports[-1]
|
|
111
|
+
return PerformanceReceipt(
|
|
112
|
+
last.base,
|
|
113
|
+
last.head,
|
|
114
|
+
project.as_posix(),
|
|
115
|
+
iterations,
|
|
116
|
+
round(p95, 6),
|
|
117
|
+
round(peak_memory_mb, 3),
|
|
118
|
+
time_budget_seconds,
|
|
119
|
+
memory_budget_mb,
|
|
120
|
+
next(iter(digests)),
|
|
121
|
+
len(last.required),
|
|
122
|
+
len(last.gaps),
|
|
123
|
+
)
|