Skip to content

Commit f415d75

Browse files
docs(128): post-delivery quality review (#133)
* refactor(128): simplify code per /simplify review Production (scripts/extract-infographic-data.py): - Move datetime import to module level - Add module-level constants: _QUALIFYING_SEVERITIES, _SEVERITY_FIELD_PREFERENCE, _TIER_SOURCE_LABEL - Promote duplicated severity extractor closures to _canonical_severity - Promote _extract_composite_score and _extract_description to module level - Remove dead parameter threats_content from _build_executive_architecture_payload - Delete _SEV_RANK local dict, use imported _SEVERITY_ORDINAL directly - Delete task-reference and step-enumeration comments (F-128, T011, --- Step 1 ---) - Trim verbose helper docstrings to single-line PEP 257 summaries Tests (5 files): - Remove T0NN task references, F-128 banners, and point-in-time gate notes - Strip # Step 1..6 narrative comments in test bodies - Trim oversized module docstrings to intent-only - Apply @pytest.mark.slow to typst-compiling tests - Consolidate 5 identical subprocess runs in test_existing_image_flags_unchanged into a module-scoped agentic_app_report_typst fixture (5x subprocess to 1x) Infrastructure (pyproject.toml): - Register slow pytest marker so `pytest -m "not slow"` enables fast iteration Verification: - Full suite: 39 tests pass in ~20s - Fast suite: pytest -m "not slow" runs 32 tests in ~5s (4x speedup) - Production smoke test against agentic-app sample produces identical output Co-Authored-By: Claude <noreply@anthropic.com> * docs(128): add docstrings per docs-lint Add single-line summaries to two promoted helpers (_extract_composite_score, _extract_description) that were extracted from closures during the simplify refactor and lacked documentation. Co-Authored-By: Claude <noreply@anthropic.com> * docs(128): update CHANGELOG Append Feature 128 (Executive Threat Architecture Infographic) entry to ## [Unreleased] / ### Added. Describes the new template, the PDF placement rationale, the `exec` alias, `all` expansion inclusion, the pytest bootstrap, and the backward-compatibility guarantee via committed .baseline PDFs. Co-Authored-By: Claude <noreply@anthropic.com> * docs(128): review KB entries Add KB-027 capturing the magnitude of the post-delivery simplify pass on F-128: 31% reduction in changed-line count (660 deletions, 216 insertions against 2,139 new lines), dominated by narrative-comment sprawl in the generated test modules. Recommends treating /aod.document simplify as a mandatory cleanup gate rather than optional polish. Bump entry count 26 to 27. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 838a673 commit f415d75

9 files changed

+243
-661
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525

2626
### Added
2727

28+
- **Executive Threat Architecture Infographic** (Feature 128) — New `/tachi.infographic --template executive-architecture` (alias: `exec`) generates a layered architecture diagram with Critical/High finding callouts, designed for CISO-level readers. In the compiled PDF security report the new page lands immediately after the Executive Summary (pages 2–3 area) so executives see the visual threat narrative within their first-glance window. Included in the `all` shorthand expansion alongside the existing five templates. Backward compatible — example PDFs without a generated `threat-executive-architecture.jpg` render byte-identical to the pre-F-128 baseline. Ships with tachi's first project-level pytest harness (`pyproject.toml`, `requirements-dev.txt`, `tests/`) and five committed `.baseline` PDFs guarding backward compatibility against silent regressions.
2829
- **Architecture Lifecycle Command** (Feature 120) — `/tachi.architecture` now tracks versions with YAML frontmatter (version, date, description, SHA-256 checksum), archives previous versions to `.archive/v{N}/`, and supports guided updates through change categories. `/tachi.threat-model` automatically snapshots the architecture file into each timestamped output folder for full traceability. Backward compatible with existing architecture files.
2930

3031
### Changed

docs/INSTITUTIONAL_KNOWLEDGE.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**Created**: {{PROJECT_START_DATE}}
66
**Last Updated**: 2026-04-10
77

8-
**Entry Count**: 26 / 20 (KB System Upgrade triggers at 20 — schedule review)
8+
**Entry Count**: 27 / 20 (KB System Upgrade triggers at 20 — schedule review)
99
**Last Review**: 2026-03-30
1010
**Status**: ✅ Manual mode (file-based)
1111

@@ -548,6 +548,29 @@ Captured during structured delivery retrospective. Smooth sailing — everything
548548

549549
---
550550

551+
### KB-027: Post-Delivery Simplify Pass Commonly Finds Narrative-Comment Sprawl in Generated Tests
552+
553+
**Date**: 2026-04-10
554+
**Category**: Process / Tooling
555+
**Source**: Feature 128 /aod.document simplify review
556+
**Severity**: Informational
557+
558+
**Problem**: Feature 128 shipped 2,139 lines of new Python (two extraction-script additions plus six test modules bootstrapping the pytest harness). The post-delivery `/aod.document` simplify review deleted 660 lines and rewrote 216 — a 31% reduction in changed-line count — with almost all of that coming from the test modules. The cleanup was dominated by three repeating anti-patterns: (1) module-level docstrings narrating the task-gating story ("Note on the test-first gate (T007)"), (2) per-function step-enumeration comments (`# Step 1:`, `# Step 2:`), and (3) inline references to task IDs (T023, T024, T029, T033) and review-artifact paths that will not make sense after the feature branch is merged.
559+
560+
**Root Cause**: Test generation during feature build captures the implementer's working-memory narrative — which tasks are gating which tests, which steps run in which order, which review findings shaped which assertion — and emits it as comments and docstrings. This information is useful *during* implementation (while the author is still holding the whole plan in their head) but becomes stale the moment the feature merges. The comments then linger as archaeological debris in the test modules, reducing signal density and creating misleading context for future maintainers.
561+
562+
**Solution**: Keep the `/aod.document` simplify step in the lifecycle as a mandatory cleanup gate, not an optional polish. The `/simplify` skill's three-agent review (reuse, quality, efficiency) reliably catches these patterns when pointed at the changed files. For F-128 the pass also caught a dead parameter, two duplicated closures, a hand-rolled severity dict that duplicated an imported constant, and 5× redundant subprocess runs in a parametrized test that collapsed cleanly into a module-scoped fixture.
563+
564+
**Result**: Production code became smaller, tighter, and free of task-reference artifacts. The pytest harness gained a `slow` marker registration enabling `pytest -m "not slow"` to run in ~5 seconds instead of ~20 (4× faster local iteration). Full suite still passes (39/39) and the smoke test against the agentic-app sample produces byte-identical output to the pre-refactor run.
565+
566+
**When to Apply**: Every feature that ships new test modules or non-trivial Python helpers. Expect a post-delivery simplify pass to remove 20–30% of changed-line count through comment and duplication cleanup alone. Allocate time for the `/aod.document` stage rather than treating it as optional.
567+
568+
**Tags**: #retrospective #process #simplify #test-quality #comments #tooling #aod-document
569+
570+
**Quality Score**: 8/10
571+
572+
---
573+
551574
## Bug Fixes
552575

553576
*No entries yet. Use `/kb-create` to add the first bug fix.*

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ testpaths = ["tests"]
1313
python_files = ["test_*.py"]
1414
python_functions = ["test_*"]
1515
addopts = "-ra --strict-markers"
16+
markers = [
17+
"slow: tests that invoke typst compile or other slow external subprocesses",
18+
]

scripts/extract-infographic-data.py

Lines changed: 55 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import math
2121
import re
2222
import sys
23+
from datetime import datetime, timezone
2324
from pathlib import Path
2425

2526
sys.path.insert(0, str(Path(__file__).resolve().parent))
@@ -62,9 +63,20 @@
6263
"Note": "#6B7280",
6364
}
6465

65-
# Alias for backward compatibility within this script
6666
_SEVERITY_ORDINAL = SEVERITY_ORDINAL
6767

68+
_QUALIFYING_SEVERITIES = frozenset({"Critical", "High"})
69+
_SEVERITY_FIELD_PREFERENCE = ("severity", "residual_severity", "risk_level")
70+
_TIER_SOURCE_LABEL = {1: "compensating-controls", 2: "risk-scores", 3: "threats"}
71+
72+
73+
def _canonical_severity(finding):
74+
for key in _SEVERITY_FIELD_PREFERENCE:
75+
val = finding.get(key)
76+
if val:
77+
return str(val).strip().title()
78+
return ""
79+
6880

6981
# =============================================================================
7082
# T009: Largest Remainder Method
@@ -655,23 +667,8 @@ def _compute_trust_zones(scope_data):
655667
return zones
656668

657669

658-
# =============================================================================
659-
# F-128: Executive Architecture Helpers
660-
# =============================================================================
661-
662670
def _normalize_component_name(name):
663-
"""Normalize a component name for case-insensitive, punctuation-insensitive matching.
664-
665-
Strips whitespace, lowercases, and removes hyphens, underscores, and spaces so that
666-
"API Gateway", "api-gateway", "api_gateway", and "apigateway" all collapse to
667-
"apigateway". Used as the matching key for layer-finding association.
668-
669-
Args:
670-
name: Component name string (may be None).
671-
672-
Returns:
673-
Normalized string. Empty string if input is None or empty.
674-
"""
671+
"""Collapse component names so "API Gateway", "api-gateway", "api_gateway" all match."""
675672
if not name:
676673
return ""
677674
return (
@@ -684,24 +681,11 @@ def _normalize_component_name(name):
684681

685682

686683
def _compute_dfd_type_layers(scope_data):
687-
"""Group Section 1 components by DFD type for the executive-architecture fallback.
688-
689-
Used when no trust zones are defined in Section 2. Each unique component `type`
690-
becomes an ArchitecturalLayer, with components listed alphabetically within the
691-
layer. Layers are sorted alphabetically by type name.
692-
693-
Args:
694-
scope_data: Dict from parse_scope_data().
695-
696-
Returns:
697-
List of layer dicts (name, position, components, component_count, source_kind)
698-
or None if scope_data has no components at all.
699-
"""
684+
"""Fallback layer derivation: group Section 1 components by DFD type when no trust zones exist."""
700685
components = scope_data.get("components", [])
701686
if not components:
702687
return None
703688

704-
# Group component names by type
705689
by_type = {}
706690
for comp in components:
707691
ctype = (comp.get("type") or "").strip()
@@ -713,7 +697,6 @@ def _compute_dfd_type_layers(scope_data):
713697
if not by_type:
714698
return None
715699

716-
# Build layer dicts sorted alphabetically by type name
717700
layers = []
718701
for position, ctype in enumerate(sorted(by_type.keys())):
719702
comp_names = sorted(by_type[ctype])
@@ -730,144 +713,96 @@ def _compute_dfd_type_layers(scope_data):
730713
return layers if layers else None
731714

732715

733-
def _select_critical_high_callouts(findings, layers):
734-
"""Filter Critical/High findings and select one callout per layer.
716+
def _extract_composite_score(finding):
717+
"""Prefer composite_score, fall back to residual_score; return float or None."""
718+
for key in ("composite_score", "residual_score"):
719+
raw = finding.get(key)
720+
if raw is None or raw == "":
721+
continue
722+
try:
723+
return float(raw)
724+
except (TypeError, ValueError):
725+
continue
726+
return None
735727

736-
For each layer:
737-
1. Filter findings whose normalized component is in the layer's normalized
738-
component set, AND whose severity is Critical or High.
739-
2. Sort by:
740-
a. severity descending (Critical before High)
741-
b. composite_score descending (None treated as 0.0)
742-
c. finding_id ascending (lexicographic tie-break)
743-
3. Select the first element as the layer's callout.
744728

745-
Orphaned findings (those whose component matches no layer) are dropped.
729+
def _extract_description(finding):
730+
"""Prefer description, fall back to threat, fall back to mitigation."""
731+
for key in ("description", "threat", "mitigation"):
732+
val = finding.get(key)
733+
if val:
734+
return str(val).strip()
735+
return ""
746736

747-
Args:
748-
findings: List of finding dicts from parse_*_findings(). Expected keys:
749-
`id`, `component`, `threat` (or `description`), `severity` or `risk_level`,
750-
optional `composite_score`, `residual_score`.
751-
layers: List of layer dicts (name, components, ...).
752737

753-
Returns:
754-
List of callout dicts matching the ThreatCallout schema in data-model.md.
755-
One callout maximum per layer; layers with no qualifying findings yield no callout.
738+
def _select_critical_high_callouts(findings, layers):
739+
"""Select one Critical/High callout per layer.
740+
741+
Sort order per layer: severity desc, composite score desc, finding id asc.
742+
Findings whose component matches no layer are dropped.
756743
"""
757-
# Build a lookup: normalized component name -> (layer_name, layer_index)
758744
comp_to_layer = {}
759745
for idx, layer in enumerate(layers):
760746
for comp in layer.get("components", []):
761747
key = _normalize_component_name(comp)
762748
if key and key not in comp_to_layer:
763749
comp_to_layer[key] = (layer["name"], idx)
764750

765-
# Severity ordering for sort: Critical=2, High=1 (others excluded by filter)
766-
_SEV_RANK = {"critical": 2, "high": 1}
767-
768-
def _extract_severity(finding):
769-
"""Return canonical severity label ('Critical', 'High') or empty string."""
770-
# Tier 2/1 use `severity` / `residual_severity`; Tier 3 uses `risk_level`.
771-
for key in ("severity", "residual_severity", "risk_level"):
772-
val = finding.get(key)
773-
if val:
774-
return str(val).strip().title()
775-
return ""
776-
777-
def _extract_composite_score(finding):
778-
"""Return composite score as float or None."""
779-
for key in ("composite_score", "residual_score"):
780-
raw = finding.get(key)
781-
if raw is None or raw == "":
782-
continue
783-
try:
784-
return float(raw)
785-
except (TypeError, ValueError):
786-
continue
787-
return None
788-
789-
def _extract_description(finding):
790-
"""Return the narrative description for the callout."""
791-
for key in ("description", "threat", "mitigation"):
792-
val = finding.get(key)
793-
if val:
794-
return str(val).strip()
795-
return ""
796-
797-
# Group qualifying findings by layer
798751
per_layer = {layer["name"]: [] for layer in layers}
799752
for finding in findings:
800-
severity = _extract_severity(finding)
801-
if severity not in ("Critical", "High"):
753+
severity = _canonical_severity(finding)
754+
if severity not in _QUALIFYING_SEVERITIES:
802755
continue
803756
component = finding.get("component") or ""
804757
key = _normalize_component_name(component)
805758
if not key or key not in comp_to_layer:
806-
# Orphaned finding — drop per architect L-1 observation
807759
continue
808760
layer_name, _idx = comp_to_layer[key]
809761
per_layer[layer_name].append(finding)
810762

811-
# Select the top finding per layer using the tie-break rule
812763
callouts = []
813764
for layer in layers:
814765
layer_findings = per_layer.get(layer["name"], [])
815766
if not layer_findings:
816767
continue
817768

818769
def _sort_key(f):
819-
severity = _extract_severity(f)
770+
severity = _canonical_severity(f)
820771
composite = _extract_composite_score(f)
821772
return (
822-
-_SEV_RANK.get(severity.lower(), 0), # severity desc
823-
-(composite if composite is not None else 0.0), # score desc
824-
f.get("id", ""), # id asc
773+
-_SEVERITY_ORDINAL.get(severity, 0),
774+
-(composite if composite is not None else 0.0),
775+
f.get("id", ""),
825776
)
826777

827778
layer_findings.sort(key=_sort_key)
828779
top = layer_findings[0]
829-
composite = _extract_composite_score(top)
830780
callouts.append({
831781
"layer_name": layer["name"],
832782
"finding_id": top.get("id", ""),
833-
"severity": _extract_severity(top),
783+
"severity": _canonical_severity(top),
834784
"raw_description": _extract_description(top),
835-
"composite_score": composite,
785+
"composite_score": _extract_composite_score(top),
836786
"affected_component": (top.get("component") or None),
837787
})
838788

839789
return callouts
840790

841791

842-
def _build_executive_architecture_payload(threats_content, tier, findings, scope_data, source_file):
843-
"""Assemble the ExecutiveArchitecturePayload dict for the executive-architecture template.
844-
845-
Handles layer derivation (trust zones → DFD type fallback), Critical/High filtering,
846-
per-layer callout selection, metadata assembly, and the skip_image flag.
847-
848-
Args:
849-
threats_content: Full text of threats.md (unused; reserved for future use).
850-
tier: Tier source label (one of "compensating-controls", "risk-scores", "threats").
851-
findings: List of finding dicts from extract_severity().
852-
scope_data: Dict from parse_scope_data().
853-
source_file: Path-like to the source threats.md file.
792+
def _build_executive_architecture_payload(tier, findings, scope_data, source_file):
793+
"""Assemble the ExecutiveArchitecturePayload.
854794
855-
Returns:
856-
ExecutiveArchitecturePayload dict, OR a dict {"error": "no_scope_data"} when
857-
neither trust zones nor DFD-type-grouped components can be derived from
858-
scope_data. The caller translates the error dict into exit code 2.
795+
Derives layers from trust zones (preferred) or falls back to grouping components by
796+
DFD type. Returns ``{"error": "no_scope_data"}`` when neither source yields layers;
797+
the caller translates that into exit code 2.
859798
"""
860-
from datetime import datetime, timezone
861-
862-
# --- Step 1: Derive architectural layers ---
863799
trust_zones = _compute_trust_zones(scope_data)
864800
fallback_used = False
865801
layers = []
866802

867803
if trust_zones:
868-
# Trust zones returned sorted by trust level ASCENDING (trusted first).
869-
# The executive-architecture view wants UNTRUSTED at top (position 0 = most
870-
# exposed), so we reverse the order.
804+
# Trust zones arrive sorted trusted-first; the executive view places the most
805+
# exposed layer at position 0, so reverse into untrusted-first order.
871806
ordered = list(reversed(trust_zones))
872807
for position, zone in enumerate(ordered):
873808
comp_names = list(zone.get("components") or [])
@@ -886,35 +821,24 @@ def _build_executive_architecture_payload(threats_content, tier, findings, scope
886821
fallback_used = True
887822
layers = dfd_layers
888823

889-
# Drop any layers that ended up with zero components
890824
layers = [layer for layer in layers if layer.get("component_count", 0) > 0]
891825

892826
if not layers:
893827
return {"error": "no_scope_data"}
894828

895-
# --- Step 2: Filter findings to Critical/High ---
896-
def _severity_of(f):
897-
for key in ("severity", "residual_severity", "risk_level"):
898-
val = f.get(key)
899-
if val:
900-
return str(val).strip().title()
901-
return ""
902-
903829
critical_count = 0
904830
high_count = 0
905831
for f in findings:
906-
sev = _severity_of(f)
832+
sev = _canonical_severity(f)
907833
if sev == "Critical":
908834
critical_count += 1
909835
elif sev == "High":
910836
high_count += 1
911837
total_qualifying = critical_count + high_count
912838

913-
# --- Step 3: Per-layer callout selection ---
914839
callouts = _select_critical_high_callouts(findings, layers)
915840
total_after_layer_dedup = len(callouts)
916841

917-
# --- Step 4: Metadata assembly ---
918842
skip_image = (total_qualifying == 0)
919843
generation_timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
920844
metadata = {
@@ -1529,15 +1453,9 @@ def main():
15291453
# Extract severity, findings, and cc_data
15301454
severity, findings, cc_data = extract_severity(tier, threats_content, rs_content, cc_content)
15311455

1532-
# F-128 T011: executive-architecture branch (early exit — unique payload shape)
15331456
if args.template == "executive-architecture":
1534-
# Map integer tier to string tier_source label per data-model.md PayloadMetadata
1535-
_TIER_SOURCE_LABEL = {1: "compensating-controls", 2: "risk-scores", 3: "threats"}
1536-
tier_source = _TIER_SOURCE_LABEL.get(tier, "threats")
1537-
15381457
payload = _build_executive_architecture_payload(
1539-
threats_content=threats_content,
1540-
tier=tier_source,
1458+
tier=_TIER_SOURCE_LABEL.get(tier, "threats"),
15411459
findings=findings,
15421460
scope_data=scope,
15431461
source_file=(target_dir / "threats.md"),
@@ -1564,7 +1482,6 @@ def main():
15641482
file=sys.stderr,
15651483
)
15661484
sys.exit(EXIT_SUCCESS)
1567-
# End F-128 T011 early exit
15681485

15691486
# Compute severity percentages
15701487
severity_distribution = compute_severity_percentages(severity)

0 commit comments

Comments
 (0)