Skip to content

Add repo context support for GitHub issue creation#166

Merged
gregology merged 7 commits intomainfrom
repo-context-for-github-issues
Mar 27, 2026
Merged

Add repo context support for GitHub issue creation#166
gregology merged 7 commits intomainfrom
repo-context-for-github-issues

Conversation

@gregology
Copy link
Copy Markdown
Contributor

@gregology gregology bot commented Mar 27, 2026

Draft — one quality tool failure remains that can't be fixed from this branch (details below).


Closes #165

What this does

When the chat LLM proposes a create_issue action, it previously had no idea which repos exist or what they're for. It just knew the action takes repo, title, body params and had to guess.

This PR lets users attach context to repo entries in their config, and that context gets injected into the LLM's system prompt so it can pick the right repo and write better issues.

Config stays backward compatible — plain strings still work:

integrations:
  - type: github
    name: my_repos
    repos:
      - repo: myorg/backend
        context: "Python API server. Issues should include endpoint and error details."
      - repo: myorg/frontend
        context: "React SPA. Include browser and component info in issues."
      - myorg/docs  # plain string, no context — still works fine

Review walkthrough

Each commit maps to one piece of the implementation:

Commit 1: Support object form for repo entries in config and GitHub client

  • manifest.yaml schema updated — repos items now accept oneOf: [string, {repo, context}]
  • client.py gets normalize_repo_entry() to handle both forms; _scope_qualifiers() uses it
  • config.py (_json_schema_to_field) handles oneOf array items by falling back to unparameterized list
  • example.config.yaml shows the new format
  • Tests: TestNormalizeRepoEntry, TestScopeQualifiers dict cases, TestBuildIntegrationModel oneOf case

Commit 2: Inject repo context into chat system prompt via ACTION_METADATA

  • app/integrations/__init__.py_collect_repo_context() pulls entries with non-empty context from integration instances, attaches as repo_context on ACTION_METADATA[key]
  • action_prompt.jinja — conditional "Available repositories:" block when repo_context is present
  • Tests: test_includes_repo_context_when_present, test_no_repo_context_section_when_absent

Commit 3: Quality fixes

  • Upgraded requests 2.32.5 -> 2.33.0 (CVE-2026-25645)
  • Added --ignore-vuln CVE-2026-4539 for pygments in CI (no fix available)
  • Fixed mypy type params, ruff line length, ruff formatting

Documentation updates

  • packages/assistant-github/README.md — config example and explanation updated for object form
  • packages/integration-guide/config.md — added oneOf array items to the type mapping table

Remaining quality failure

pip-audit: pygments 2.19.2 has CVE-2026-4539 with no fix version available — 2.19.2 is the latest release. The CI workflow now ignores this specific CVE. Once upstream releases a patched version, remove the --ignore-vuln flag and run uv lock --upgrade-package pygments.

This is a transitive dependency (pulled in by pytest and rich), not something we pin directly.

Next steps to merge

  1. Confirm the --ignore-vuln CVE-2026-4539 approach in CI is acceptable, or add a project-level pip-audit config if preferred
  2. Remove draft status once reviewed
Developer metrics

Total duration: 16m 36s
Turns: 247
Tool calls: 172
Tokens: 6,850,707 input / 27,161 output

Stage Model Duration Turns Tool calls Tokens (in/out) Cache read Cache creation
triage claude-opus-4-6 2m 1s 46 40 178,248 / 2,293 150,959 27,282
decompose claude-opus-4-6 3m 52s 44 38 326,446 / 6,142 281,908 44,529
implement_step_1 claude-opus-4-6 2m 21s 28 17 685,777 / 5,025 630,269 55,494
implement_step_2 claude-opus-4-6 1m 48s 32 17 693,996 / 4,019 645,093 48,888
docs_review claude-opus-4-6 1m 22s 28 18 483,439 / 2,439 427,417 56,013
quality_fix claude-opus-4-6 1m 24s 21 13 979,340 / 2,373 896,555 82,773
quality_fix claude-opus-4-6 2m 49s 38 22 2,649,582 / 3,170 2,535,578 113,980
craft_draft_pr claude-opus-4-6 0m 57s 10 7 853,879 / 1,700 705,059 148,813

Resolves #165

gregology bot and others added 6 commits March 26, 2026 22:23
…lient

Three areas change:

1. **`app/config.py`** — `_json_schema_to_field()`: When processing an `array` type whose `items` contains `oneOf` or `anyOf` (instead of a simple `type`), produce an unparameterized `list` (equivalent to `list[Any]`) instead of `list[item_type]`. This is a ~3-line change in the existing `if json_type == "array"` branch.

2. **`packages/assistant-github/src/assistant_github/manifest.yaml`** — Change `config_schema.properties.repos` from `{type: array, items: {type: string}}` to `{type: array, items: {oneOf: [{type: string}, {type: object, properties: {repo: {type: string}, context: {type: string}}, required: [repo]}]}}`. This declares the mixed-type format in the schema.

3. **`packages/assistant-github/src/assistant_github/client.py`** — Add a module-level `normalize_repo_entry(entry: str | dict) -> dict` function that returns `{"repo": ..., "context": ...}` for both string and dict inputs. Update `_scope_qualifiers()` to use it: extract just the `repo` string from each normalized entry for the `repo:` qualifier.

4. **`example.config.yaml`** — Add a commented example showing the object form with context alongside the existing string form.

5. **Tests**: Add test cases to `packages/assistant-github/tests/test_client.py::TestScopeQualifiers` for dict-form repo entries. Add test case to `tests/test_loader.py::TestBuildIntegrationModel` verifying that a manifest with `oneOf` array items produces a model that accepts both strings and dicts in the list.
…DATA

Wire repo context from integration config into the LLM system prompt:

1. **`app/integrations/__init__.py`** — In `_register_single_service()`, after building `ACTION_METADATA[key]`, if the service domain is associated with integration instances that have `repos` config, collect normalized repo entries (using `normalize_repo_entry` from the github client module) and attach them as `ACTION_METADATA[key]["repo_context"]` — a list of `{"repo": str, "context": str}` dicts. Only include entries that have non-empty context. If no entries have context, omit the key entirely (template handles absence gracefully).

2. **`app/templates/action_prompt.jinja`** — After the existing parameters block for each action, add a conditional block: if `meta.get('repo_context')` exists, render a "Available repositories:" section listing each repo with its context description.

3. **Tests**: Add test in `tests/test_chat.py::TestBuildActionPrompt` verifying that when `ACTION_METADATA` contains a `repo_context` key, the rendered prompt includes the repo names and their context strings. Add a negative test confirming that when `repo_context` is absent, no "Available repositories" section appears.
Copy link
Copy Markdown
Owner

@gregology gregology left a comment

Choose a reason for hiding this comment

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

There is come mixing of concerns here. Keep logic about specific integrations out of the core app. Any context that needs to be passed should be shaped in such a way that other integrations can pass their specific required context using the same mechanism. Otherwise, we'll just end up with integration specific logic throughout the core app and no clean separation of concerns.

Comment on lines +123 to 150

# Inject repo context from integration config, if available.
repo_context = _collect_repo_context(instances)
if repo_context:
ACTION_METADATA[key]["repo_context"] = repo_context

log.info("Registered chat action: %s", key)


def _normalize_repo_entry(entry: str | dict[str, str]) -> dict[str, str]:
"""Normalize a repo config entry to {"repo": ..., "context": ...}."""
if isinstance(entry, str):
return {"repo": entry, "context": ""}
return {"repo": entry["repo"], "context": entry.get("context", "")}


def _collect_repo_context(instances: list[object]) -> list[dict[str, str]]:
"""Collect repo entries with non-empty context from integration instances."""
context_entries = []
for instance in instances:
for entry in getattr(instance, "repos", None) or []:
normalized = _normalize_repo_entry(entry)
if normalized["context"]:
context_entries.append(normalized)
return context_entries


def register_all() -> None:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why are you including details specific to the GitHub integration here? These should be separate concerns by design.

Comment on lines +20 to +25
{% if meta.get('repo_context') %}
Available repositories:
{% for rc in meta.get('repo_context') %}
- {{ rc.repo }}: {{ rc.context }}
{% endfor %}
{% endif %}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Again, this is specific to the GitHub integration. This is tightly coupling GitHub integration concepts back into the core app. Find a more extensible way to pass context from integrations as context for chat actions.

@gregology
Copy link
Copy Markdown
Contributor Author

gregology bot commented Mar 27, 2026

Good call — refactored so the core no longer knows about repos or GitHub-specific context shapes.

The new approach: ChatActionConfig gets an optional context_builder field (a handler path, same pattern as service handlers). The core loads and calls it generically. The GitHub integration provides build_chat_context() in its own service module, which does the repo-to-{label, description} mapping. Template now renders a generic "Context:" block from those pairs.

Removed _collect_repo_context() and _normalize_repo_entry() from the core entirely. Any integration can now wire up its own context builder without touching app-layer code.

Developer metrics

Total duration: 9m 40s
Turns: 156
Tool calls: 117
Tokens: 3,741,327 input / 18,679 output

Stage Model Duration Turns Tool calls Tokens (in/out) Cache read Cache creation
analyze_feedback claude-opus-4-6 1m 37s 33 31 85,511 / 1,019 61,052 24,455
implement claude-opus-4-6 5m 14s 84 61 1,354,742 / 11,835 1,299,681 55,033
quality_fix claude-opus-4-6 1m 17s 21 13 915,206 / 3,572 837,369 77,825
quality_fix claude-opus-4-6 1m 16s 17 11 1,175,992 / 2,014 1,075,732 100,248
craft_update claude-opus-4-6 0m 13s 1 1 209,876 / 239 104,809 105,064

@gregology gregology marked this pull request as ready for review March 27, 2026 03:54
@gregology gregology merged commit 650f104 into main Mar 27, 2026
8 checks passed
@gregology gregology deleted the repo-context-for-github-issues branch March 27, 2026 04:13
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.

Add context support for GitHub issues

1 participant