Skip to content

feat: add issue analyzer script for priority/scope management#1084

Merged
Aureliolo merged 4 commits intomainfrom
feat/issue-analyzer-script
Apr 5, 2026
Merged

feat: add issue analyzer script for priority/scope management#1084
Aureliolo merged 4 commits intomainfrom
feat/issue-analyzer-script

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

Adds a Python CLI tool to analyze and reorganize GitHub issues by priority, scope, and version.

Features

  • python scripts/issue_analyzer.py analyze - Full analysis of all open issues
  • python scripts/issue_analyzer.py summary - Brief summary by version
  • python scripts/issue_analyzer.py critical - Show only critical/high priority issues
  • python scripts/issue_analyzer.py propose - Propose new labels/versions
  • python scripts/issue_analyzer.py deps - Show dependency chains

Analysis Capabilities

  • Re-assesses priority based on actual product value vs. technical complexity
  • Identifies over-scoped issues (many "large" should be "medium" or "small")
  • Categorizes issues by type, scope, and version
  • Finds dependency chains in issue bodies
  • Proposes denser version organization

Usage

# View all commands
python scripts/issue_analyzer.py

# See critical issues that need attention
python scripts/issue_analyzer.py critical

# Propose reorganization
python scripts/issue_analyzer.py propose

Notes

  • Requires gh CLI to be authenticated
  • Excludes ZAP scan reports from analysis
  • Uses relaxed ruff rules (scripts directory)

Reviewed by: ruff, format, secrets detection (all passed)

Copilot AI review requested due to automatic review settings April 5, 2026 23:15
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

Walkthrough

Adds an executable CLI script at scripts/issue_analyzer.py that fetches open GitHub issues via gh issue list (fixed JSON fields), extracts label-derived metadata (prio:<...>, scope:<...>, type:<...>, and v0.* version labels), heuristically analyzes issue bodies for scope signals and acceptance-criteria counts, and computes recommended priority and sometimes revised scope using rule sets and per-issue overrides. Exposes subcommands analyze, summary, critical, deps, and propose; emits stderr progress/status, and fails fast on missing gh, fetch/parsing errors, or timeouts. Constants for thresholds and special-issue sets were added.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main addition: a new CLI script for analyzing and managing GitHub issues by priority and scope.
Description check ✅ Passed The description clearly explains the purpose, features, and usage of the new issue analyzer script with relevant examples and notes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 40.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces issue_analyzer.py, a utility script for analyzing and reorganizing GitHub issues for the SynthOrg project. The feedback highlights the need for better error handling when the GitHub CLI is missing and identifies several instances of hardcoded data—such as specific issue numbers and version themes—that make the script brittle. Additionally, a minor optimization for a redundant regular expression was suggested to improve code quality.

Comment on lines +52 to +54
except subprocess.CalledProcessError as e:
print(f"Error fetching issues: {e}", file=sys.stderr)
return []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current error handling only catches subprocess.CalledProcessError. If the gh CLI is not installed on the system, subprocess.run will raise a FileNotFoundError, which is currently unhandled and will cause the script to crash with a traceback. It is better to catch this specifically and provide a helpful error message.

Suggested change
except subprocess.CalledProcessError as e:
print(f"Error fetching issues: {e}", file=sys.stderr)
return []
except FileNotFoundError:
print("Error: 'gh' CLI not found. Please install GitHub CLI.", file=sys.stderr)
return []
except subprocess.CalledProcessError as e:
print(f"Error fetching issues: {e}", file=sys.stderr)
return []

Comment on lines +117 to +136
if num in [1081, 1082]:
recommended_prio = "critical"
recommended_scope = "medium"
reason = "Blocking core functionality - dashboard editing doesn't work"

# HIGH: Quick wins with major value
elif num in [1080, 1079, 1077]:
recommended_prio = "high"
recommended_scope = "small"
reason = "Fast implementation, high user value"

# Research items - deferrable
elif issue_type == "research":
recommended_prio = "low"
reason = "Needs evaluation before implementation"

# Speculative features - future
elif num in [251, 252, 253, 254, 255, 242, 241, 250, 249, 248, 246, 1012, 1005]:
recommended_prio = "low"
reason = "Aspirational features - revisit later"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for re-assessing priority and scope relies on several blocks of hardcoded issue numbers. This makes the script very brittle and specific to the current state of the repository. As issues are closed or new ones are created, this logic will quickly become outdated. Consider using GitHub labels (e.g., impact:critical, effort:medium) or metadata within the issue body to drive these recommendations dynamically.

# Common dependency patterns
patterns = [
r"(?:depends?\s+(?:on|upon)|blocked\s+(?:by|on)|requires?|needs?|builds?\s+on)\s*[:\s]*#(\d+)",
r"(?:follow(?:s|ing|[- ]up)|extends?|extends?)\s*[:\s]*#(\d+)",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The regular expression contains a redundant alternative extends?. Removing the duplicate makes the pattern cleaner and slightly more efficient.

Suggested change
r"(?:follow(?:s|ing|[- ]up)|extends?|extends?)\s*[:\s]*#(\d+)",
r"(?:follow(?:s|ing|[- ]up)|extends?)\s*[:\s]*#(\d+)",

Comment on lines +337 to +355
v064 = [] # Critical
v065 = [] # High
v070 = [] # Medium
v080 = [] # Low + research

for c in categorized:
if c["recommended_prio"] == "critical":
v064.append(c)
elif c["recommended_prio"] == "high":
v065.append(c)
elif c["recommended_prio"] == "medium":
v070.append(c)
else:
v080.append(c)

print("\n### v0.6.4 (CRITICAL - 2 issues)")
print("Theme: Foundation - dashboard editing actually works\n")
for c in sorted(v064, key=lambda x: x["num"]):
print(f" #{c['num']}: {c['title'][:55]} [{c['recommended_scope']}]")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The version buckets and associated display logic (such as the hardcoded '2 issues' in the print statement) are tightly coupled to the current project state. This limits the tool's longevity. Consider abstracting the versioning and theme logic into a configuration object or deriving it from repository milestones to ensure the tool remains useful as the project progresses.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 21f97e3.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a standalone Python CLI (scripts/issue_analyzer.py) that uses the GitHub CLI (gh) to fetch open issues and produce analyses/summaries focused on priority, scope, versions, and dependency chains.

Changes:

  • Introduces scripts/issue_analyzer.py with commands: analyze, summary, critical, propose, deps.
  • Implements heuristics to infer scope/priority adjustments from labels and issue body content.
  • Adds dependency-chain detection via regex patterns and prints proposed version bucket groupings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +213 to +219
}

for c in categorized:
groups.get(c["recommended_prio"], []).append(c)

# Print groups
for prio in ["critical", "high", "medium", "low"]:
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In print_full_analysis, issues whose recommended_prio isn't one of the predefined keys are silently dropped because groups.get(..., []) returns a temporary list that isn't stored back into groups. This can happen when issues are missing a prio: label and get_label_value() returns "unknown". Consider adding an explicit "unscoped"/"unknown" bucket (or normalizing unknown priorities to a default) so every issue appears in the output.

Suggested change
}
for c in categorized:
groups.get(c["recommended_prio"], []).append(c)
# Print groups
for prio in ["critical", "high", "medium", "low"]:
"unknown": [],
}
for c in categorized:
prio = c["recommended_prio"]
if prio not in groups:
prio = "unknown"
groups[prio].append(c)
# Print groups
for prio in ["critical", "high", "medium", "low", "unknown"]:

Copilot uses AI. Check for mistakes.
Comment on lines +352 to +356
print("\n### v0.6.4 (CRITICAL - 2 issues)")
print("Theme: Foundation - dashboard editing actually works\n")
for c in sorted(v064, key=lambda x: x["num"]):
print(f" #{c['num']}: {c['title'][:55]} [{c['recommended_scope']}]")

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The heading "### v0.6.4 (CRITICAL - 2 issues)" is hardcoded to 2 issues, but the actual critical issue count is computed dynamically into v064. If the set of critical issues changes, this output becomes incorrect. Consider formatting the header with len(v064) instead of a fixed number.

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +101
def categorize_issue(issue: dict) -> dict:
"""Categorize an issue by priority and scope."""
num = issue["number"]
title = issue["title"]
labels = issue.get("labels", [])
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing unit tests for scripts under tests/unit/scripts/ (e.g. test_generate_comparison.py). Consider adding tests around categorize_issue() (and related analyze_scope() helpers) to lock in the categorization heuristics and catch regressions like missing/unknown prio: labels affecting output.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +148
reason += "; No complex logic or acceptance criteria - likely medium"
elif (
analysis["has_endpoints"]
and analysis["ac_count"] < ACCEPTANCE_CRITERIA_THRESHOLD
):
recommended_scope = "medium"
reason += "; Standard CRUD endpoints, not complex architecture"
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reason can end up starting with a leading "; " when only a scope reassessment applies (since reason starts empty and then you append "; ..."). This produces awkward output like Reason: ; Standard CRUD endpoints.... Consider adding the separator only when reason is already non-empty.

Suggested change
reason += "; No complex logic or acceptance criteria - likely medium"
elif (
analysis["has_endpoints"]
and analysis["ac_count"] < ACCEPTANCE_CRITERIA_THRESHOLD
):
recommended_scope = "medium"
reason += "; Standard CRUD endpoints, not complex architecture"
reason = (
f"{reason}; No complex logic or acceptance criteria - likely medium"
if reason
else "No complex logic or acceptance criteria - likely medium"
)
elif (
analysis["has_endpoints"]
and analysis["ac_count"] < ACCEPTANCE_CRITERIA_THRESHOLD
):
recommended_scope = "medium"
reason = (
f"{reason}; Standard CRUD endpoints, not complex architecture"
if reason
else "Standard CRUD endpoints, not complex architecture"
)

Copilot uses AI. Check for mistakes.
Comment on lines +236 to +238
version_counts = defaultdict(
lambda: {"total": 0, "high": 0, "medium": 0, "low": 0, "unscoped": 0}
)
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print_summary() tracks an unscoped count per version (see the version_counts initializer), but the printed table only includes Total/High/Medium/Low. This can make Total not equal High+Medium+Low, which is confusing. Consider printing an Unscoped column or folding those counts into an existing bucket.

Copilot uses AI. Check for mistakes.
Comment on lines +375 to +379
def main():
"""Main entry point for the issue analyzer."""
if len(sys.argv) < MIN_CLI_ARGS:
print(__doc__)
sys.exit(1)
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script's entrypoint diverges from the pattern used by other scripts/*.py utilities (e.g. scripts/export_openapi.py), where main() returns an int and if __name__ == "__main__": sys.exit(main()). Consider returning explicit exit codes (0/1) and exiting via sys.exit(main()) for consistent CLI behavior and easier testing.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/issue_analyzer.py`:
- Around line 352-353: The hard-coded "2 issues" string in the print statements
should be replaced with a computed count from the actual issue list for that
release (e.g. use len(issues_for_version) or len(version_issues) instead of the
literal 2) and format the output with correct pluralization ("1 issue" vs "N
issues"); update the print calls (the lines that currently call print("###
v0.6.4 (CRITICAL - 2 issues)") and the following theme print) to compute count =
len(<the list holding v0.6.4 issues>) and interpolate that count into the header
string, falling back to zero if the list is missing or None.
- Around line 381-401: Validate the CLI command before calling
run_gh_issue_list(): check the value of the local variable command (from
sys.argv[1]) against the allowed commands used later (print_full_analysis,
print_summary, print_critical, find_dependencies, propose_reorganization) and
exit with the usage message if it's not one of them; only after the command is
known to be valid, call run_gh_issue_list() and proceed to dispatch to the
appropriate function (analyze -> print_full_analysis, summary -> print_summary,
critical -> print_critical, deps -> find_dependencies, propose ->
propose_reorganization).
- Around line 208-217: The current loop silently drops entries whose
c["recommended_prio"] isn't a key in groups; update the logic around the groups
dict and the loop over categorized (variable names: groups, categorized, and
c["recommended_prio"]) to ensure unknown priorities are preserved by either
adding an "unknown" bucket up-front or using
groups.setdefault(c["recommended_prio"], []).append(c) (or an explicit
membership check and creation) so items with unexpected priorities are collected
instead of being appended to a throwaway list.
- Around line 196-197: Several public functions lack return type annotations and
Google-style docstrings; add explicit return type hints to the signatures and
insert Google-style docstrings (with Args, Returns, and Raises where applicable)
immediately under each def for: run_gh_issue_list, get_label_value,
analyze_scope, categorize_issue, print_full_analysis, print_summary,
print_critical, find_dependencies, propose_reorganization, and main; ensure the
documented parameter types and return types match the function signatures so
mypy strict mode and ruff D rules pass.
- Around line 42-57: The try/except block that calls subprocess.run(cmd, ...)
should be changed to add a timeout (e.g., timeout=30) to the subprocess.run call
and stop swallowing failures: remove the broad except handlers that print and
return [] and instead let subprocess.CalledProcessError and json.JSONDecodeError
propagate (or re-raise a wrapped exception) so callers can distinguish fetch
errors from "no issues"; also perform command validation before executing
subprocess.run by validating the variable cmd earlier so invalid commands don't
trigger the fetch. Reference the existing subprocess.run(...) call, the local
cmd variable, and the current except handlers for subprocess.CalledProcessError
and json.JSONDecodeError when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 109fc893-a65f-4a26-8dc0-a68f94558048

📥 Commits

Reviewing files that changed from the base of the PR and between d341d37 and b71db28.

📒 Files selected for processing (1)
  • scripts/issue_analyzer.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations.
Use PEP 758 except syntax: use except A, B: (no parentheses) in Python 3.14+ code -- ruff enforces this.
All public functions in Python must have type hints and docstrings in Google style. Enforce with mypy strict mode and ruff D rules.
Use NotBlankStr (from core.types) for all identifier/name fields in Pydantic models -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators.
Use allow_inf_nan=False in all ConfigDict declarations in Pydantic models to reject NaN/Inf in numeric fields at validation time.
Use @computed_field for derived values in Pydantic models instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).
Use frozen Pydantic models for config/identity. Use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new Python code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Python line length: 88 characters (ruff).
Python functions must be < 50 lines, files < 800 lines.
Handle errors explicitly in Python code, never silently swallow them.
Validate at system boundaries (user input, external APIs, config files) in Python code.
All provider calls in Python code must go through...

Files:

  • scripts/issue_analyzer.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Version bumping (pre-1.0): `fix:` = patch, `feat:` = patch, `feat!:` = minor, `BREAKING CHANGE` trailer = minor. Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T11:08:01.542Z
Learning: GitHub issue queries: use `gh issue list` via Bash (not MCP tools) — MCP `list_issues` has unreliable field data.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Use `gh issue list` via Bash (not MCP tools) for GitHub issue queries -- MCP `list_issues` has unreliable field data.

- Add return type hints to all functions (None, list[tuple[int,int]])
- Add Google-style docstrings to all public functions (Args sections)
- Validate command BEFORE fetching issues (fail fast with set lookup)
- Move MIN_CLI_ARGS to top-level constant
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 5, 2026 23:37 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
scripts/issue_analyzer.py (1)

445-446: ⚠️ Potential issue | 🟡 Minor

main() still needs a Google-style docstring.

main() has a one-line docstring, but this repository requires Google-style sections for public functions.

📝 Proposed fix
 def main() -> None:
-    """Main entry point for the issue analyzer."""
+    """Run the issue analyzer CLI command dispatcher.
+
+    Returns:
+        None
+
+    Raises:
+        SystemExit: If arguments are invalid or command execution fails.
+    """

As per coding guidelines: "All public functions in Python must have type hints and docstrings in Google style."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/issue_analyzer.py` around lines 445 - 446, The one-line docstring for
the public function main() needs to be replaced with a Google-style docstring:
expand the docstring of main() to include a short summary line, a blank line, an
Args section (if there are parameters; if none, omit or state none), and a
Returns section describing the return type (None) and side effects, and any
Raises section if exceptions are propagated; keep the existing type hint "->
None" and update the docstring in the def main() block to follow that structure
so it complies with the repository's Google-style docstring requirement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/issue_analyzer.py`:
- Around line 291-315: The priority-check logic currently only recognizes
["high","medium","low"], so labels like "prio:critical" get counted as
"unscoped"; update the code around version_counts and the prio handling (the
defaultdict setup and the two places where prio is checked) to include a
"critical" bucket and to treat "critical" as a valid priority (e.g., add
"critical" to the default dict keys and to the list used in the conditional that
uses get_label_value(labels, "prio")), and make the same change in the mirrored
block referenced at lines ~320-327; refer to variables/function names
version_counts, get_label_value, ZAP_REPORT_ISSUE_NUMBER, and issues to find the
relevant logic.
- Around line 132-206: categorize_issue is too long and mixes label extraction,
priority rules, and scope reassessment; split it into smaller helpers: create
extract_issue_meta(issue) to pull
num/title/labels/body/current_prio/current_scope/issue_type/versions,
determine_priority(num, current_prio, issue_type, versions) to encapsulate the
hardcoded priority rules (handle CRITICAL/HIGH/research/speculative lists), and
reassess_scope(current_scope, analysis) to encapsulate the "Scope
re-assessments" block (use ACCEPTANCE_CRITERIA_THRESHOLD and return updated
scope + scope reason); then have categorize_issue call extract_issue_meta ->
analyze_scope -> determine_priority -> reassess_scope, merge reasons and
analysis, and return the same dict shape as before (preserve keys
"recommended_prio", "recommended_scope", "reason", and **analysis).
- Around line 392-443: propose_reorganization currently sorts issues only by
recommended_prio and simple keys (functions/vars: propose_reorganization,
categorized, v064/v065/v070/v080) and can place blocked items before their
prerequisites; call find_dependencies for the incoming issues to build a
dependency map and, for each version bucket, partition items into unblocked (no
unmet dependencies in other buckets) and blocked sets, sort unblocked items
first (then blocked), and use that ordering in the prints (replace the current
sorted(...) iterations for v064/v065/v070/v080 so unblocked issues appear before
blocked ones while preserving existing secondary sort keys like num or has_ui).
Ensure find_dependencies usage identifies cross-issue prerequisites and treats
ZAP_REPORT_ISSUE_NUMBER the same as elsewhere.
- Around line 54-76: The try/except around subprocess.run that invokes cmd with
timeout=30 must also catch subprocess.TimeoutExpired to avoid an unformatted
traceback; add an except subprocess.TimeoutExpired as e block after the
FileNotFoundError handler that prints a clear error to stderr (including the cmd
and timeout or e) and exits with sys.exit(1), referencing the existing
subprocess.run call, the cmd variable, and the timeout value to help locate the
code.

---

Duplicate comments:
In `@scripts/issue_analyzer.py`:
- Around line 445-446: The one-line docstring for the public function main()
needs to be replaced with a Google-style docstring: expand the docstring of
main() to include a short summary line, a blank line, an Args section (if there
are parameters; if none, omit or state none), and a Returns section describing
the return type (None) and side effects, and any Raises section if exceptions
are propagated; keep the existing type hint "-> None" and update the docstring
in the def main() block to follow that structure so it complies with the
repository's Google-style docstring requirement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ff9686e7-b163-448a-831a-4cc131f6a0d0

📥 Commits

Reviewing files that changed from the base of the PR and between b71db28 and de61889.

📒 Files selected for processing (1)
  • scripts/issue_analyzer.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations.
Use PEP 758 except syntax: use except A, B: (no parentheses) in Python 3.14+ code -- ruff enforces this.
All public functions in Python must have type hints and docstrings in Google style. Enforce with mypy strict mode and ruff D rules.
Use NotBlankStr (from core.types) for all identifier/name fields in Pydantic models -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators.
Use allow_inf_nan=False in all ConfigDict declarations in Pydantic models to reject NaN/Inf in numeric fields at validation time.
Use @computed_field for derived values in Pydantic models instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).
Use frozen Pydantic models for config/identity. Use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new Python code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Python line length: 88 characters (ruff).
Python functions must be < 50 lines, files < 800 lines.
Handle errors explicitly in Python code, never silently swallow them.
Validate at system boundaries (user input, external APIs, config files) in Python code.
All provider calls in Python code must go through...

Files:

  • scripts/issue_analyzer.py
🧠 Learnings (17)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Version bumping (pre-1.0): `fix:` = patch, `feat:` = patch, `feat!:` = minor, `BREAKING CHANGE` trailer = minor. Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all in Python and other code. No deferring, no 'out of scope' skipping.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-01T05:46:17.064Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T05:46:17.064Z
Learning: Fix all valid issues found by review agents (including pre-existing issues in surrounding code, suggestions, and adjacent findings) — never skip or defer

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Prioritize issues by dependency order, not priority labels — unblocked dependencies come first.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Export OpenAPI schema with `uv run python scripts/export_openapi.py` (required before docs build)

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-30T16:36:33.512Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: cli/CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:33.512Z
Learning: Applies to cli/cmd/**/*.go : CLI exit codes must follow the standard: 0 = Success, 1 = Runtime error, 2 = Usage error (bad arguments), 3 = Unhealthy (backend/containers), 4 = Unreachable (Docker not available), 10 = Updates available (`--check`)

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Export OpenAPI schema with: `uv run python scripts/export_openapi.py` (needed before docs build).

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Use `gh issue list` via Bash (not MCP tools) — MCP `list_issues` has unreliable field data

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Applies to **/*.py : All public functions in Python must have type hints and docstrings in Google style. Enforce with mypy strict mode and ruff D rules.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-03T07:50:58.367Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T07:50:58.367Z
Learning: Applies to **/*.py : Use Google-style docstrings, required on all public classes and functions (enforced by ruff D rules)

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-15T22:16:31.632Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:16:31.632Z
Learning: Applies to **/*.py : Type hints: all public functions and classes require type hints, enforced with mypy strict mode.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-16T10:40:25.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T10:40:25.144Z
Learning: Applies to **/*.py : Docstrings: Google style, required on public classes and functions (enforced by ruff D rules).

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-02T08:47:46.313Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:47:46.313Z
Learning: Applies to **/*.py : Include type hints for all public functions; enforce with mypy strict mode

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-21T11:08:01.542Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T11:08:01.542Z
Learning: GitHub issue queries: use `gh issue list` via Bash (not MCP tools) — MCP `list_issues` has unreliable field data.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Use `gh issue list` via Bash (not MCP tools) for GitHub issue queries -- MCP `list_issues` has unreliable field data.

Applied to files:

  • scripts/issue_analyzer.py

Comment on lines +392 to +443
def propose_reorganization(issues: list[dict]) -> None:
"""Propose new version organization based on priority analysis.

Args:
issues: List of issue dictionaries from GitHub API.
"""
categorized = [
categorize_issue(i) for i in issues if i["number"] != ZAP_REPORT_ISSUE_NUMBER
]

print("\n" + "=" * 60)
print("PROPOSED REORGANIZATION")
print("=" * 60)

# Define version buckets based on recommended priority
v064 = [] # Critical
v065 = [] # High
v070 = [] # Medium
v080 = [] # Low + research

for c in categorized:
if c["recommended_prio"] == "critical":
v064.append(c)
elif c["recommended_prio"] == "high":
v065.append(c)
elif c["recommended_prio"] == "medium":
v070.append(c)
else:
v080.append(c)

print(f"\n### v0.6.4 (CRITICAL - {len(v064)} issue{'s' if len(v064) != 1 else ''})")
print("Theme: Foundation - dashboard editing actually works\n")
for c in sorted(v064, key=lambda x: x["num"]):
print(f" #{c['num']}: {c['title'][:55]} [{c['recommended_scope']}]")

print(f"\n### v0.6.5 (HIGH PRIORITY - {len(v065)} issues)")
print("Theme: User value - polish and features\n")
for c in sorted(v065, key=lambda x: (x["has_ui"], x["num"]), reverse=True)[
:MAX_DISPLAY_ISSUES
]:
print(f" #{c['num']}: {c['title'][:55]} [{c['recommended_scope']}]")
if len(v065) > MAX_DISPLAY_ISSUES:
print(f" ... and {len(v065) - MAX_DISPLAY_ISSUES} more")

print(f"\n### v0.7.0 (MEDIUM - {len(v070)} issues)")
print("Theme: Engine hardening\n")
for c in sorted(v070, key=lambda x: x["num"])[:8]:
print(f" #{c['num']}: {c['title'][:55]} [{c['recommended_scope']}]")

print(f"\n### v0.8.x / Future ({len(v080)} issues)")
print("Theme: Research and aspirational features")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reorganization should account for dependency order, not only priority buckets.

Current release bucketing ignores dependency edges, so blocked issues can be scheduled before their prerequisites. Use find_dependencies() output to order unblocked items first within each proposed release.

Based on learnings: "Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/issue_analyzer.py` around lines 392 - 443, propose_reorganization
currently sorts issues only by recommended_prio and simple keys (functions/vars:
propose_reorganization, categorized, v064/v065/v070/v080) and can place blocked
items before their prerequisites; call find_dependencies for the incoming issues
to build a dependency map and, for each version bucket, partition items into
unblocked (no unmet dependencies in other buckets) and blocked sets, sort
unblocked items first (then blocked), and use that ordering in the prints
(replace the current sorted(...) iterations for v064/v065/v070/v080 so unblocked
issues appear before blocked ones while preserving existing secondary sort keys
like num or has_ui). Ensure find_dependencies usage identifies cross-issue
prerequisites and treats ZAP_REPORT_ISSUE_NUMBER the same as elsewhere.

- Extract hardcoded issue numbers to top-level constants (CRITICAL_ISSUES,
  HIGH_QUICK_WIN_ISSUES, DEFERRED_ISSUES) for maintainability
- Add 'Other' column to print_summary() table so Total equals the sum of
  all printed columns (previously unscoped issues were counted but hidden)
- Catch subprocess.TimeoutExpired for controlled error on gh CLI timeout
- Split categorize_issue into _determine_priority and _reassess_scope
  helpers to stay under 50-line function limit
- Add critical priority bucket to print_summary so prio:critical labels
  are not miscounted as unscoped
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 5, 2026 23:45 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/issue_analyzer.py`:
- Around line 410-445: The function find_dependencies currently mixes
computation and printing; remove the printing side effects from
find_dependencies so it only returns the list of (source, target) tuples, then
create a new function print_dependencies(deps: list[tuple[int,int]]) that
contains the existing console output (header, formatted lines, and "No explicit
dependencies found" message); update main() to call deps =
find_dependencies(issues) and then call print_dependencies(deps) so the logic is
pure and printing is separated (keep the same return type for find_dependencies
and keep existing patterns and variable names such as find_dependencies,
print_dependencies, deps, and main).
- Around line 126-127: The code assigns `_ = title` but never uses the title, so
remove the unused `title` parameter from the function signature (and delete the
`_ = title` line) and update all call sites to stop passing a title; also update
the function's docstring/type hints and any tests accordingly. If you prefer to
keep it for planned work, replace `_ = title` with a clear TODO comment like
"TODO: use title in future issue analysis" and keep a FIXME/TODO marker in the
function's docstring or above the line so intent is explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ff09802f-6d85-4154-95c8-fec0003d9ae8

📥 Commits

Reviewing files that changed from the base of the PR and between de61889 and 21f97e3.

📒 Files selected for processing (1)
  • scripts/issue_analyzer.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations.
Use PEP 758 except syntax: use except A, B: (no parentheses) in Python 3.14+ code -- ruff enforces this.
All public functions in Python must have type hints and docstrings in Google style. Enforce with mypy strict mode and ruff D rules.
Use NotBlankStr (from core.types) for all identifier/name fields in Pydantic models -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators.
Use allow_inf_nan=False in all ConfigDict declarations in Pydantic models to reject NaN/Inf in numeric fields at validation time.
Use @computed_field for derived values in Pydantic models instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).
Use frozen Pydantic models for config/identity. Use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new Python code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Python line length: 88 characters (ruff).
Python functions must be < 50 lines, files < 800 lines.
Handle errors explicitly in Python code, never silently swallow them.
Validate at system boundaries (user input, external APIs, config files) in Python code.
All provider calls in Python code must go through...

Files:

  • scripts/issue_analyzer.py
🧠 Learnings (23)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T12:54:22.557Z
Learning: Version bumping (pre-1.0): `fix:` = patch, `feat:` = patch, `feat!:` = minor, `BREAKING CHANGE` trailer = minor. Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Use `gh issue list` via Bash (not MCP tools) — MCP `list_issues` has unreliable field data
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T11:08:01.542Z
Learning: GitHub issue queries: use `gh issue list` via Bash (not MCP tools) — MCP `list_issues` has unreliable field data.
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all in Python and other code. No deferring, no 'out of scope' skipping.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-01T05:46:17.064Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T05:46:17.064Z
Learning: Fix all valid issues found by review agents (including pre-existing issues in surrounding code, suggestions, and adjacent findings) — never skip or defer

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Prioritize issues by dependency order, not priority labels — unblocked dependencies come first.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Export OpenAPI schema with `uv run python scripts/export_openapi.py` (required before docs build)

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-30T16:36:33.512Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: cli/CLAUDE.md:0-0
Timestamp: 2026-03-30T16:36:33.512Z
Learning: Applies to cli/cmd/**/*.go : CLI exit codes must follow the standard: 0 = Success, 1 = Runtime error, 2 = Usage error (bad arguments), 3 = Unhealthy (backend/containers), 4 = Unreachable (Docker not available), 10 = Updates available (`--check`)

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Export OpenAPI schema with: `uv run python scripts/export_openapi.py` (needed before docs build).

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-31T14:17:24.182Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:17:24.182Z
Learning: Use `gh issue list` via Bash (not MCP tools) — MCP `list_issues` has unreliable field data

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Applies to **/*.py : All public functions in Python must have type hints and docstrings in Google style. Enforce with mypy strict mode and ruff D rules.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-03T07:50:58.367Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-03T07:50:58.367Z
Learning: Applies to **/*.py : Use Google-style docstrings, required on all public classes and functions (enforced by ruff D rules)

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-15T22:16:31.632Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:16:31.632Z
Learning: Applies to **/*.py : Type hints: all public functions and classes require type hints, enforced with mypy strict mode.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-16T10:40:25.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T10:40:25.144Z
Learning: Applies to **/*.py : Docstrings: Google style, required on public classes and functions (enforced by ruff D rules).

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-02T08:47:46.313Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:47:46.313Z
Learning: Applies to **/*.py : Include type hints for all public functions; enforce with mypy strict mode

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-21T11:08:01.542Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T11:08:01.542Z
Learning: GitHub issue queries: use `gh issue list` via Bash (not MCP tools) — MCP `list_issues` has unreliable field data.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Use `gh issue list` via Bash (not MCP tools) for GitHub issue queries -- MCP `list_issues` has unreliable field data.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Python tests have a 30 second timeout per test (global in `pyproject.toml` -- do not add per-file `pytest.mark.timeout(30)` markers; non-default overrides like `timeout(60)` are allowed).

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Functions must be less than 50 lines; files must be less than 800 lines

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-01T06:12:03.047Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T06:12:03.047Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Keep functions under 50 lines and files under 800 lines

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-04-05T22:36:24.545Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-05T22:36:24.545Z
Learning: Applies to **/*.py : Python functions must be < 50 lines, files < 800 lines.

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-28T09:24:37.044Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-28T09:24:37.044Z
Learning: Applies to **/*.{py,ts,tsx,go} : Functions should be under 50 lines; files should be under 800 lines

Applied to files:

  • scripts/issue_analyzer.py
📚 Learning: 2026-03-17T13:48:18.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T13:48:18.114Z
Learning: Applies to **/*.py : Functions: < 50 lines, files < 800 lines.

Applied to files:

  • scripts/issue_analyzer.py
🔇 Additional comments (10)
scripts/issue_analyzer.py (10)

479-494: Reorganization still ignores dependency order.

The sorting logic (e.g., sorted(v064, key=lambda x: x["num"])) organizes issues without considering dependency edges from find_dependencies(). Blocked issues could be scheduled before their prerequisites.

Based on learnings: "Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first."


21-42: Well-structured constants for issue overrides.

The extraction of hardcoded issue lists into typed top-level constants (CRITICAL_ISSUES, HIGH_QUICK_WIN_ISSUES, DEFERRED_ISSUES) improves maintainability and makes the priority override logic explicit.


68-96: Robust error handling with timeout.

All exception paths are now properly handled:

  • TimeoutExpired with clear timeout value in message
  • FileNotFoundError with helpful installation link
  • CalledProcessError and JSONDecodeError with stderr output

The 30-second timeout prevents indefinite hangs on network issues.


99-114: Clean label extraction helper.

Simple, focused function with proper type hints and docstring.


152-211: Good refactoring of categorization logic.

The extraction of _determine_priority and _reassess_scope from the original categorize_issue addresses the past review feedback about function size. Each helper has a single responsibility with clear inputs/outputs.


213-252: Properly refactored orchestration function.

The categorize_issue function is now well under 50 lines and delegates priority determination and scope reassessment to dedicated helpers. The return type dict[str, str | int | list[str]] accurately reflects the mixed payload.


302-314: Unknown priorities now properly bucketed.

The addition of the "unknown" bucket (line 307) and the explicit fallback logic (lines 312-314) ensures issues with unexpected priority values are collected rather than silently dropped.


336-346: Summary buckets now include critical and other.

The known_prios set includes "critical" and the default dict includes an "other" bucket, addressing the past review feedback about miscounting and missing columns. Totals now equal the sum of displayed buckets.


477-478: Dynamic issue count with proper pluralization.

The header now uses {len(v064)} with conditional plural suffix, addressing the past review feedback about the hard-coded "2 issues" value.


506-517: Command validation before network call.

The command is validated against valid_commands (lines 509-513) before calling run_gh_issue_list() (line 517), addressing the past review feedback about unnecessary network requests on invalid commands.

Comment on lines +126 to +127
body_lower = body.lower()
_ = title # title may be used for future analysis
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider removing unused title parameter.

The title parameter is explicitly marked unused (_ = title) with a comment about "future analysis use." If there's no concrete plan to use it soon, removing it simplifies the API and avoids dead code. Otherwise, a TODO comment would make the intention clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/issue_analyzer.py` around lines 126 - 127, The code assigns `_ =
title` but never uses the title, so remove the unused `title` parameter from the
function signature (and delete the `_ = title` line) and update all call sites
to stop passing a title; also update the function's docstring/type hints and any
tests accordingly. If you prefer to keep it for planned work, replace `_ =
title` with a clear TODO comment like "TODO: use title in future issue analysis"
and keep a FIXME/TODO marker in the function's docstring or above the line so
intent is explicit.

Comment on lines +410 to +445
def find_dependencies(issues: list[dict]) -> list[tuple[int, int]]:
"""Find dependency chains in issue bodies.

Args:
issues: List of issue dictionaries from GitHub API.

Returns:
List of (source_issue, target_issue) dependency tuples.
"""
deps = []

for issue in issues:
body = issue.get("body", "") or ""

# Common dependency patterns
patterns = [
r"(?:depends?\s+(?:on|upon)|blocked\s+(?:by|on)|requires?|needs?|builds?\s+on)\s*[:\s]*#(\d+)",
r"(?:follow(?:s|ing|[- ]up)|extends?)\s*[:\s]*#(\d+)",
]

for pattern in patterns:
matches = re.findall(pattern, body, re.IGNORECASE)
deps.extend((issue["number"], int(m)) for m in matches)

print("\n" + "=" * 60)
print("DEPENDENCY CHAINS")
print("=" * 60)

if deps:
for src, dst in sorted(set(deps)):
print(f" #{src} -> depends on -> #{dst}")
else:
print(" No explicit dependencies found")

return deps

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Function mixes computation with side effects.

find_dependencies both prints output and returns the dependency list, but the return value is unused in main(). For better separation of concerns, consider either:

  1. Rename to print_dependencies and remove the return, or
  2. Extract printing into a separate function and have find_dependencies be pure

This is a minor design smell for a CLI script but would improve testability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/issue_analyzer.py` around lines 410 - 445, The function
find_dependencies currently mixes computation and printing; remove the printing
side effects from find_dependencies so it only returns the list of (source,
target) tuples, then create a new function print_dependencies(deps:
list[tuple[int,int]]) that contains the existing console output (header,
formatted lines, and "No explicit dependencies found" message); update main() to
call deps = find_dependencies(issues) and then call print_dependencies(deps) so
the logic is pure and printing is separated (keep the same return type for
find_dependencies and keep existing patterns and variable names such as
find_dependencies, print_dependencies, deps, and main).

@Aureliolo Aureliolo merged commit 1ccba27 into main Apr 5, 2026
27 checks passed
@Aureliolo Aureliolo deleted the feat/issue-analyzer-script branch April 5, 2026 23:50
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 5, 2026 23:50 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Apr 6, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.6.2](v0.6.1...v0.6.2)
(2026-04-06)


### Features

* add issue analyzer script for priority/scope management
([#1084](#1084))
([1ccba27](1ccba27))
* config fixes and deferred improvements from PR
[#1058](#1058) review
([#1067](#1067))
([2cac2d3](2cac2d3)),
closes [#1061](#1061)
[#1060](#1060)
* cumulative risk-unit action budgets
([#806](#806)) and automated
reporting ([#245](#245))
([#1063](#1063))
([4689816](4689816))
* fine-tuning pipeline + CompositeBackend + workflow lifecycle
([#1065](#1065))
([85b05bc](85b05bc)),
closes [#1001](#1001)
[#850](#850)
[#1058](#1058)
* memory consolidation upgrades (LLM Merge, Search-and-Ask, diversity
penalty, distillation capture)
([#1071](#1071))
([174e2be](174e2be)),
closes [#704](#704)
* migrate web dashboard from Radix UI to Base UI, activate CSP nonce,
rebuild org chart page, and fix agent routing
([#1083](#1083))
([ebc6921](ebc6921))
* v0.7.0 engine foundations -- structured failure diagnosis + auditable
decisions ([#1072](#1072))
([d341d37](d341d37))
* workflow templates and versioning with diff and rollback
([#1069](#1069))
([7af94de](7af94de)),
closes [#1006](#1006)
[#1008](#1008)


### Documentation

* unify REST API docs under /docs/openapi/ and patch sitemap
([#1073](#1073))
([af19382](af19382))


### Maintenance

* bump hypothesis from 6.151.10 to 6.151.11 in the all group
([#1086](#1086))
([3176318](3176318))
* bump nginxinc/nginx-unprivileged from `f99cc61` to `601c823` in
/docker/web in the all group
([#1085](#1085))
([5eb99ac](5eb99ac))
* bump the all group in /web with 3 updates
([#1087](#1087))
([8deae44](8deae44))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants