Repository navigation
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Comment |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: copejon The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
9ad25e7 to
799f404
Compare
brandisher
left a comment
There was a problem hiding this comment.
Strong test coverage and the collectors are appropriately narrow in what data they pull. But stepping back, this plugin has ~2,700 lines of pipeline code (collectors, metrics, rendering, CSV export) trying to deterministically pre-solve a lot of problems that the model is already good at solving live: calling gh/Jira APIs in a loop per team member, aggregating results, formatting a table or summary. A skill doesn't need a hand-built GraphQL batching engine, a custom Jira REST client, or a 1,100-line renderer with duplicated text/markdown code paths — it can just tell the model what data to gather and how to present it, and let it write and run that logic on the fly each time.
Suggest cutting this down significantly: lean on gh and the mcp-atlassian MCP tools directly instead of reimplementing their transports, and trust the model to loop over the roster, compute the aggregates, and render the output per-invocation rather than shipping a large deterministic pipeline to maintain. Specific spots called out inline, but the overall ask is to simplify broadly, not just at these points.
| # Managers on the Eng/QE roster by role title who are not IC contributors. | ||
| # Filtering them from the team median prevents distortion of allocation signals. | ||
| # Stored as the hash256 of the member's jira username | ||
| EXCLUDED_MEMBERS = ("82e5282cc07f498ff0daf8352e7403e2f16b017dd17c3e589276e406480bb5be" |
There was a problem hiding this comment.
These are SHA-256 hashes of manager jira_usernames, presumably so the names aren't readable directly in source. But the roster itself (roster.json) has these same people in plaintext, and the comment above already explains why they're excluded (managers skew the team median). Hashing here doesn't add real privacy — it just makes the exclusion list unreviewable at a glance in code review, and brittle if anyone's jira username changes. Would a plaintext list (or pulling the exclusion from the role field already in the roster, e.g. anyone not is_engineering_or_qe) be simpler and equally safe here?
There was a problem hiding this comment.
The intent here is to keep the names out of the published source. Obfuscating names in the output artifacts isn't really a concern b/c they aren't published publicly.
There was a problem hiding this comment.
Another way of looking at this is that the list of names is an input detail rather than something that needs to be hard coded. For a one-shot approach having the names hardcoded makes sense but I wouldn't consider that a requirement for this since an individual can run it for themselves or it can be run for a team/arbitrary set of people.
There was a problem hiding this comment.
Okay. To make that work, the edge-context's roles aren't quite sufficient because they cross the include/exclude boundary we spoke about previously (e.g. Principle Software Engineer covers both ICs as well as architects / tech leads).
The simplest approach would be to add a column to the edge context roster to make the distinction, a la openshift-eng/edge-context#77.
| return JiraConfig(base_url=base_url, username=username, api_token=api_token) | ||
|
|
||
|
|
||
| class JiraClient: |
There was a problem hiding this comment.
This implements a full custom Jira REST client (auth, HTTP, pagination) via requests. This workspace already has mcp-atlassian configured with Jira search tools — could this plugin depend on that instead of maintaining its own HTTP/auth layer? Would cut a meaningful chunk of _common.py plus the test_common.py coverage for it.
There was a problem hiding this comment.
That's a good point. I don't see that wouldn't work. Will try and see
There was a problem hiding this comment.
The short answer is "kind of." Models communicate w/ mcps over jsonrpc 2.0. So instead of implementing a REST client here, it would be a jsonrpc2.0 client, add dependency on another plugin, but gain nothing (AFAICT).
I probably misread your comment. Corrected response:
It'd definitely an option to allow the model to make direct mcp calls. That said, the inputs and outputs are deterministic, which makes it's a good candidate for scripting, and saves expending tokens to execute nearly identical mcp calls for nmembers.
There was a problem hiding this comment.
At least for this iteration let's err on the side of trusting the model's MCP calls to keep the plugin simple. This is an option for improving the determinism later if we see it can't do a good job consistently.
| return f"{window.start.isoformat()}..{window.end.isoformat()}" | ||
|
|
||
|
|
||
| def build_batch_query(slots: List[SearchSlot], window: Window) -> str: |
There was a problem hiding this comment.
Suggest dropping the custom batching layer here in favor of plain gh calls — SearchSlot, build_batch_query, parse_batch_response, and graphql_rate_limit_adapter (~350 lines across lines 83–430) amount to a bespoke GraphQL query engine built on top of gh api graphql. gh search prs or per-member gh api calls would get the same data with a fraction of the code and none of the custom rate-limit/parsing logic to maintain. Team-roster-sized workloads shouldn't need this level of batching — let's lean on gh directly and cut this.
There was a problem hiding this comment.
I started out with using gh directly, but ran into some limitations that forced me onto graphql. Namely because running 1 gh search per user was consistently triggering API rate limiting. Batching via graphql has eliminated that problem.
…map) Introduce the edge-contribution Claude Code plugin: two skills over a shared, standalone, testable data-collection layer that report cross-workstream contribution for a quarter or date range. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
799f404 to
5604d33
Compare
The location and kerberos fields were parsed from the roster and serialized to roster.json, but nothing downstream (metrics.py, render.py, report.py, export_csv.py) ever read them. kerberos is still derived from the Rover URL and used transiently to build jira_username, but it's no longer persisted as a separate field. location is dropped entirely. Addresses code review feedback on PR openshift-eng#300. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previously, each summary section had two functions: _foo_text and _foo_markdown, duplicating logic with only formatting differences (markdown headers, pipe tables vs plain text). This resulted in 16 functions (8 pairs) with duplicated code. Now each section has one function with a format parameter that branches only on the line-formatting tokens. Output is byte-for-byte identical to the previous implementation. Functions refactored: - _summary_header (was _summary_header_text + _summary_header_markdown) - _summary_team - _summary_workstreams - _summary_people - _summary_data_health - _summary_what_counted - _summary_how_attributed - _summary_what_excluded Reduces render.py from 1143 to 1112 lines. All 408 tests pass. Addresses code review feedback on PR openshift-eng#300. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This was a local development planning file that accidentally slipped into the PR. Removing per code review feedback. Addresses code review feedback on PR openshift-eng#300. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ion of the contribution plugin is implemented
Replace Python REST client in collect_jira.py with MCP-based collection
orchestrated by skills. Skills now call mcp__mcp-atlassian__jira_search
directly and delegate attribution logic to a new pure Python script.
Changes:
- Add attribute_jira_activities.py: Pure attribution logic (no API calls)
extracted from collect_jira.py. Takes raw Jira issues collected via
MCP and applies same attribution chain (component → parent → project).
- Update pipeline.md step 4: Replace collect_jira.py with MCP workflow:
* Skills query Jira via mcp__mcp-atlassian__jira_search (3 queries per
member: assignee, QA contact cf[10470], OCPSTRAT SME cf[10475])
* Extract parent keys and batch-resolve via MCP
* Write intermediate raw_jira_issues.json
* Run attribute_jira_activities.py to produce jira_activity.json
- Add mcp__mcp-atlassian__jira_search to allowed-tools in all 3 skills
(heatmap, summary, export)
Architecture: Skills orchestrate MCP calls → Python handles processing.
Output format unchanged: jira_activity.json has same schema as before.
Keep collect_jira.py for rollback. Can remove after validation.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove collect_jira.py and its tests now that Jira collection happens via MCP tools in skills. Changes: - Delete bin/collect_jira.py (replaced by MCP workflow in pipeline.md) - Delete bin/tests/test_collect_jira.py (tests for removed script) - Move constants to _common.py: ACTIVITY_PROJECTS, QA_CONTACT_FIELD, SME_FIELD, UNATTRIBUTED_NO_PARENT, UNATTRIBUTED_PARENT_EMPTY - Update report.py imports to use _common instead of collect_jira - Update attribute_jira_activities.py to import constants from _common - Update README.md: Remove references to collect_jira.py, document new MCP-based architecture The old REST client is fully replaced. Skills orchestrate MCP queries, attribute_jira_activities.py handles attribution logic. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update comments that referenced the removed collect_jira.py script. Changes: - attribute_jira_activities.py: Remove "as collect_jira.py" reference in docstring (line 5) and "same format as collect_jira.py" comment (line 213) - render.py: Change "collect_jira does not gather" to passive voice "Jira comments are not collected" All functional code unchanged. Only comment cleanup after removal of collect_jira.py. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add the edge-contribution Claude Code plugin: 3 skills providing a manager-user 2 different views of edge-team member work distribution across work streams. Users are provided a data-driven analysis of contribution diversity (how many workstreams a member contributed to) and workstream silo-ing (how many contributions each workstream received). Accepts times spans in FYQ (e.g.
2026Q1), quarter-to-date, and arbitrary time spans.Skills

/edge-contribution:heatmap/edge-contribution:summary/edge-contribution:exportExports the raw data in csv format