Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/analyzers/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

class ActivityAnalyzer(BaseAnalyzer):
name = "activity"
weight = 0.15

def analyze(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class BaseAnalyzer(ABC):
"""Abstract base for all repo analyzers."""

name: str
weight: float

@abstractmethod
def analyze(
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/cicd.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

class CicdAnalyzer(BaseAnalyzer):
name = "cicd"
weight = 0.10

def analyze(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/code_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@

class CodeQualityAnalyzer(BaseAnalyzer):
name = "code_quality"
weight = 0.15

def analyze(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/community_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ class CommunityProfileAnalyzer(BaseAnalyzer):
"""

name = "community_profile"
weight = 0.03

def analyze(
self,
Expand Down
2 changes: 0 additions & 2 deletions src/analyzers/completeness.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@

class DocumentationAnalyzer(BaseAnalyzer):
name = "documentation"
weight = 0.05

def analyze(
self,
Expand Down Expand Up @@ -108,7 +107,6 @@ def analyze(

class BuildReadinessAnalyzer(BaseAnalyzer):
name = "build_readiness"
weight = 0.05

def analyze(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@

class DependenciesAnalyzer(BaseAnalyzer):
name = "dependencies"
weight = 0.10

def cache_inputs_hash(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/interest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class InterestAnalyzer(BaseAnalyzer):
"""

name = "interest"
weight = 0.0 # Not part of completeness score — separate axis

def analyze(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/readme.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@

class ReadmeAnalyzer(BaseAnalyzer):
name = "readme"
weight = 0.15

def cache_inputs_hash(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ class SecurityAnalyzer(BaseAnalyzer):
"""Scans for security surface issues — exposed secrets, dangerous files, missing config."""

name = "security"
weight = 0.0 # Advisory dimension, not part of completeness score

def analyze(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@

class StructureAnalyzer(BaseAnalyzer):
name = "structure"
weight = 0.10

def cache_inputs_hash(
self,
Expand Down
1 change: 0 additions & 1 deletion src/analyzers/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

class TestingAnalyzer(BaseAnalyzer):
name = "testing"
weight = 0.15

def analyze(
self,
Expand Down
20 changes: 19 additions & 1 deletion src/app/run_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@
DEFAULT_ANALYSIS_WORKERS = 1
MAX_ANALYSIS_WORKERS = 8
DEFAULT_PORTFOLIO_WORKSPACE = Path.home() / "Projects"
_SCORING_PROFILE_RESERVED_KEYS = frozenset(
{"stale_threshold_days", "grade_thresholds", "completeness_tiers"}
)


class ScoringProfile(dict[str, float]):
"""Flat profile weights plus optional scoring-constant overrides."""

def __init__(self, weights: dict[str, float], *, overrides: dict[str, object]) -> None:
super().__init__(weights)
self.overrides = overrides
CLI_MODE_GUIDE = """GitHub portfolio operating system with four product modes:
First Run setup, baseline creation, first workbook, first control-center read
Weekly Review normal workbook-first operator loop
Expand Down Expand Up @@ -545,6 +556,7 @@ def _analyze_one(repo_meta: RepoMetadata, repo_path: Path) -> RepoAudit:
repo_path=repo_path,
portfolio_lang_freq=portfolio_lang_freq,
custom_weights=custom_weights,
scoring_profile=getattr(custom_weights, "overrides", None),
github_client=worker_client,
scorecard_enabled=args.scorecard,
security_offline=args.security_offline,
Expand Down Expand Up @@ -1646,7 +1658,13 @@ def _load_scoring_profile(profile_name: str | None) -> tuple[dict[str, float] |
profile_path = Path(f"config/scoring-profiles/{profile_name}.json")
if profile_path.is_file():
print_info(f"Using scoring profile: {profile_name}")
return json.loads(profile_path.read_text()), normalized
profile = json.loads(profile_path.read_text())
overrides = {
key: profile.pop(key)
for key in _SCORING_PROFILE_RESERVED_KEYS
if key in profile
}
return ScoringProfile(profile, overrides=overrides), normalized

print_warning(f"Scoring profile not found: {profile_path}")
return None, normalized
Expand Down
22 changes: 16 additions & 6 deletions src/excel_action_items_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from typing import Any

from src.scoring_dimensions import display_dimension

ACTION_ITEMS_HEADERS = ["#", "Repo", "Action", "Impact", "Effort", "Dimension"]
EFFORT_MAP = {
"readme": "Low",
Expand Down Expand Up @@ -42,11 +44,13 @@ def collect_action_items(data: dict[str, Any]) -> list[dict[str, Any]]:
for result in audit.get("analyzer_results", [])
if result["dimension"] != "interest"
}
for dimension, score in sorted(dimension_scores.items(), key=lambda item: item[1])[:2]:
for dimension, score in sorted(
dimension_scores.items(), key=lambda item: item[1]
)[:2]:
actions.append(
{
"repo": audit["metadata"]["name"],
"action": f"Improve {dimension} (currently {score:.1f})",
"action": f"Improve {display_dimension(dimension)} (currently {score:.1f})",
"impact": f"Close {gap:.3f} gap to {next_tier}",
"effort": EFFORT_MAP.get(dimension, "Med"),
"dimension": dimension,
Expand All @@ -69,7 +73,9 @@ def collect_action_items(data: dict[str, Any]) -> list[dict[str, Any]]:
)

effort_order = {"Low": 0, "Med": 1, "High": 2}
actions.sort(key=lambda action: (effort_order.get(action["effort"], 1), action["gap"]))
actions.sort(
key=lambda action: (effort_order.get(action["effort"], 1), action["gap"])
)

seen: set[tuple[str, str]] = set()
unique: list[dict[str, Any]] = []
Expand Down Expand Up @@ -102,7 +108,9 @@ def write_action_items_sections(
ws.freeze_panes = "A5"

if content["sprint_rows"]:
ws.cell(row=3, column=1, value="Weekly Sprint (Top 5 Low-Effort)").font = section_font
ws.cell(
row=3, column=1, value="Weekly Sprint (Top 5 Low-Effort)"
).font = section_font
for col, header in enumerate(ACTION_ITEMS_HEADERS, 1):
ws.cell(row=4, column=col, value=header)
style_header_row(ws, 4, len(ACTION_ITEMS_HEADERS))
Expand All @@ -111,7 +119,9 @@ def write_action_items_sections(
style_data_cell(ws.cell(row=row_number, column=col, value=value))

full_start = len(content["sprint_rows"]) + 7
ws.cell(row=full_start, column=1, value="All Actions (Prioritized)").font = section_font
ws.cell(
row=full_start, column=1, value="All Actions (Prioritized)"
).font = section_font
full_start += 1
for col, header in enumerate(ACTION_ITEMS_HEADERS, 1):
ws.cell(row=full_start, column=col, value=header)
Expand All @@ -136,7 +146,7 @@ def _build_action_rows(actions: list[dict[str, Any]]) -> list[list[Any]]:
action["action"],
action["impact"],
action["effort"],
action["dimension"],
display_dimension(action["dimension"]),
]
for index, action in enumerate(actions, start=1)
]
7 changes: 4 additions & 3 deletions src/excel_all_repos_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from openpyxl.styles import Alignment, Font
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
from src.scoring_dimensions import display_dimension

ALL_REPOS_HEADERS = [
"Repo",
Expand Down Expand Up @@ -158,7 +159,7 @@ def _build_biggest_drag(audit: dict[str, Any]) -> str:
if not dimension_scores:
return "—"
worst_dimension = min(dimension_scores, key=dimension_scores.get)
return f"{worst_dimension} ({dimension_scores[worst_dimension]:.1f})"
return f"{display_dimension(worst_dimension)} ({dimension_scores[worst_dimension]:.1f})"


def _build_grade_reason(audit: dict[str, Any]) -> str:
Expand All @@ -174,8 +175,8 @@ def _build_grade_reason(audit: dict[str, Any]) -> str:
if len(weakest_dimensions) < 2:
return grade
return (
f"{grade}: {weakest_dimensions[0][0]}={weakest_dimensions[0][1]:.1f}, "
f"{weakest_dimensions[1][0]}={weakest_dimensions[1][1]:.1f}"
f"{grade}: {display_dimension(weakest_dimensions[0][0])}={weakest_dimensions[0][1]:.1f}, "
f"{display_dimension(weakest_dimensions[1][0])}={weakest_dimensions[1][1]:.1f}"
)


Expand Down
5 changes: 4 additions & 1 deletion src/excel_repo_data_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
build_score_explanation,
)
from src.sparkline import sparkline as render_sparkline
from src.scoring_dimensions import display_dimension


def score_explanation_for_audit(audit: dict) -> dict:
Expand Down Expand Up @@ -300,7 +301,9 @@ def repo_detail_rows(
)
for rank, (dimension, score, summary) in enumerate(ranked_dimensions, 1):
lookup_key = f"{repo_name}::{rank}"
dimension_rows.append([lookup_key, repo_name, rank, dimension, score, summary])
dimension_rows.append(
[lookup_key, repo_name, rank, display_dimension(dimension), score, summary]
)

for run_index, score in enumerate(scores, 1):
history_rows.append([repo_name, run_index, score, render_sparkline(scores)])
Expand Down
17 changes: 16 additions & 1 deletion src/excel_score_explainer_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from typing import Any

from src.scoring_dimensions import UNSCORED_DIMENSIONS, display_dimension

SCORE_EXPLAINER_HEADERS = ["Dimension", "Weight", "What It Measures", "How to Improve"]

DIMENSION_INFO = {
Expand Down Expand Up @@ -41,6 +43,10 @@
"docs/ dir, CHANGELOG, comment density",
"Add docs/ folder or CHANGELOG.md",
),
"description": (
"Repository description confidence and consistency",
"Add a concise, accurate repository description",
),
}


Expand All @@ -53,12 +59,21 @@ def build_score_explainer_content(
return {
"dimension_rows": [
[
dimension,
display_dimension(dimension),
f"{weight:.0%}",
DIMENSION_INFO.get(dimension, ("", ""))[0],
DIMENSION_INFO.get(dimension, ("", ""))[1],
]
for dimension, weight in sorted(weights.items(), key=lambda item: item[1], reverse=True)
] + [
[
display_dimension(dimension),
"Unscored",
DIMENSION_INFO.get(dimension, ("", ""))[0],
DIMENSION_INFO.get(dimension, ("", ""))[1],
]
for dimension in sorted(UNSCORED_DIMENSIONS)
if dimension not in weights
],
"grade_rows": [[grade, f">= {threshold:.0%}"] for threshold, grade in grade_thresholds],
"tier_rows": [
Expand Down
1 change: 0 additions & 1 deletion src/portfolio_risk.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ def build_risk_entry(
display_name: str,
operating_path: str,
path_override: str,
path_confidence: str,
context_quality: str,
activity_status: str,
registry_status: str,
Expand Down
1 change: 0 additions & 1 deletion src/portfolio_truth_reconcile.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,6 @@ def _build_truth_project(
display_name=raw_project["name"],
operating_path=path_entry.get("operating_path", ""),
path_override=path_entry.get("path_override", ""),
path_confidence=path_entry.get("path_confidence", "legacy"),
context_quality=context_quality,
activity_status=activity_status,
registry_status=registry_status,
Expand Down
8 changes: 5 additions & 3 deletions src/report_enrichment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from src.portfolio_truth_types import truth_latest_path
from src.report_contracts import RiskLookupEntry, RiskPosture, TopElevatedEntry
from src.scoring_dimensions import display_dimension
from src.terminology import ACTION_SYNC_CANONICAL_LABELS
from src.weekly_packaging import finalize_weekly_pack
from src.weekly_scheduling_overlay import apply_weekly_scheduling_overlay
Expand Down Expand Up @@ -614,9 +615,10 @@ def _top_dimension_labels(scores: dict[str, float], *, reverse: bool) -> list[st
for dimension, score in ordered:
if dimension == "interest":
continue
labels.append(
f"{DIMENSION_LABELS.get(dimension, dimension.replace('_', ' ').title())} ({score:.2f})"
)
label = DIMENSION_LABELS.get(dimension, dimension.replace("_", " ").title())
if display_dimension(dimension) != dimension:
label = f"{label} (unscored)"
labels.append(f"{label} ({score:.2f})")
if len(labels) == 3:
break
return labels
Expand Down
Loading