From 203f9a7c5dd95c3ba067ae54b7b2dfdf4646f1f8 Mon Sep 17 00:00:00 2001 From: Ruchitha1608 Date: Sat, 6 Jun 2026 21:37:33 +0530 Subject: [PATCH 1/2] feat(context-graph): GitHub repo one-shot ingestion skill + CLI trigger Adds a one-shot ingestion playbook for GitHub repositories, following the same pattern as the Linear team skill merged in #839. - Playbook (domain/playbooks/github_repo_one_shot_ingestion.md): phases to enumerate commits, PRs, and issues; emits Activity, Person, Fix (merged PRs only), BugPattern, and Decision nodes. Merge commits and bot authors are explicitly skipped. BugPattern keys are stable and designed to converge with the Linear sibling skill for cross-source merge. - CLI: adds `potpie pot repo ingest ` with owner/repo validation, mirroring `potpie pot linear-team ingest`. - Tests: 6 contract checks enforcing frontmatter, bounded list calls, Fix-from- merged-PR-only rule, and current ontology edge names. --- .../adapters/inbound/cli/main.py | 83 ++++ .../github_repo_one_shot_ingestion.md | 365 ++++++++++++++++++ ...st_github_repo_one_shot_ingestion_skill.py | 86 +++++ 3 files changed, 534 insertions(+) create mode 100644 potpie/context-engine/domain/playbooks/github_repo_one_shot_ingestion.md create mode 100644 potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py diff --git a/potpie/context-engine/adapters/inbound/cli/main.py b/potpie/context-engine/adapters/inbound/cli/main.py index 6f2aa5050..a7e18cb6f 100644 --- a/potpie/context-engine/adapters/inbound/cli/main.py +++ b/potpie/context-engine/adapters/inbound/cli/main.py @@ -955,6 +955,89 @@ def pot_repo_add_cmd( ) +@pot_repo_app.command("ingest") +def pot_repo_ingest_cmd( + owner_repo: str = typer.Argument( + ..., + help="GitHub repository as owner/repo (e.g. acme/api).", + ), + pot_opt: str | None = typer.Option( + None, + "--pot", + help="Pot UUID or alias (default: active pot / git cwd).", + ), + count: int = typer.Option( + 120, + "--count", + min=1, + max=1000, + help=( + "Soft per-kind item limit; default keeps the one-shot playbook " + "under its 400 tool-call budget." + ), + ), + cwd: str | None = typer.Option( + None, + "--cwd", + help="Git repo directory for pot inference when --pot is omitted.", + ), +) -> None: + """Queue one-shot ingestion for a GitHub repo's recent commits, pull requests, and issues.""" + load_cli_env() + j, v = _flags() + raw = owner_repo.strip() + if "/" not in raw: + emit_error("Invalid repo", "Use owner/repo (e.g. org/service).", verbose=v) + raise typer.Exit(code=1) + o, rn = raw.split("/", 1) + o, rn = o.strip(), rn.strip() + if not o or not rn: + emit_error("Invalid repo", "owner and repo name required.", verbose=v) + raise typer.Exit(code=1) + raw = f"{o}/{rn}" + + pid = _pot_id_or_git(pot_opt, cwd=cwd) + client = _cli_client_or_exit(v) + repo_key = raw.lower().replace("/", ":") + source_id = f"one_shot_ingest:github:{repo_key}:{uuid.uuid4()}" + try: + status_code, data = client.submit_event( + pot_id=pid, + source_system="github", + event_type="github_repo", + action="one_shot_ingest", + source_id=source_id, + payload={"repo": raw, "count": count}, + repo_name=raw, + provider="github", + provider_host=None, + occurred_at=datetime.now(timezone.utc), + ) + except PotpieContextApiError as exc: + emit_error("GitHub repo ingest failed", _format_api_error(exc), verbose=v) + raise typer.Exit(code=1) from exc + + out = { + "status": "queued" if status_code == 202 else data.get("status", "applied"), + "pot_id": pid, + "repo": raw, + "count": count, + "source_id": source_id, + "event_id": data.get("event_id"), + "batch_id": data.get("batch_id"), + } + if status_code == 409: + out["status"] = "duplicate" + if j: + print_json_blob(out, as_json=True) + else: + print_plain_line( + f"Queued GitHub repo ingest for {raw} in pot {pid} " + f"(event {out.get('event_id') or 'unknown'}).", + as_json=False, + ) + + pot_app.add_typer(pot_repo_app, name="repo") diff --git a/potpie/context-engine/domain/playbooks/github_repo_one_shot_ingestion.md b/potpie/context-engine/domain/playbooks/github_repo_one_shot_ingestion.md new file mode 100644 index 000000000..4617476df --- /dev/null +++ b/potpie/context-engine/domain/playbooks/github_repo_one_shot_ingestion.md @@ -0,0 +1,365 @@ +--- +name: github-repo-one-shot-ingestion +description: One-time ingestion of a GitHub repo's recent commits, pull requests, and issues into the context graph. Not incremental — live updates go through the live GitHub webhook path. +source_system: github +event_type: github_repo +action: one_shot_ingest +enables_planner: true +--- + +# GitHub repo one-shot ingestion + +A reusable skill for ingesting a GitHub repository's recent commits, pull requests, and +issues into the context graph in a single pass. Sibling to the Linear +`linear_team_one_shot_ingestion` playbook: same shape, different source. Designed to +be invoked by either Claude Code (as a checklist with a compatible write path) or the +internal reconciliation agent (loaded as a playbook). + +## When to invoke + +- A user wants to seed the context graph from a GitHub repo's recent history in one pass. +- The GitHub repository is already attached to the target pot (so connector tools are + scoped to the right credentials). +- You will NOT run this skill repeatedly against the same repo window — incremental + updates flow through live GitHub merged-PR webhook events, which write the same + Activity keys so a future webhook converges with what this skill already wrote. + +## Inputs + +- `repo`: GitHub repository as `owner/repo` (required). Must be attached to the active + pot. +- `count`: soft per-kind list limit. Default `120` (chosen so the soft tool-call cap + below stays under the playbook's `max_tool_calls=400`). Read `count` from + `event.payload.count` and pass it as `limit` on each list tool. Hard ceiling: respect + whatever each bounded list tool returns — do not page past it. +- `batch_size`: items per todo. Default `10`. +- `parallel_per_batch` (`K`): items to hydrate in parallel per batch. Default `5`. Drop + lower if hydrated items prove unusually large (long PR bodies or many review comments). +- `event_id`: required for the internal reconciliation agent. The single + `(github, github_repo, one_shot_ingest)` event id for the run. + +## Tools assumed available + +- `github_list_commits(repo=repo, limit=count)` — bounded enumeration of commit refs + `{sha, message, author_login, committed_at}`, newest-first. ONE call. +- `github_list_pull_requests(repo=repo, state="closed", limit=count)` — bounded + enumeration of PR refs `{number, title, state, merged, merged_at, user_login}`, + newest-first. ONE call. +- `github_list_issues(repo=repo, state="closed", limit=count)` — bounded enumeration + of issue refs `{number, title, state, labels, closed_at}`, newest-first. ONE call. +- `github_get_commit(repo, sha)` — full commit payload: message, author, committer, + stats, changed file list. +- `github_get_pull_request(repo, pr_number)` — full PR payload: title, body, labels, + merged_at, user, linked issue references, commit count. +- `github_get_issue(repo, issue_number)` — full issue payload: title, body, labels, + state, assignee, creator, linked PRs. +- `apply_graph_mutations(plan, event_id, summary)` — context-graph write. + The `plan` argument MUST be an object with this shape: + - `summary`: string. + - `entity_upserts`: list of `{entity_key, labels, properties}`. + - `edge_upserts`: list of `{edge_type, from_entity_key, to_entity_key, properties}`. + - `edge_deletes`: usually `[]`. + - `invalidations`: usually `[]`. + - `evidence`: list of `{kind, ref, metadata}`. + - `confidence`: optional number. + - `warnings`: list of strings. +- Planner / todo tools (`read_todos`, `write_todos`, `update_todo_status`) — + REQUIRED. The todo list rides in the agent's message history and is checkpointed; + a resumed run continues the existing list instead of re-enumerating. +- `mark_event_processed(event_id, summary)` + `finish_batch(summary)` — completion. + +## Procedure + +### Phase 0 — Setup + +1. Trust the event payload that the repo is attached to the pot. If a list tool returns + an auth / not-connected error, abort with a warning. Do NOT attempt to connect the + repository from this skill. +2. Initialize the todo list with three entries: + - `Enumerate commits` + - `Enumerate pull requests` + - `Enumerate issues` + +### Phase 1 — Enumerate (three list calls, one each) + +1. Call `github_list_commits(repo=repo, limit=count)` ONCE. Bounded server-side. +2. Call `github_list_pull_requests(repo=repo, state="closed", limit=count)` ONCE. +3. Call `github_list_issues(repo=repo, state="closed", limit=count)` ONCE. +4. Drain order: commits first (they anchor the timeline spine), then PRs (they frame + code-delivery units and are the source of `Fix` entities), then issues newest-first + (they provide intent and bug evidence). +5. For each returned ref, append a todo: + `Process github ` (e.g. `Process github commit abc123`). +6. Mark each enumeration todo done via `update_todo_status` or `write_todos`. + +### Phase 2 — Drain batches + +Drain todos sequentially across kinds (commits, then PRs, then issues); within a +batch parallelize up to `K`. + +1. Choose `K` (`parallel_per_batch`, default 5). Reduce if hydrated payloads are large + (long PR bodies with many review comments). +2. In parallel for `K` items at a time, hydrate via the matching `get_*` tool: + + **COMMIT** — `github_get_commit(repo, sha)`: + - Skip merge commits — messages that start with `"Merge pull request #"` or + `"Merge branch '"` are housekeeping entries that duplicate the PR Activity + already emitted from the PR phase. Append a warning and do NOT emit an Activity + for them. + - Skip bot authors — logins ending in `[bot]` (e.g. `dependabot[bot]`, + `renovate[bot]`) produce low-signal Activity nodes. Append a warning and skip. + - Read signals in PRIORITY ORDER, stopping when intent is clear: + 1. **Commit message prefix** — conventional-commit prefix (`fix:`, `feat:`, + `chore:`, `refactor:`, `docs:`) is the highest-signal kind classifier; it's + author-applied and standardized per project. + 2. **Author login / email** — the actor for the `PERFORMED` edge. + 3. **Changed file list** — scope signal (which components or services are touched). + 4. **Stats** (additions / deletions) — scale signal. + - Classify: kind (fix/feat/chore/refactor/docs/other from prefix, fallback from + message body), author handle, 1-2 sentence functional summary. + + **PULL REQUEST** — `github_get_pull_request(repo, pr_number)`: + - Skip non-merged PRs — only `merged=true` warrants a `Fix` entity and activity + write. Append a warning and skip if `merged=false`. + - Read signals in PRIORITY ORDER: + 1. **PR title** — conventional-commit prefix or explicit `[fix]` / `[feat]` tags + are the highest-signal kind classifier. + 2. **Labels** — GitHub labels (`bug`, `enhancement`, `breaking change`, etc.) + narrow kind and flag bug evidence. + 3. **PR body** — author rationale; check for `Fixes #`, + `Closes #`, `Resolves #` links to issues. + 4. **Linked issues** — if the PR body links a `bug`-labeled issue, that anchors + the `Fix` → `BugPattern` relationship. + 5. **Commit count / list** — LAST RESORT; confirms kind when title/body are + ambiguous. + - Classify: kind (fix/feat/chore/refactor/other), PR author handle, summary. + + **ISSUE** — `github_get_issue(repo, issue_number)`: + - Read signals in PRIORITY ORDER (GitHub-adapted; equivalent to Linear's issue + procedure): + 1. **Labels** — `bug` label is the highest-signal kind classifier; it's + author-applied and standardized per repo. + 2. **Issue state** — `closed` bug issues may warrant `BugPattern` (if symptom is + clear). `open` issues never warrant `BugPattern`. + 3. **Title** — fallback kind signal if labels are absent. + 4. **Body** — author rationale; check for explicit `Why:` / `Decision:` / + `Alternatives:` sections that justify a `Decision` entity. + 5. **Comments** — LAST RESORT. Only when higher signals leave kind / outcome + ambiguous. + - Classify: author / creator handle, kind (feat/fix/chore/other), summary. + +3. Build one `LlmReconciliationPlan`-shaped object for the batch (see Mutations + section). Call `apply_graph_mutations(plan, event_id, summary)` once per batch. +4. Mark each batch todo done. Move to the next batch. + +### Phase 3 — Finalize + +When all todos are drained (or you've hit the tool-call budget with a coherent subset +complete), tally: +- Commits ingested / skipped +- PRs merged / non-merged (skipped) / skipped +- Issues ingested by kind (feat / fix / bug / chore / ...) +- Distinct authors / creators +- Fix nodes emitted, BugPattern nodes emitted, Decision nodes emitted + +Then `mark_event_processed(event_id, summary)` followed by `finish_batch(summary)`. + +## Mutations (per item) + +Use the existing ontology. Stable keys ensure backfill + future webhook converge. + +Identity rules (NOT free-form strings): + +- `Activity` is `EXTERNAL_ID` with `key_prefix=activity`. Use + `activity:github:commit:`, + `activity:github:pr://`, and + `activity:github:issue://`. Segments after `activity:` + may contain `-`, `/`, `.` per `_EXTERNAL_ID_SAFE_RE`. +- `Person` is `SLUG_ALIAS` with `key_prefix=person`. Use `person:`. +- `Period` uses the production builder + `timeline:period:daily::`. +- `Fix` is `CONTENT_HASH`. Use `fix:<12-hex-sha256>` minted from + `"github:pr://|fix|"` (stable so a re-ingest + of the same PR collides on the same key and a future merged-PR webhook event + converges). +- `BugPattern` is `SLUG_ALIAS` with `key_prefix=bug_pattern`. Use + `bug_pattern:github-:` where `repo-slug` slugifies + `owner/repo` (e.g. `acme/my-repo` → `acme-my-repo`). Each colon-separated segment + must be a valid slug. This key MUST match the key emitted by the Linear + `linear_team_one_shot_ingestion` skill for the same symptom on the same repo + (if a Linear team tracks that repo), so cross-source `BugPattern` nodes converge. +- `Decision` is `CONTENT_HASH`. Use `decision:<12-hex-sha256>` from + `"github:issue://|decision|"`. + +### Always emit (endpoint entities, at least once per batch) + +- **Entity** `Period` — one per distinct activity date in the batch. + - key: `timeline:period:daily:<pot>:<yyyy-mm-dd>`. + - labels: `["Entity", "Period"]`. + - properties: `period_kind="daily"`, `label="<yyyy-mm-dd>"`, + `opened_at="<yyyy-mm-dd>T00:00:00+00:00"`. + +### Per COMMIT — always emit + +- **Entity** `Activity` + - key: `activity:github:commit:<sha-lowered>`. + - labels: `["Entity", "Activity"]`. + - properties: `occurred_at=<committed_at>`, `verb="github_commit"`, + `title=<first line of commit message>`, `summary=<1-2 sentence summary>`, + `kind` (fix/feat/chore/refactor/docs/other), `sha=<full sha>`, + `repo_name=<owner/repo>`. +- **Entity** `Person` for the commit author. + - key: `person:<github_login_lowered>`. + - labels: `["Entity", "Person"]`. + - properties: `name=<display_name>`, `handle=<login>`. +- **Edge** `PERFORMED` — `person:<author>` → activity key, + `valid_from=<committed_at>`. +- **Edge** `IN_PERIOD` — activity key → period key (date from `committed_at`). + +### Per PULL REQUEST (merged only) — always emit + +- **Entity** `Activity` + - key: `activity:github:pr:<owner>/<repo>/<pr_number>`. + - labels: `["Entity", "Activity"]`. + - properties: `occurred_at=<merged_at>`, `verb="github_pr_merged"`, + `title=<pr_title>`, `summary=<1-2 sentence summary>`, + `pr_number=<number>`, `pr_url=<html_url>`, + `kind` (fix/feat/chore/refactor/other), `repo_name=<owner/repo>`. +- **Entity** `Person` for the PR author. + - key: `person:<github_login_lowered>`. + - labels: `["Entity", "Person"]`. + - properties: `name=<display_name>`, `handle=<login>`. +- **Edge** `PERFORMED` — `person:<author>` → activity key, + `valid_from=<merged_at>`. +- **Edge** `IN_PERIOD` — activity key → period key. + +### Per PULL REQUEST (merged, fixing a confirmed bug) — conditionally emit + +Emit when (a) the PR title or labels signal `bug` / `fix` AND (b) the PR body links +a bug issue via `Fixes #N`, `Closes #N`, or `Resolves #N`, OR the PR labels include +`bug` / `bugfix`. + +- **Entity** `Fix` + - key: `fix:<12-hex-sha256>` from + `"github:pr:<owner>/<repo>/<pr_number>|fix|<symptom-slug>"`. + - labels: `["Entity", "Fix"]`. + - properties: `title=<pr_title>`, `summary=<short symptom sentence>`, + `source_pr=<activity_key>`, `pr_url=<html_url>`, + `repo_name=<owner/repo>`. +- **Entity** `BugPattern` + - key: `bug_pattern:github-<repo-slug>:<symptom-slug>`. + - labels: `["Entity", "BugPattern"]`. + - properties: `summary=<short canonical symptom>`, + `symptom_signature=<short canonical sentence>`, + `title=<symptom title>`, `source_pr=<activity_key>`. +- **Edge** `RESOLVED` — fix key → bug_pattern key (only when both are emitted). + +### Per ISSUE — always emit + +- **Entity** `Activity` + - key: `activity:github:issue:<owner>/<repo>/<issue_number>`. + - labels: `["Entity", "Activity"]`. + - properties: `occurred_at=<closed_at or created_at>`, + `verb="github_issue_<state>"` (e.g. `github_issue_closed`), + `title=<issue_title>`, `summary=<1-2 sentence summary>`, + `state=<state>`, `kind` (feat/fix/chore/other), `issue_url`. +- **Entity** `Person` per creator (and assignee if distinct and the issue is closed). + - key: `person:<github_login_lowered>`. + - labels: `["Entity", "Person"]`. + - properties: `name=<display_name>`, `handle=<login>`. +- **Edge** `PERFORMED` — `person:<creator>` → activity key, + `valid_from=<occurred_at>`. +- **Edge** `PERFORMED` — `person:<assignee>` → activity key, + `valid_from=<occurred_at>` and `role="assignee"`, only when the assignee + differs from the creator and the issue is closed. +- **Edge** `IN_PERIOD` — activity key → period key. + +### Per ISSUE — conditionally emit + +- **Bug report** (labels include `bug` AND state is `closed` AND symptom is clear in + title or body): + - **Entity** `BugPattern` + - key: `bug_pattern:github-<repo-slug>:<symptom-slug>`. + - labels: `["Entity", "BugPattern"]`. + - properties: `summary=<short canonical symptom sentence>`, + `symptom_signature=<short canonical sentence>`, + `title=<symptom title>`, `source_issue=<activity_key>`. + - NOTE: This key converges with the `BugPattern` emitted by a merged fixing PR + (from this skill's PR phase) and with the Linear sibling skill — all write the + same stable `bug_pattern:` key so cross-source nodes merge. + - **Do NOT emit `Fix`** from a GitHub issue — even a closed `bug` issue. Fix is + reserved for the merged PR that shipped the code change. + - **Do NOT emit `RESOLVED`** from an issue (that edge requires a `Fix`, and issues + never emit `Fix`). +- **Design decision** (body explicitly documents rationale + alternatives — common for + RFCs, specs, ADRs filed as issues): + - **Entity** `Decision` + - key: `decision:<12-hex-sha256>` from + `"github:issue:<owner>/<repo>/<number>|decision|<title>"`. + - labels: `["Entity", "Decision"]`. + - properties: `title=<short title>`, + `summary=<one sentence decision summary>`, `status="accepted"`, + `rationale=<stated rationale>`, + `alternatives_rejected=<list or string>`, + `source_issue=<activity_key>`. + - **Edge** `AFFECTS` — decision key → feature/component/service/code asset, ONLY + when a single affected scope is clear. Otherwise omit. + +## Source-priority rationale (why) + +GitHub's structured signals — conventional-commit prefixes, PR merge status, issue +labels — are author-applied and standardized per project. They beat free-form bodies +for kind / outcome classification. A commit message prefix of `fix:` or a PR label of +`bug` encodes intent that would take multiple comment reads to recover. PR merge status +gates `Fix` emission: an un-merged PR is a proposal, not a delivered change. Reading +full diffs, review threads, or comment history burns budget on rediscovering intent the +author already encoded in metadata. Stop climbing the priority ladder as soon as you +can answer kind + summary + bug/decision evidence. + +## Bounds and budget + +- ONE call each of `github_list_commits`, `github_list_pull_requests`, + `github_list_issues`. No pagination beyond what the bounded list tools return. +- Soft tool-call cap: `30 + 3 × count` (each item averages ~3 calls: + list + get + classify). Plan accordingly. +- If you approach the cap with a coherent recent subset ingested cleanly, FINISH — + do not partially ingest an item. The tail can be re-run later with a smaller `count` + (stable keys mean already-ingested items will be deduplicated, not duplicated). + +## Anti-patterns + +- Do NOT emit Activity for merge commits (messages starting with + `"Merge pull request #"` or `"Merge branch '"`). These duplicate the PR Activity + from the PR phase and produce noise in the timeline. +- Do NOT emit Person or Activity for bot authors (logins ending in `[bot]`). + Dependabot, Renovate, and similar bots are not human contributors. +- Do NOT emit `Fix` from a GitHub issue — even a closed `bug` issue. Fix is reserved + for the merged PR that shipped the code change. A closed issue is evidence the team + believes it is fixed; it is NOT itself a Fix. +- Do NOT emit `RESOLVED` from a GitHub issue (RESOLVED connects Fix → BugPattern, and + issues never emit Fix). +- Do NOT process non-merged PRs as `Fix` candidates — skip them with a warning. +- Do NOT page past the bounded list calls. +- Do NOT read full commit diffs or PR review-comment threads unless kind / summary + cannot be derived from the commit message, PR title, and labels. +- Do NOT invent BugPatterns, Fixes, Decisions, or Persons not actually evidenced in the + data you read. Emit a warning record instead. +- Do NOT auto-close / auto-resolve any open issue or incident based on a GitHub + `closed` status alone — the status is evidence, not closure. +- Do NOT run this skill on a repo that already has live merged-PR webhook ingestion + going against the SAME date window — let the webhook handle live updates. + +## Single-event contract + +This skill, when invoked by the internal agent, runs as a single +`(github, github_repo, one_shot_ingest)` event. Pass that ONE `event_id` to every +`apply_graph_mutations` call and to the final `mark_event_processed` — per-item +identity is the entity_key (`activity:github:commit:...`, `fix:<hash>`, etc.), not +the event id, so multiple Activities / Fixes / BugPatterns under one event id is +correct. + +When invoked by Claude Code outside the event pipeline, there is no internal-agent +event state, so the internal `apply_graph_mutations` tool will reject an empty or +invented event id. Use this document as the extraction procedure only when the host +provides a compatible context-graph write path and a valid event/provenance id. +Otherwise stop after producing the proposed plan; do not pretend to apply it. diff --git a/potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py b/potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py new file mode 100644 index 000000000..30fe5c9bf --- /dev/null +++ b/potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py @@ -0,0 +1,86 @@ +"""Contract checks for the GitHub repo one-shot ingestion playbook.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.unit + + +SKILL_PATH = ( + Path(__file__).resolve().parents[2] + / "domain" + / "playbooks" + / "github_repo_one_shot_ingestion.md" +) + + +def _read_skill() -> tuple[dict[str, str], str]: + raw = SKILL_PATH.read_text(encoding="utf-8") + assert raw.startswith("---\n") + end = raw.find("\n---\n", 4) + assert end > 0 + frontmatter: dict[str, str] = {} + for line in raw[4:end].splitlines(): + if ":" not in line: + continue + key, _, value = line.partition(":") + frontmatter[key.strip()] = value.strip() + return frontmatter, raw[end + 5:] + + +def test_github_repo_skill_frontmatter_targets_one_shot_event() -> None: + frontmatter, _ = _read_skill() + + assert frontmatter["source_system"] == "github" + assert frontmatter["event_type"] == "github_repo" + assert frontmatter["action"] == "one_shot_ingest" + assert frontmatter["enables_planner"].lower() == "true" + + +def test_github_repo_skill_uses_bounded_list_calls() -> None: + _, body = _read_skill() + + assert 'github_list_commits(repo=repo, limit=count)' in body + assert 'github_list_pull_requests(repo=repo, state="closed", limit=count)' in body + assert 'github_list_issues(repo=repo, state="closed", limit=count)' in body + assert "do not page" in body.lower() + + +def test_github_repo_skill_allows_fix_from_merged_prs() -> None: + _, body = _read_skill() + lowered = body.lower() + + assert "fix" in lowered + assert "merged" in lowered + assert "github_pr_merged" in lowered + assert "`Fix`" in body + + +def test_github_repo_skill_forbids_fix_from_issues() -> None: + _, body = _read_skill() + lowered = body.lower() + + assert "do not emit `fix` from a github issue" in lowered + assert "do not emit `resolved` from a github issue" in lowered + + +def test_github_repo_skill_uses_current_timeline_ontology_names() -> None: + _, body = _read_skill() + + assert "`PERFORMED`" in body + assert "`IN_PERIOD`" in body + assert "`RESOLVED`" in body + assert "`valid_from=<" in body + assert '`period_kind="daily"`, `label="<yyyy-mm-dd>"`' in body + for stale in ("`MENTIONS`", "`AUTHORED`", "`DECIDED`", "`verb_class="): + assert stale not in body + + +def test_github_repo_skill_bugpattern_key_converges_with_linear() -> None: + _, body = _read_skill() + + assert "bug_pattern:github-<repo-slug>:<symptom-slug>" in body + assert "converge" in body.lower() From 099f002d484ccf8f04cd593a719bda07a9fd675e Mon Sep 17 00:00:00 2001 From: Ruchitha1608 <ruchithareddy890@gmail.com> Date: Sat, 6 Jun 2026 21:52:11 +0530 Subject: [PATCH 2/2] fix: add docstrings and use regex for robust test assertions Address CodeRabbit review: add docstrings to all test functions and helper to meet 80% coverage threshold; replace fragile lowercased string checks with re.search(IGNORECASE) in test_github_repo_skill_forbids_fix_from_issues. --- .../test_github_repo_one_shot_ingestion_skill.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py b/potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py index 30fe5c9bf..9b80f940c 100644 --- a/potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py +++ b/potpie/context-engine/tests/unit/test_github_repo_one_shot_ingestion_skill.py @@ -2,6 +2,7 @@ from __future__ import annotations +import re from pathlib import Path import pytest @@ -18,6 +19,7 @@ def _read_skill() -> tuple[dict[str, str], str]: + """Parse the playbook frontmatter and body; assert the file is well-formed.""" raw = SKILL_PATH.read_text(encoding="utf-8") assert raw.startswith("---\n") end = raw.find("\n---\n", 4) @@ -32,6 +34,7 @@ def _read_skill() -> tuple[dict[str, str], str]: def test_github_repo_skill_frontmatter_targets_one_shot_event() -> None: + """Frontmatter must target the (github, github_repo, one_shot_ingest) event triple.""" frontmatter, _ = _read_skill() assert frontmatter["source_system"] == "github" @@ -41,6 +44,7 @@ def test_github_repo_skill_frontmatter_targets_one_shot_event() -> None: def test_github_repo_skill_uses_bounded_list_calls() -> None: + """Playbook must use exactly one bounded list call per kind and prohibit pagination.""" _, body = _read_skill() assert 'github_list_commits(repo=repo, limit=count)' in body @@ -50,6 +54,7 @@ def test_github_repo_skill_uses_bounded_list_calls() -> None: def test_github_repo_skill_allows_fix_from_merged_prs() -> None: + """Playbook must allow Fix emission from merged PRs (unlike the Linear sibling skill).""" _, body = _read_skill() lowered = body.lower() @@ -60,14 +65,15 @@ def test_github_repo_skill_allows_fix_from_merged_prs() -> None: def test_github_repo_skill_forbids_fix_from_issues() -> None: + """Playbook must explicitly forbid Fix and RESOLVED emission from GitHub issues.""" _, body = _read_skill() - lowered = body.lower() - assert "do not emit `fix` from a github issue" in lowered - assert "do not emit `resolved` from a github issue" in lowered + assert re.search(r"do\s+not\s+emit\s+`[Ff]ix`\s+from\s+a\s+github\s+issue", body, re.IGNORECASE) + assert re.search(r"do\s+not\s+emit\s+`[Rr]esolved`\s+from\s+a\s+github\s+issue", body, re.IGNORECASE) def test_github_repo_skill_uses_current_timeline_ontology_names() -> None: + """Playbook must use current ontology edge names and forbid stale aliases.""" _, body = _read_skill() assert "`PERFORMED`" in body @@ -80,6 +86,7 @@ def test_github_repo_skill_uses_current_timeline_ontology_names() -> None: def test_github_repo_skill_bugpattern_key_converges_with_linear() -> None: + """BugPattern keys must be stable and designed to converge with the Linear sibling skill.""" _, body = _read_skill() assert "bug_pattern:github-<repo-slug>:<symptom-slug>" in body