Improve Jeannie explain readability

This commit is contained in:
juvdiaz 2026-06-29 20:37:03 -06:00
parent 86e061dd72
commit d0004d99f8
2 changed files with 70 additions and 30 deletions

View File

@ -2,6 +2,7 @@ pattern meaning diagnose_commands jeannie_fix_commands other_fix_commands risk a
unsupported template variable Generated docs found a template placeholder that render-docs does not support. ./jeannie validate none Edit README.md.tmpl or scripts/render-docs low no Usually a docs/template mismatch rather than an infrastructure issue. unsupported template variable Generated docs found a template placeholder that render-docs does not support. ./jeannie validate none Edit README.md.tmpl or scripts/render-docs low no Usually a docs/template mismatch rather than an infrastructure issue.
generated README.md is stale README.md does not match README.md.tmpl rendering. ./jeannie validate none scripts/render-docs low candidate A future docs-render Jeannie command could safely wrap this. generated README.md is stale README.md does not match README.md.tmpl rendering. ./jeannie validate none scripts/render-docs low candidate A future docs-render Jeannie command could safely wrap this.
missing_resource_policy Workloads are missing resource requests or limits, so capacity planning is unreliable. ./jeannie resource-budget;./jeannie capacity none Edit app manifests or Helm values medium no Add requests first, then limits for workloads you own. missing_resource_policy Workloads are missing resource requests or limits, so capacity planning is unreliable. ./jeannie resource-budget;./jeannie capacity none Edit app manifests or Helm values medium no Add requests first, then limits for workloads you own.
pods missing requests/limits Some Kubernetes pods are missing resource requests or limits, so the scheduler and capacity reports cannot fully trust workload sizing. ./jeannie resource-budget --details;./jeannie capacity none Add requests/limits in the app manifests, Helm values, or platform chart values medium no Yes, this is fixed from code/config by adding resources to workloads you own; avoid auto-generating limits blindly.
missing requests One or more containers do not declare CPU or memory requests. ./jeannie resource-budget --details;./jeannie capacity none Edit app manifests or Helm values medium no Start with application namespaces before system namespaces. missing requests One or more containers do not declare CPU or memory requests. ./jeannie resource-budget --details;./jeannie capacity none Edit app manifests or Helm values medium no Start with application namespaces before system namespaces.
missing limits One or more containers do not declare CPU or memory limits. ./jeannie resource-budget --details;./jeannie capacity none Edit app manifests or Helm values medium no Limits are useful but can cause throttling; set them deliberately. missing limits One or more containers do not declare CPU or memory limits. ./jeannie resource-budget --details;./jeannie capacity none Edit app manifests or Helm values medium no Limits are useful but can cause throttling; set them deliberately.
resource policy Capacity or resource policy needs attention. ./jeannie resource-budget;./jeannie capacity none Edit infra/resource-budgets.yml or app manifests medium no Do not hide pressure by only changing the budget file. resource policy Capacity or resource policy needs attention. ./jeannie resource-budget;./jeannie capacity none Edit infra/resource-budgets.yml or app manifests medium no Do not hide pressure by only changing the budget file.

1 pattern meaning diagnose_commands jeannie_fix_commands other_fix_commands risk auto_fix notes
2 unsupported template variable Generated docs found a template placeholder that render-docs does not support. ./jeannie validate none Edit README.md.tmpl or scripts/render-docs low no Usually a docs/template mismatch rather than an infrastructure issue.
3 generated README.md is stale README.md does not match README.md.tmpl rendering. ./jeannie validate none scripts/render-docs low candidate A future docs-render Jeannie command could safely wrap this.
4 missing_resource_policy Workloads are missing resource requests or limits, so capacity planning is unreliable. ./jeannie resource-budget;./jeannie capacity none Edit app manifests or Helm values medium no Add requests first, then limits for workloads you own.
5 pods missing requests/limits Some Kubernetes pods are missing resource requests or limits, so the scheduler and capacity reports cannot fully trust workload sizing. ./jeannie resource-budget --details;./jeannie capacity none Add requests/limits in the app manifests, Helm values, or platform chart values medium no Yes, this is fixed from code/config by adding resources to workloads you own; avoid auto-generating limits blindly.
6 missing requests One or more containers do not declare CPU or memory requests. ./jeannie resource-budget --details;./jeannie capacity none Edit app manifests or Helm values medium no Start with application namespaces before system namespaces.
7 missing limits One or more containers do not declare CPU or memory limits. ./jeannie resource-budget --details;./jeannie capacity none Edit app manifests or Helm values medium no Limits are useful but can cause throttling; set them deliberately.
8 resource policy Capacity or resource policy needs attention. ./jeannie resource-budget;./jeannie capacity none Edit infra/resource-budgets.yml or app manifests medium no Do not hide pressure by only changing the budget file.

View File

@ -105,8 +105,12 @@ def read_tsv(path, key):
with open(path, encoding="utf-8", newline="") as handle: with open(path, encoding="utf-8", newline="") as handle:
return {row[key].lower(): row for row in csv.DictReader(handle, delimiter="\t")} return {row[key].lower(): row for row in csv.DictReader(handle, delimiter="\t")}
def read_tsv_rows(path):
with open(path, encoding="utf-8", newline="") as handle:
return list(csv.DictReader(handle, delimiter="\t"))
scorecard_info = read_tsv(scorecard_map, "label") scorecard_info = read_tsv(scorecard_map, "label")
finding_info = read_tsv(findings_map, "pattern") finding_rows = read_tsv_rows(findings_map)
with open(input_file, encoding="utf-8") as handle: with open(input_file, encoding="utf-8") as handle:
text = handle.read() text = handle.read()
@ -116,30 +120,32 @@ lines = text.splitlines()
def split_commands(value): def split_commands(value):
return [item.strip() for item in (value or "").split(";") if item.strip() and item.strip() != "none"] return [item.strip() for item in (value or "").split(";") if item.strip() and item.strip() != "none"]
def print_info(info): def print_info(info, indent=" "):
diagnose = split_commands(info.get("diagnose_commands")) diagnose = split_commands(info.get("diagnose_commands"))
jeannie_fix = split_commands(info.get("jeannie_fix_commands")) jeannie_fix = split_commands(info.get("jeannie_fix_commands"))
other_fix = split_commands(info.get("other_fix_commands")) other_fix = split_commands(info.get("other_fix_commands"))
print(f" means: {info.get('meaning') or 'No explain entry exists yet.'}") print(f"{indent}Why: {info.get('meaning') or 'No explain entry exists yet.'}")
if diagnose: if diagnose:
print(" diagnose:") print(f"{indent}Next command: {diagnose[0]}")
for command in diagnose: if len(diagnose) > 1:
print(f" {command}") print(f"{indent}More checks:")
for command in diagnose[1:]:
print(f"{indent} {command}")
if jeannie_fix: if jeannie_fix:
print(" Jeannie fix command exists: yes") print(f"{indent}Jeannie fix command:")
for command in jeannie_fix: for command in jeannie_fix:
print(f" {command}") print(f"{indent} {command}")
else: else:
print(" Jeannie fix command exists: no") print(f"{indent}Jeannie fix command: none yet")
if other_fix: if other_fix:
print(" non-Jeannie fix:") print(f"{indent}Code/config fix:")
for command in other_fix: for command in other_fix:
print(f" {command}") print(f"{indent} {command}")
print(f" risk: {info.get('risk') or 'unknown'}") print(f"{indent}Risk: {info.get('risk') or 'unknown'}")
print(f" auto-fix safe: {info.get('auto_fix') or 'no'}") print(f"{indent}Auto-fix safe: {info.get('auto_fix') or 'no'}")
if info.get("notes"): if info.get("notes"):
print(f" note: {info['notes']}") print(f"{indent}Note: {info['notes']}")
def parse_scorecard(): def parse_scorecard():
rows = [] rows = []
@ -191,11 +197,35 @@ def interesting_lines():
def match_findings(line): def match_findings(line):
lowered = line.lower() lowered = line.lower()
matches = [] matches = []
for pattern, info in finding_info.items(): for info in finding_rows:
pattern = (info.get("pattern") or "").lower()
if pattern and pattern in lowered: if pattern and pattern in lowered:
matches.append((pattern, info)) matches.append((pattern, info))
return matches return matches
def best_finding(line):
matches = match_findings(line)
if not matches:
return None
return sorted(matches, key=lambda item: (len(item[0]), item[0]), reverse=True)[0]
def parse_report_line(line):
report_match = re.match(r"^(?P<status>ok|warn|fail|skip)\s+(?P<check>.+?)(?:\s{2,}(?P<summary>.+))?$", line)
if report_match:
return (
report_match.group("check").strip(),
report_match.group("status").strip(),
(report_match.group("summary") or "").strip(),
)
scorecard_match = re.match(r"^(?P<label>.{1,28}) (?P<status>pass|warn|fail)(?: - (?P<summary>.*))?$", line)
if scorecard_match:
return (
scorecard_match.group("label").strip(),
scorecard_match.group("status").strip(),
(scorecard_match.group("summary") or "").strip(),
)
return (line[:72], "", "")
print(f"Jeannie Explain: {topic}") print(f"Jeannie Explain: {topic}")
print("=" * (17 + len(topic))) print("=" * (17 + len(topic)))
print("mode: read-only") print("mode: read-only")
@ -204,13 +234,13 @@ if topic == "scorecard":
rows = parse_scorecard() rows = parse_scorecard()
if not rows: if not rows:
print("\nNo scorecard warnings or failures found.") print("\nNo scorecard warnings or failures found.")
for label, status, summary in rows: for index, (label, status, summary) in enumerate(rows, start=1):
info = scorecard_info.get(label.lower(), {}) info = scorecard_info.get(label.lower(), {})
print(f"\n{label}") print(f"\n{index}. {label}")
print(f" status: {status}") print(f" Status: {status}")
if summary: if summary:
print(f" observed: {summary}") print(f" Observed: {summary}")
print_info(info) print_info(info, " ")
raise SystemExit(0) raise SystemExit(0)
found_lines = interesting_lines() found_lines = interesting_lines()
@ -218,17 +248,26 @@ seen_patterns = set()
matched_any = False matched_any = False
for line in found_lines: for line in found_lines:
matches = match_findings(line) title, status, summary = parse_report_line(line)
if not matches: match = best_finding(summary) if summary else None
if not match:
match = best_finding(line)
if not match:
continue continue
print(f"\nFinding: {line}") pattern, info = match
for pattern, info in matches:
if pattern in seen_patterns: if pattern in seen_patterns:
continue continue
seen_patterns.add(pattern) seen_patterns.add(pattern)
matched_any = True matched_any = True
print(f" matched: {pattern}") print(f"\n{len(seen_patterns)}. {title}")
print_info(info) if status:
print(f" Status: {status}")
if summary:
print(f" Observed: {summary}")
else:
print(f" Observed: {line}")
print(f" Matched rule: {pattern}")
print_info(info, " ")
if not matched_any: if not matched_any:
if found_lines: if found_lines: