sourcecode 5.0.0__py3-none-any.whl → 5.1.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.
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.0.0"
7
+ __version__ = "5.1.0"
sourcecode/cache_model.py CHANGED
@@ -87,7 +87,28 @@ class CommandCache:
87
87
  #: releases (spring-audit, 408 s in #13 against 2 351 s in #16) is itself the
88
88
  #: measurement C3-76 exists for.
89
89
  field_seconds: "Optional[float]" = None
90
+ #: **C3-100.** `field_blocked` is a *measurement outcome*, not a property of the
91
+ #: command: it says "this run did not finish on that build, at that size", and
92
+ #: like every other measurement here it belongs to the build named in
93
+ #: `field_measured_version` and expires with it. It was read as a property for
94
+ #: three releases — `repo-ir` and `verify-edit` carried a 4.10.4 outcome while
95
+ #: eval #23 ran them in 9,8 s and 76,6 s — which is worse than a stale number,
96
+ #: because a stale number is a measurement and *"the field could not run this"*
97
+ #: is an incapacity the product does not have. A blocked row without a build is
98
+ #: rejected by the battery for the same reason a figure without one is.
90
99
  field_blocked: bool = False
100
+ #: The cache state the field figure was taken in. Eval #22 measured a warm
101
+ #: machine; eval #23 purged every cache first (`cache clear --all`,
102
+ #: `context-clear`, `rm -rf parse-cache-v1`) and then ran the whole catalogue
103
+ #: in one round, so its first figures are cold and its later ones are neither —
104
+ #: the shared layers filled as the round went on. Published because *72,3 s
105
+ #: cold* and *72,3 s warm* are two different facts about the same command, and
106
+ #: the row that says which is the only thing standing between the two:
107
+ #: ``warm`` — the caches were warmed before the run
108
+ #: ``cold`` — every cache was purged before the run
109
+ #: ``mixed`` — inside a round that began purged: earlier commands had already
110
+ #: filled some of the shared layers this one reads
111
+ field_cache_state: str = "warm"
91
112
  #: **C3-97.** The build the field figure was taken on. An anchor without one is
92
113
  #: a measurement that can outlive its build and keep deciding: 4.18.0 deleted
93
114
  #: two catastrophic-backtracking patterns (C3-95, C3-96) and every figure taken
@@ -137,21 +158,20 @@ REFERENCE_MEASURED_VERSION = "3.2.2"
137
158
  #: What can be said honestly is the class, the two measured anchors, **the build
138
159
  #: each was measured on**, and how to run it.
139
160
  #:
140
- #: History of this constant, kept because it is the measurement C3-76 exists for:
161
+ #: History of the anchor, kept because it is the measurement C3-76 exists for:
141
162
  #: 408 s (#13) → 2 351 s (#16, C3-72) → 8,4 s (#22, C3-97). The middle figure was
142
163
  #: quoted as an operational anchor for four releases after the build that produced
143
164
  #: it, and told operators to detach commands that finish in seconds.
144
- FIELD_ANCHOR = (
145
- "field evaluation #22: spring-audit on 3 342 Java files took 8.4 s "
146
- "(Windows, pipx, warm cache) against 8.8 s on the 2 000-file reference"
147
- )
165
+ #:
166
+ #: ⚠ `FIELD_ANCHOR`, `FIELD_ANCHOR_SECONDS` and `FIELD_ANCHOR_MEASURED_VERSION`
167
+ #: are **derived from the `spring-audit` row**, below `COMMANDS`. They used to be
168
+ #: written out here as well, which made the anchor two facts kept in step by hand:
169
+ #: C3-97 refreshed both, and C3-100 is what it costs when the next refresh reaches
170
+ #: only one of the copies. One authority per fact — the table — and the headline
171
+ #: reads itself off it.
172
+ #:
173
+ #: The size of the field repository every `field_seconds` below was taken on.
148
174
  FIELD_ANCHOR_JAVA_FILES = 3342
149
- FIELD_ANCHOR_SECONDS = 8.4
150
-
151
- #: The build the field anchors above were taken on. Rows carrying a different
152
- #: `field_measured_version` are published with that build's name attached rather
153
- #: than as figures for this one (C3-97).
154
- FIELD_ANCHOR_MEASURED_VERSION = "4.18.0"
155
175
 
156
176
 
157
177
  #: Every layer keys on `cache.worktree_signature` — the exact tree state (C1-9).
@@ -217,9 +237,12 @@ COMMANDS: tuple[CommandCache, ...] = (
217
237
  CommandCache("ask (root)", ("snapshot", "ris", "parse"), "answer", True,
218
238
  "`--compact` is what a warm stores by default; `--agent` needs `cache warm --agent`. "
219
239
  "`--env-map`, `--depth N` and `--exclude` change the *analysis*, so they miss the "
220
- "warmed core and rescan — this is the 171 s the field measured after a 103 s warm.",
240
+ "warmed core and rescan — the 171 s the field measured after a 103 s warm on 4.10.4. "
241
+ "Eval #23 timed the same invocation at 72,3 s from a purged cache, and then paid "
242
+ "71,7 s again for `--agent` on the state it had just analysed (C3-103).",
221
243
  "--compact 13.3 s cold → 0.3 s warm (cold re-measured on 3.7.0: was 19.3 s, C3-6); "
222
- "--agent --full --env-map --depth 20 34.7 s → 33.9 s (no gain)", analysis_class="repo-wide", cold_seconds=13.3, warm_seconds=0.3, field_seconds=115.0, field_measured_version="4.10.4"),
244
+ "--agent --full --env-map --depth 20 34.7 s → 33.9 s (no gain)", analysis_class="repo-wide", cold_seconds=13.3, warm_seconds=0.3,
245
+ field_seconds=72.3, field_cache_state="cold", field_measured_version="5.0.0"),
223
246
  CommandCache("posture", ("cir", "parse"), "shared", False,
224
247
  "Resolves the conditional bean graph on every run, over the shared CIR a warm "
225
248
  "builds — the parse it used to repeat for itself. `--diff` compares two profile "
@@ -284,9 +307,11 @@ COMMANDS: tuple[CommandCache, ...] = (
284
307
  CommandCache("verify", (), "none", False, "Runs the contracts against a fresh reading.", analysis_class="core"),
285
308
  CommandCache("verify-edit", ("parse",), "shared", True,
286
309
  "Built for the edit loop: the parse cache is what keeps an unchanged file out of the "
287
- "next run. Its own second run is faster again.",
310
+ "next run. Its own second run is faster again. At field size that loop is not short "
311
+ "yet: eval #23 measured 76,6 s on a tree with no edits, because the HEAD side is "
312
+ "still built in a throwaway worktree instead of reusing the shared CIR (C3-102).",
288
313
  "14.6 s → 9.6 s → 5.6 s on repeat", analysis_class="repo-wide", cold_seconds=14.6, warm_seconds=9.6,
289
- field_blocked=True, field_measured_version="4.10.4"),
314
+ field_seconds=76.6, field_cache_state="mixed", field_measured_version="5.0.0"),
290
315
  CommandCache("review-pr", ("cir",), "none", False,
291
316
  "Diff-dependent, and it reuses the CIR only if one exists. On a small diff, loading "
292
317
  "the warmed CIR costs more than the work it saves.",
@@ -318,8 +343,12 @@ COMMANDS: tuple[CommandCache, ...] = (
318
343
  "9.8 s → 1.6 s", analysis_class="core", cold_seconds=9.8, warm_seconds=1.6,
319
344
  field_seconds=4.4, field_measured_version="4.18.0"),
320
345
  CommandCache("export", ("parse",), "shared", False, "", "8.8 s → 3.7 s", analysis_class="repo-wide", cold_seconds=8.8, warm_seconds=3.7),
321
- CommandCache("repo-ir", ("parse",), "shared", False, "", "5.1 s → 2.9 s", analysis_class="repo-wide", cold_seconds=5.1, warm_seconds=2.9,
322
- field_blocked=True, field_measured_version="4.10.4"),
346
+ CommandCache("repo-ir", ("parse",), "shared", False,
347
+ "Carried as *did not finish* from 4.10.4 until eval #23 ran it at field size in "
348
+ "9,8 s — a run that did not finish once is not a command that cannot finish "
349
+ "(C3-100, and the same correction `modernize` needed).",
350
+ "5.1 s → 2.9 s", analysis_class="repo-wide", cold_seconds=5.1, warm_seconds=2.9,
351
+ field_seconds=9.8, field_cache_state="mixed", field_measured_version="5.0.0"),
323
352
  CommandCache("validation", ("parse",), "shared", False, "", "11.6 s → 6.4 s", analysis_class="repo-wide", cold_seconds=11.6, warm_seconds=6.4,
324
353
  field_seconds=17.4, field_measured_version="4.18.0"),
325
354
  CommandCache("modernize", ("parse",), "shared", False,
@@ -333,8 +362,11 @@ COMMANDS: tuple[CommandCache, ...] = (
333
362
  "`no_ris` instead of a snapshot.",
334
363
  "0.2 s either way", analysis_class="core", cold_seconds=0.2, warm_seconds=0.2),
335
364
  CommandCache("trend", (), "none", False, "Reads stored baseline artifacts from disk; analyses no source, so no cache layer applies. Same command as `baseline trend`.", analysis_class="none"),
336
- CommandCache("baseline", ("parse",), "shared", False, "`capture`/`diff`/`trend` over architectural metrics.",
337
- "capture 8.7 s → 3.6 s", analysis_class="repo-wide", cold_seconds=8.7, warm_seconds=3.6, field_seconds=11.0, field_measured_version="4.10.4"),
365
+ CommandCache("baseline", ("parse",), "shared", False,
366
+ "`capture`/`diff`/`trend` over architectural metrics. The field figure is `capture`, "
367
+ "the subcommand that analyses; `trend` reads stored artifacts and has its own row.",
368
+ "capture 8.7 s → 3.6 s", analysis_class="repo-wide", cold_seconds=8.7, warm_seconds=3.6,
369
+ field_seconds=2.2, field_cache_state="mixed", field_measured_version="5.0.0"),
338
370
  CommandCache("retrieve", ("cir", "parse"), "shared", False,
339
371
  "Every query builds or reuses the shared CIR a warm builds.", analysis_class="core"),
340
372
  CommandCache("archetype", ("parse",), "shared", False, "", "9.3 s → 5.6 s", analysis_class="repo-wide", cold_seconds=9.3, warm_seconds=5.6),
@@ -355,6 +387,73 @@ _WARM_LABEL = {
355
387
  "none": "nothing",
356
388
  }
357
389
 
390
+ #: The cache states a field figure may declare. A row outside this set is a
391
+ #: measurement whose conditions nobody can read (C3-100).
392
+ FIELD_CACHE_STATES = ("warm", "cold", "mixed")
393
+
394
+
395
+ def _thousands(n: int) -> str:
396
+ return f"{n:,}".replace(",", " ")
397
+
398
+
399
+ def _row(command: str) -> "CommandCache":
400
+ for entry in COMMANDS:
401
+ if entry.command == command:
402
+ return entry
403
+ raise KeyError(command)
404
+
405
+
406
+ #: The headline anchor, read off the table rather than written twice (C3-100).
407
+ #: `spring-audit` is the command the whole series measured — 408 s (#13), 2 351 s
408
+ #: (#16), 8,4 s (#22) — so it is the row the front page quotes, and quoting it
409
+ #: means reading it, not copying it.
410
+ _FIELD_ANCHOR_ROW = _row("spring-audit")
411
+ FIELD_ANCHOR_SECONDS = _FIELD_ANCHOR_ROW.field_seconds
412
+ FIELD_ANCHOR_MEASURED_VERSION = _FIELD_ANCHOR_ROW.field_measured_version
413
+ FIELD_ANCHOR = (
414
+ f"field evaluation #22: spring-audit on {_thousands(FIELD_ANCHOR_JAVA_FILES)} "
415
+ f"Java files took {FIELD_ANCHOR_SECONDS:g} s (Windows, pipx, "
416
+ f"{_FIELD_ANCHOR_ROW.field_cache_state} cache) against "
417
+ f"{_FIELD_ANCHOR_ROW.cold_seconds:g} s on the "
418
+ f"{_thousands(REFERENCE_JAVA_FILES)}-file reference"
419
+ )
420
+
421
+
422
+ def _version_key(version: "Optional[str]") -> tuple:
423
+ """A comparable key for a release name, for ordering measurements only.
424
+
425
+ Not a distance — `reference_currency` refuses to invent one and this does not
426
+ either. It answers exactly one question: is this row's build older than the
427
+ newest build anything in the table was measured on (C3-100)? Anything
428
+ unparseable sorts oldest, which is the safe direction: it gets labelled.
429
+ """
430
+ if not version:
431
+ return ()
432
+ parts: list[int] = []
433
+ for chunk in version.split("."):
434
+ digits = "".join(c for c in chunk if c.isdigit())
435
+ if not digits:
436
+ break
437
+ parts.append(int(digits))
438
+ return tuple(parts)
439
+
440
+
441
+ def newest_field_measurement_version() -> "Optional[str]":
442
+ """The most recent build any field figure in the table was taken on.
443
+
444
+ The reference point for *"this row is older than the table it sits in"* — the
445
+ state C3-100 lived in for three releases, where a refresh reached five rows
446
+ and left four behind on a build four releases older, with nothing anywhere
447
+ saying so.
448
+ """
449
+ versions = [
450
+ row.field_measured_version
451
+ for row in COMMANDS
452
+ if (row.field_seconds is not None or row.field_blocked)
453
+ and row.field_measured_version
454
+ ]
455
+ return max(versions, key=_version_key) if versions else None
456
+
358
457
 
359
458
  def layer(layer_id: str) -> Layer:
360
459
  for entry in LAYERS:
@@ -459,10 +558,6 @@ def cost_sentence() -> str:
459
558
  )
460
559
 
461
560
 
462
- def _thousands(n: int) -> str:
463
- return f"{n:,}".replace(",", " ")
464
-
465
-
466
561
  def field_currency() -> dict:
467
562
  """The same statement as `reference_currency`, for the field anchors (C3-97).
468
563
 
@@ -473,11 +568,32 @@ def field_currency() -> dict:
473
568
  from sourcecode import __version__
474
569
 
475
570
  current = __version__
571
+ measured = [
572
+ row for row in COMMANDS
573
+ if row.field_seconds is not None or row.field_blocked
574
+ ]
476
575
  stale = sorted(
477
576
  row.command
478
- for row in COMMANDS
479
- if (row.field_seconds is not None or row.field_blocked)
480
- and row.field_measured_version not in (None, current)
577
+ for row in measured
578
+ if row.field_measured_version not in (None, current)
579
+ )
580
+ # C3-100: the other staleness, and the one that hid for three releases — not
581
+ # "older than the build you are running" (every row is, the day after a
582
+ # release) but "older than the newest measurement in this same table", which
583
+ # is a refresh that reached some rows and not others. Named here so the gap is
584
+ # readable without diffing the rows by hand.
585
+ newest = newest_field_measurement_version()
586
+ behind = sorted(
587
+ row.command
588
+ for row in measured
589
+ if _version_key(row.field_measured_version) < _version_key(newest)
590
+ )
591
+ lagging = (
592
+ f" {len(behind)} of the {len(measured)} field figures were taken before "
593
+ f"the newest one in this table ({newest}): "
594
+ f"{', '.join(behind)}."
595
+ if behind
596
+ else ""
481
597
  )
482
598
  return {
483
599
  "anchor": FIELD_ANCHOR,
@@ -485,12 +601,114 @@ def field_currency() -> dict:
485
601
  "running_version": current,
486
602
  "current": FIELD_ANCHOR_MEASURED_VERSION == current,
487
603
  "rows_measured_on_an_earlier_build": stale,
604
+ "newest_field_measurement_version": newest,
605
+ "rows_behind_the_newest_field_measurement": behind,
606
+ "statement": (
607
+ (
608
+ f"The field anchors were taken on {FIELD_ANCHOR_MEASURED_VERSION}; you "
609
+ f"are running {current}. Each row states the build its field figure "
610
+ f"belongs to, and no figure is projected onto this one."
611
+ if FIELD_ANCHOR_MEASURED_VERSION != current
612
+ else f"The field anchors were taken on {current}, the build you are running."
613
+ )
614
+ + lagging
615
+ ),
616
+ }
617
+
618
+
619
+ def gate_anchor(command: str) -> "Optional[dict]":
620
+ """What the release gate measured for *command*, or ``None`` (C3-109).
621
+
622
+ The third figure in this module, and the only one this repository can refresh
623
+ by itself. `REFERENCE_*` is a 3.2.2 measurement on a 2 000-file tree;
624
+ `field_seconds` is the field's, at 3 342 files, on their machine and their
625
+ build. Both age, and neither can be re-measured from here — which is exactly
626
+ how B17 reopened one release after it was closed: the labelling worked and
627
+ the number still expired.
628
+
629
+ The gate measures a 2 985-file repository under a declared host class every
630
+ release and commits its cells, so this figure carries the build that produced
631
+ it and can be brought current by running the gate. It never overrides a field
632
+ anchor — a measurement at field scale outranks one at gate scale — it is
633
+ published *beside* it, so an anchor from an older build is no longer the only
634
+ number a reader has.
635
+ """
636
+ try:
637
+ from sourcecode.gate_anchors import (
638
+ GATE_ANCHORS,
639
+ GATE_HOST_CLASS,
640
+ GATE_JAVA_FILES,
641
+ GATE_MEASURED_VERSION,
642
+ GATE_REPOSITORY,
643
+ )
644
+ except Exception:
645
+ return None
646
+ cell = GATE_ANCHORS.get(command)
647
+ if not cell:
648
+ return None
649
+ return {
650
+ "seconds": cell["seconds"],
651
+ "cache_state": cell["mode"],
652
+ "runs": cell["runs"],
653
+ "measured_on_version": GATE_MEASURED_VERSION,
654
+ "repository": GATE_REPOSITORY,
655
+ "java_files": GATE_JAVA_FILES,
656
+ "host_class": GATE_HOST_CLASS,
657
+ }
658
+
659
+
660
+ def gate_currency() -> dict:
661
+ """Whether the in-house gate figures describe the build in hand (C3-109).
662
+
663
+ Same shape and same purpose as `field_currency`, for the anchor set that can
664
+ actually be refreshed. When this says `current: false`, the remedy is a
665
+ command rather than a hope: re-run the gate on the tag and commit the cells.
666
+ """
667
+ from sourcecode import __version__
668
+
669
+ current = __version__
670
+ try:
671
+ from sourcecode.gate_anchors import (
672
+ GATE_ANCHORS,
673
+ GATE_CAPTURED_AT,
674
+ GATE_HOST_CLASS,
675
+ GATE_JAVA_FILES,
676
+ GATE_MEASURED_VERSION,
677
+ GATE_REPOSITORY,
678
+ )
679
+ except Exception:
680
+ return {
681
+ "available": False,
682
+ "running_version": current,
683
+ "statement": (
684
+ "This build carries no gate measurement of its own. The gate "
685
+ "publishes one per release (docs/perf/REGRESSION-GATE.md); until "
686
+ "it runs, the field anchors are the only measured figures here."
687
+ ),
688
+ }
689
+ return {
690
+ "available": True,
691
+ "measured_on_version": GATE_MEASURED_VERSION,
692
+ "running_version": current,
693
+ "current": GATE_MEASURED_VERSION == current,
694
+ "captured_at": GATE_CAPTURED_AT,
695
+ "repository": GATE_REPOSITORY,
696
+ "java_files": GATE_JAVA_FILES,
697
+ "host_class": GATE_HOST_CLASS,
698
+ "commands": sorted(GATE_ANCHORS),
488
699
  "statement": (
489
- f"The field anchors were taken on {FIELD_ANCHOR_MEASURED_VERSION}; you "
490
- f"are running {current}. Each row states the build its field figure "
491
- f"belongs to, and no figure is projected onto this one."
492
- if FIELD_ANCHOR_MEASURED_VERSION != current
493
- else f"The field anchors were taken on {current}, the build you are running."
700
+ f"The release gate measured {len(GATE_ANCHORS)} commands on "
701
+ f"{GATE_REPOSITORY} ({GATE_JAVA_FILES} Java files, {GATE_HOST_CLASS}) "
702
+ + (
703
+ f"on {current}, the build you are running."
704
+ if GATE_MEASURED_VERSION == current
705
+ else (
706
+ f"on {GATE_MEASURED_VERSION}; you are running {current}, which "
707
+ f"the gate has not measured. Re-run it on this build "
708
+ f"(docs/perf/REGRESSION-GATE.md) — unlike the field anchors, "
709
+ f"this is a figure this project can refresh."
710
+ )
711
+ )
494
712
  ),
495
713
  }
496
714
 
@@ -559,6 +777,11 @@ def as_dict(here: "Optional[Conditioning]" = None) -> dict:
559
777
  # measured 2 351 s and 8,4 s on two of our own builds (C4-19, C3-97).
560
778
  "field_anchor": FIELD_ANCHOR,
561
779
  "field_currency": field_currency(),
780
+ # C3-109: and the figure this project can refresh by itself. A field
781
+ # anchor belongs to the build the field ran; this one belongs to the
782
+ # build the gate ran, which is a release away at worst instead of
783
+ # unreachable. Published beside, never instead.
784
+ "gate_currency": gate_currency(),
562
785
  "commands": [
563
786
  {
564
787
  "command": cmd.command,
@@ -590,10 +813,17 @@ def as_dict(here: "Optional[Conditioning]" = None) -> dict:
590
813
  "seconds": cmd.field_seconds,
591
814
  "did_not_finish": cmd.field_blocked,
592
815
  "java_files": FIELD_ANCHOR_JAVA_FILES,
816
+ "cache_state": cmd.field_cache_state,
593
817
  "measured_on_version": cmd.field_measured_version,
594
818
  "note": field_anchor_note(cmd),
595
819
  }
596
820
  ),
821
+ # C3-109: the in-house measurement of this command, on the build
822
+ # the gate last ran. Absent for commands the gate does not cover —
823
+ # its population is declared in `sourcecode.perf`, with the
824
+ # exclusions and their reasons, and an axis nobody measured is
825
+ # never a number here.
826
+ "gate_measurement": gate_anchor(cmd.command),
597
827
  }
598
828
  for cmd in COMMANDS
599
829
  ],
@@ -705,6 +935,7 @@ def render_text(here: "Optional[Conditioning]" = None) -> str:
705
935
  lines.append(f" Timings: {REFERENCE_REPOSITORY}, each command measured in isolation.")
706
936
  lines.append(f" {reference_currency()['statement']}")
707
937
  lines.append(f" {field_currency()['statement']}")
938
+ lines.append(f" {gate_currency()['statement']}")
708
939
  width = max(len(c.command) for c in COMMANDS)
709
940
  for cmd in COMMANDS:
710
941
  repeat = "repeat cached" if cmd.repeat else "recomputes"
@@ -713,17 +944,30 @@ def render_text(here: "Optional[Conditioning]" = None) -> str:
713
944
  lines.append(f" {' ' * width} measured: {cmd.measured}")
714
945
  if cmd.field_seconds is not None or cmd.field_blocked:
715
946
  # C3-97: never the seconds without the build they were taken on.
947
+ # C3-100: nor without the cache state they were taken in.
716
948
  note = field_anchor_note(cmd)
717
949
  shown = (
718
950
  "did not finish in an interactive session"
719
951
  if cmd.field_seconds is None
720
952
  else f"{cmd.field_seconds:g} s"
721
953
  )
722
- suffix = f" ({note})" if note else ""
954
+ qualifiers = [f"{cmd.field_cache_state} cache"] + ([note] if note else [])
955
+ suffix = f" ({', '.join(qualifiers)})"
723
956
  lines.append(
724
957
  f" {' ' * width} field: {shown} at "
725
958
  f"{_thousands(FIELD_ANCHOR_JAVA_FILES)} Java files{suffix}"
726
959
  )
960
+ _gate = gate_anchor(cmd.command)
961
+ if _gate:
962
+ # C3-109: the in-house figure, beside the field's rather than instead
963
+ # of it — one is at field scale on another build, one is on a build
964
+ # this project can bring current.
965
+ lines.append(
966
+ f" {' ' * width} gate: {_gate['seconds']:g} s at "
967
+ f"{_thousands(_gate['java_files'])} Java files "
968
+ f"({_gate['cache_state']} cache, on {_gate['measured_on_version']}, "
969
+ f"{_gate['host_class']})"
970
+ )
727
971
  if cmd.note:
728
972
  lines.append(f" {' ' * width} {cmd.note}")
729
973
  if here is not None and cmd.command in here.per_command: