diff --git a/.fullsend/config.yaml b/.fullsend/config.yaml index a8623e0fcd..9f68bd490b 100644 --- a/.fullsend/config.yaml +++ b/.fullsend/config.yaml @@ -12,15 +12,15 @@ roles: - fix - retro agents: - # Retro: run only on an explicit /fs-retro comment from a human, not on - # every closed PR. + # Retro and prioritize: disabled for the initial Jira rollout. - name: retro - runtime: pi - trigger: >- - event.transition.kind == "comment_added" - && has(event.transition.comment.command) - && event.transition.comment.command == "/fs-retro" - && event.actor.kind == "human" + enabled: false + - name: prioritize + enabled: false + - name: triage + source: harness/triage.yaml + - name: code + source: harness/code.yaml allowed_remote_resources: - https://raw.githubusercontent.com/fullsend-ai/fullsend/ - https://raw.githubusercontent.com/fullsend-ai/agents/ diff --git a/.fullsend/harness/code.yaml b/.fullsend/harness/code.yaml new file mode 100644 index 0000000000..020f7ca5d8 --- /dev/null +++ b/.fullsend/harness/code.yaml @@ -0,0 +1,21 @@ +# Code harness overlay that extends the upstream fullsend-ai/agents code harness +# and narrows dispatch to: +# - issues carries exactly one `repo:..` label +# - code dispatches only when ready-to-code is added, or a comment's first line is the /fs-code command +base: https://raw.githubusercontent.com/fullsend-ai/agents/4a6645268c6c8063c15ce5cb1b88a4f3930c74da/harness/code.yaml#sha256=0cf4e64085a181606a0f33af5250cf56d8f12d4c6b91ae7bdf9f72a28e2137eb + +trigger: > + event.source.system == "jira" + && event.entity.kind == "work_item" + && "repo:lightspeed-core/lightspeed-stack" in event.state.labels + && event.state.labels.filter(label, label.startsWith("repo:")).size() == 1 + && ( + (event.transition.kind == "label_changed" + && event.transition.label.action == "added" + && event.transition.label.name == "ready-to-code") + || + (event.transition.kind == "comment_added" + && has(event.transition.comment.command) + && event.transition.comment.command == "/fs-code" + && event.actor.kind == "human") + ) diff --git a/.fullsend/harness/triage.yaml b/.fullsend/harness/triage.yaml new file mode 100644 index 0000000000..7deb0d9d91 --- /dev/null +++ b/.fullsend/harness/triage.yaml @@ -0,0 +1,32 @@ +# Triage harness overlay that extends the upstream fullsend-ai/agents triage harness +# and narrows dispatch to +# - issues that carry exactly one `repo: ` label +# - triage dispatches only when ready-to-triage is added, or a comment's +# first line is the /fs-triage command +# Automatic triage -> code handoff is disabled. +base: https://raw.githubusercontent.com/fullsend-ai/agents/4a6645268c6c8063c15ce5cb1b88a4f3930c74da/harness/triage.yaml#sha256=f935d591305e127967c4378ab9474f50da9cbd59e5dc299eebba83241faf1718 + +trigger: > + event.source.system == "jira" + && event.entity.kind == "work_item" + && "repo:lightspeed-core/lightspeed-stack" in event.state.labels + && event.state.labels.filter(label, label.startsWith("repo:")).size() == 1 + && ( + (event.transition.kind == "label_changed" + && event.transition.label.action == "added" + && event.transition.label.name == "ready-to-triage") + || + (event.transition.kind == "comment_added" + && has(event.transition.comment.command) + && event.transition.comment.command == "/fs-triage" + && event.actor.kind == "human") + ) + +# Disable automatic code-agent handoff. +env: + runner: + TRIAGE_AUTO_CODE: "off" + TRIAGE_AUTO_CODE_CATEGORIES: "" + sandbox: + TRIAGE_AUTO_CODE: "off" + TRIAGE_AUTO_CODE_CATEGORIES: "" diff --git a/.github/workflows/fullsend-poll-jira.yml b/.github/workflows/fullsend-poll-jira.yml new file mode 100644 index 0000000000..380a4ddf82 --- /dev/null +++ b/.github/workflows/fullsend-poll-jira.yml @@ -0,0 +1,87 @@ +name: fullsend Jira poll + +on: + schedule: + - cron: "*/5 * * * *" + workflow_dispatch: {} + +permissions: + actions: write + contents: write + id-token: write + issues: write + packages: read + pull-requests: write + +jobs: + poll: + if: github.event_name == 'workflow_dispatch' || vars.FULLSEND_JIRA_POLL_ENABLED == 'true' + runs-on: ubuntu-24.04 + concurrency: + group: fullsend-jira-poll + cancel-in-progress: false + outputs: + matrix: ${{ steps.build-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Install fullsend + env: + GH_TOKEN: ${{ github.token }} + run: | + # UIE: poll with the same tested CLI release as the harness jobs. + gh release download v0.53.0 --repo fullsend-ai/fullsend \ + -p 'fullsend_*_linux_amd64.tar.gz' -O - | tar xz + sudo mv fullsend /usr/local/bin/ + + - name: Poll Jira + env: + JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }} + TARGET_REPO: ${{ github.repository }} + run: | + fullsend poll \ + --input-driver jira-poll \ + --jira-url "${JIRA_BASE_URL}" \ + --jira-project LCORE \ + --jql 'project = LCORE AND labels = "repo:lightspeed-core/lightspeed-stack" AND statusCategory != Done ORDER BY updated DESC' \ + --target-repo "${TARGET_REPO}" \ + --output dispatches.json \ + --fullsend-dir .fullsend + + - name: Build dispatch matrix + id: build-matrix + run: | + set -euo pipefail + if ! jq -e 'length > 0' dispatches.json > /dev/null 2>&1; then + echo "No dispatches." + echo 'matrix={"include":[]}' >> "${GITHUB_OUTPUT}" + exit 0 + fi + MATRIX=$(jq -c '{include: .}' dispatches.json) + DELIM="MATRIX_$(openssl rand -hex 8)" + { + echo "matrix<<${DELIM}" + printf '%s' "${MATRIX}" + echo + echo "${DELIM}" + } >> "${GITHUB_OUTPUT}" + + harness: + name: Harness + needs: poll + uses: fullsend-ai/fullsend/.github/workflows/reusable-dispatch.yml@25811c9a815e2c27d397d41e45c59819b1302f54 # v0.53.0 + with: + matrix: ${{ needs.poll.outputs.matrix }} + mint_url: ${{ vars.FULLSEND_MINT_URL }} + gcp_region: ${{ vars.FULLSEND_GCP_REGION }} + jira_base_url: ${{ vars.JIRA_BASE_URL }} + fullsend_version: v0.53.0 + secrets: + FULLSEND_GCP_WIF_PROVIDER: ${{ secrets.FULLSEND_GCP_WIF_PROVIDER }} + FULLSEND_GCP_PROJECT_ID: ${{ secrets.FULLSEND_GCP_PROJECT_ID }} + OTEL_EXPORTER_OTLP_TRACES_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_TRACES_HEADERS }} + OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} + JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} diff --git a/.github/workflows/fullsend.yaml b/.github/workflows/fullsend.yaml index eefcaf447b..bc2665bdac 100644 --- a/.github/workflows/fullsend.yaml +++ b/.github/workflows/fullsend.yaml @@ -45,7 +45,7 @@ jobs: issues: write packages: read pull-requests: write - uses: fullsend-ai/fullsend/.github/workflows/reusable-dispatch.yml@f3b9d9869483093802aeb000f1d931880c9a48b5 # v0.51.0 + uses: fullsend-ai/fullsend/.github/workflows/reusable-dispatch.yml@25811c9a815e2c27d397d41e45c59819b1302f54 # v0.53.0 with: event_action: ${{ github.event.action }} mint_url: ${{ vars.FULLSEND_MINT_URL }} diff --git a/docs/contributing/fullsend-jira-integration.md b/docs/contributing/fullsend-jira-integration.md new file mode 100644 index 0000000000..d63924094e --- /dev/null +++ b/docs/contributing/fullsend-jira-integration.md @@ -0,0 +1,74 @@ +# Fullsend Jira integration + +[Fullsend]((https://fullsend.sh/docs)) is an agentic assistant onboarded to this repository. This document +describes how its Jira integration works, what does and does not trigger an agent, +and how to interact with it safely. + +## Overview + +With this integration, Fullsend can pick up work straight from the **LCORE** +Jira board and act on it in this repository, with a human in control of every +handoff. Nothing happens automatically from ordinary Jira activity: an agent +runs only when someone performs one of the explicit opt-in actions listed +below. + +## How a ticket opts in + +A Jira ticket is only visible to Fullsend when it carries the routing label: + +``` +repo:lightspeed-core/lightspeed-stack +``` + +Use exactly one `repo:` label on a ticket. Tickets without this label are +invisible to Fullsend, and a ticket carrying more than one `repo:` label is +skipped. + +## The poller + +A scheduled GitHub Actions workflow (`.github/workflows/fullsend-poll-jira.yml`) +polls the LCORE board every five minutes for non-done issues that carry the +routing label. The poller only observes; it never decides to dispatch on its own +— the per-agent trigger conditions do. + +Scheduled polling is **off by default**. It runs on a schedule only when the +GitHub Actions repository variable `FULLSEND_JIRA_POLL_ENABLED` is set to +`true` (repo Settings → Secrets and variables → Actions → Variables). Until +then, the schedule is a no-op. + +## What triggers an agent + +| Agent | Dispatches when… | Result | +| ------ | -------------------------------------------------------------------------------- | ------ | +| triage | the `ready-to-triage` label is added, **or** a comment's first line is `/fs-triage` | reads the issue and posts an assessment back to Jira | +| code | the `ready-to-code` label is added, **or** a comment's first line is `/fs-code` | implements the change and opens a GitHub pull request | + +Both require the ticket to carry the `repo:lightspeed-core/lightspeed-stack` +label, and that must be its only label that starts with `repo:` . A ticket with no such label — or with more than one label that starts with `repo:` is skipped. + +Triage does **not** automatically hand off to code: automatic triage→code +handoff is disabled by the `TRIAGE_AUTO_CODE: "off"` override in the `env:` +block of [`.fullsend/harness/triage.yaml`](../../.fullsend/harness/triage.yaml) +(set for both `runner` and `sandbox`, since both read it). The upstream default +is `"on"` for bug/documentation/performance issues, so this override is what +keeps a human in the loop: a human always decides whether work proceeds from +triage to implementation. Use `ready-to-code` as the normal, reviewable handoff. + +## What does NOT trigger an agent + +- **Ordinary Jira edits** — changing fields, status, description, assignee, or + adding comments that are not a slash command — do nothing. +- **Adding unrelated labels** does nothing. Only `ready-to-triage` and + `ready-to-code` are dispatch labels. Adding, for example, a priority or + component label will not start an agent. +- **An already-present label does not re-fire.** Re-triggering requires removing + and re-adding the label. +- **Actions by bots/automation do not dispatch.** A `/fs-triage` or `/fs-code` + slash command only dispatches when posted by a human actor, and the Jira + adapter filters recognized bot events (for example a label added by Fullsend's + own Jira account) before the trigger is evaluated. + +In short: a code change (and the pull request it opens) can only originate from +someone explicitly adding `ready-to-code` to — or commenting `/fs-code` on — a +Jira ticket that already carries this repository's `repo:` label. There is no +path by which an arbitrary label or edit causes refactoring.