Skip to content

feat: add explore, refine, critique refinement pipeline agents#2439

Draft
ascerra wants to merge 8 commits into
mainfrom
feat/refinement-pipeline-agents
Draft

feat: add explore, refine, critique refinement pipeline agents#2439
ascerra wants to merge 8 commits into
mainfrom
feat/refinement-pipeline-agents

Conversation

@ascerra

@ascerra ascerra commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds three new first-class agents forming a chained refinement pipeline: explore → refine → critique, triggered by /fs-refine on an issue. Includes Jira integration with credential isolation.

  • Explore: Read-only research agent that analyzes issues and gathers context
  • Refine: Decomposes issues into structured implementation plans with child issues
  • Critique: Quality gate that approves, requests revisions (max 3 rounds), or escalates to humans
  • Jira support: Static API tokens with credential isolation, comment poller, dispatch workflows, ADF conversion
  • Two ADRs: 0049 — Pipeline Architecture, 0050 — Jira Integration Model

Review guide

~70% of this diff is docs, declarative config, and tests. Focus review time here:

Priority Files What to look for
High scripts/post-critique.sh Verdict routing, max-rounds escalation, revision loop
High scripts/create-children.sh Topological ordering, Jira type resolution, orphan fallback
High scripts/pre-explore.sh Jira fetch, private-Jira-on-public-repo safety check
High scripts/sanitize-artifacts.sh PII redaction completeness
Medium scripts/pipeline-helpers.sh Shared comment/label/dispatch helpers
Medium Scaffold workflows (refine-dispatch.yml, jira-dispatch.yml, jira-comment-poller.yml) Authorization checks, command parsing
Low Reusable workflows, harness/policy/schema YAML, agent prompts, Go code Structural — follows existing patterns

What's NOT included (Phase 2+)

Test plan

  • make go-test — all packages pass (scaffold, harness, config, forge/github)
  • make go-vet — clean
  • make script-test — 5 new test scripts pass (post-explore, post-refine, post-critique, create-children, sanitize-artifacts)
  • Workflow call alignment validated for all 10 stages (6 existing + 4 new)
  • Harness discovery updated and validated (6 → 9 agents)
  • Security scan: no hardcoded secrets, tokens, or internal hostnames in code paths
  • Manual integration test on staging org with /fs-refine (planned)
  • CI pipeline green

Made with Cursor

@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Site preview

Preview: https://97fc4ff5-site.fullsend-ai.workers.dev

Commit: 6b8fed42df2536d3c2053476a894f056f0f7af2e

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:11 PM UTC · Ended 5:21 PM UTC
Commit: 4e21a60 · View workflow run →

ascerra and others added 3 commits June 18, 2026 13:22
Three new first-class agents forming a chained refinement pipeline:
explore → refine → critique, with artifact-based data passing and
revision loops (max 3 rounds).

Includes: Go role registration, GitHub App configs, scaffold files
(agents, harness, policies, schemas, scripts, skills, env, workflows),
reusable workflows, Jira integration with credential isolation,
JIRA_PROJECT_VISIBILITY safety check, shared pipeline-helpers.sh,
and two ADRs (0049 pipeline architecture, 0050 Jira integration).

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Adam Scerra <ascerra@redhat.com>
- Merge TestHarnessForgeRunnerEnvMergeRefinementPipeline into existing
  TestHarnessForgeRunnerEnvMerge table to avoid duplicate scaffold extraction
- Add Jira secret threading assertions (JIRA_HOST, JIRA_EMAIL,
  JIRA_API_TOKEN, jira_project_visibility) to refine/critique content tests
- Fix post-explore-test.sh no-op assertion (replace && true with real check)
- Add /tmp/workspace cleanup to post-critique-test.sh trap to prevent CI state leakage
- Add assert_gh_not_called helpers to explore/refine test scripts
- Add invalid JSON test case to post-refine-test.sh

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Adam Scerra <ascerra@redhat.com>
- create-children: actions:write → actions:read (terminal step, no dispatch)
- Renumber ADRs: 0049→0051, 0050→0052 (avoid conflicts with other branches)
- Add required YAML frontmatter to both ADRs
- Add missing agent doc sections (Control labels, Configuration and extension)
- Add OPTIONAL_SECTIONS to hack/lint-agent-docs for pipeline-specific headings
- Add placeholder agent icons (explore, refine, critique)
- Fix ruff lint/format violations in markdown-to-adf.py
- Fix shellcheck SC2034 (HAS_PE) and SC2129 (redirect grouping)
- Fix trailing newlines in policy YAML files
- Add header comments to reusable workflows matching existing convention

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Adam Scerra <ascerra@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:27 PM UTC · Ended 5:29 PM UTC
Commit: 4e21a60 · View workflow run →

The *.env gitignore rule prevented these from being committed.
Force-add to match existing agent env files (triage.env, review.env, etc.).

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:32 PM UTC · Ended 5:40 PM UTC
Commit: 4e21a60 · View workflow run →

@codecov

codecov Bot commented Jun 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:44 PM UTC · Completed 6:21 PM UTC
Commit: 6728544 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [protected-path] .github/workflows/reusable-create-children.yml, .github/workflows/reusable-critique.yml, .github/workflows/reusable-explore.yml, .github/workflows/reusable-refine.yml — Four workflow files under .github/ are added. This PR has no linked issue to justify adding governance/infrastructure files. Human approval is required for all protected-path changes.
    Remediation: Link this PR to an authorizing issue that documents the decision to add the refinement pipeline, and ensure human reviewers explicitly approve the workflow additions.

Medium

  • [missing-authorization] This PR adds ~7400 lines across 64 files introducing a complete refinement pipeline (explore, refine, critique agents) with Jira integration, two ADRs, and new dispatch workflows. No linked issue authorizes this work. The PR includes ADRs (0051, 0052) documenting the design, but non-trivial features require explicit authorization via a linked issue.
    Remediation: Link this PR to an authorizing issue that defines the scope and tier.

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml:148 — The follow-up path (IS_FOLLOWUP=true) searches for the most recent successful explore run without filtering by issue number. The jq query selects the newest successful fullsend-explore run regardless of which issue it was for. If multiple issues have had explore runs, the wrong exploration context will be attached to the refine stage.
    Remediation: Add a displayTitle filter matching the issue number to the gh run list jq query, consistent with how the /fs-create path searches for refine runs.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/create-children.sh:80 — The orphan parent_title validation uses jq inside() which performs substring matching. [.parent_title] | inside($titles) returns true if parent_title is a substring of any title in the array, not an exact match. A parent_title of "Cache" would match a child titled "Cache Layer Implementation", suppressing the orphan warning. The actual creation loop (line ~205) uses exact associative-array lookup, so runtime behavior is correct but the validation warning is misleading.
    Remediation: Replace inside() with exact matching, e.g., select(.parent_title as $pt | $titles | any(. == $pt) | not).

  • [architectural-divergence] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml — The refinement pipeline introduces separate dispatch workflows using workflow_dispatch + gh workflow run, which ADR 0041 eliminated in favor of synchronous workflow_call. ADR 0051 explicitly acknowledges this divergence and defers consolidation to Phase 2 (blocked on Harness-driven dispatch routing: eliminate per-agent workflow files for custom agents #1985). The divergence is documented but creates a split architecture.
    Remediation: Track consolidation as part of Harness-driven dispatch routing: eliminate per-agent workflow files for custom agents #1985. Consider noting the divergence in docs/architecture.md.

  • [architectural-divergence] internal/scaffold/fullsend-repo/scripts/post-explore.sh — Agent chaining via workflow_dispatch (post-explore→refine, post-refine→critique, post-critique→refine for revision loops) is a new orchestration pattern. ADR 0051 documents this as a deliberate design decision with alternatives considered (single-workflow pipeline, issue comments, repository dispatch).

  • [scope-creep] docs/ADRs/0052-jira-integration-model.md — PR title is "add explore, refine, critique refinement pipeline agents" but scope includes full Jira integration (ADR 0052, jira-dispatch.yml, jira-comment-poller.yml, markdown-to-adf.py, JIRA_* secrets). Jira integration is a separate architectural concern that could be delivered independently.
    Remediation: Split Jira integration into a separate PR, or update PR description to explicitly scope both features with rationale for bundling.

  • [missing-architecture-doc-update] docs/architecture.md — ADRs 0051 and 0052 are marked Accepted and introduce agent chaining and Jira integration, but docs/architecture.md is not updated. Per AGENTS.md: "When an ADR is accepted, update docs/architecture.md in the same PR." The existing architecture.md mentions eliminating workflow_dispatch + gh workflow run (per ADR 0041), but the new pipeline reintroduces this pattern.
    Remediation: Update docs/architecture.md to document the agent chaining pattern and refinement pipeline.

  • [stale-doc] docs/reference/github-setup.md:112 — The --agents parameter default value lists fullsend,triage,coder,review,retro,prioritize but DefaultAgentRoles() now returns 9 roles including explore, refine, critique.
    Remediation: Update to include the three new agent roles.

  • [stale-doc] docs/reference/installation.md:246 — Same stale --agents default value.
    Remediation: Update to include the three new agent roles.

  • [stale-doc] docs/ADRs/0033-per-repo-installation-mode.md:258 — Same stale --agents default value. Note: ADRs are point-in-time records; updating the default value is a minor annotation.
    Remediation: Update to include the three new agent roles.

  • [New Public API Schema] internal/scaffold/fullsend-repo/schemas/explore-result.schema.json, refine-result.schema.json, critique-result.schema.json — Three new JSON schemas defining agent output contracts. Embedded into scaffold via go:embed and distributed to all installations. No existing consumers to break, but these become the contract surface once deployed. The critique schema uses complex allOf/if/then conditional validation controlling pipeline behavior (verdict→required fields).
    Remediation: Ensure schema versioning strategy before first stable release. Document the conditional contracts in critique-result.

  • [Public API Extension] internal/config/config.goValidRoles() expanded from 7→10 roles, DefaultAgentRoles() expanded from 6→9 roles. New installations will require 3 additional GitHub Apps (10 total).
    Remediation: Document in release notes. Backward compatible for existing installations.

  • [Workflow Input Contract] internal/scaffold/fullsend-repo/.github/workflows/refine.yml — New workflow_dispatch inputs define the cross-workflow agent chaining protocol (issue_key, explore_run_id, review_round, critique_run_id, etc.). Multiple callers depend on this contract (post-explore.sh, post-critique.sh, refine-dispatch.yml, jira-dispatch.yml).
    Remediation: Document the chaining protocol as a formal contract in ADR 0051 or a supporting doc.

Low

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/explore.yml:38 — Accepts auto_create and repo_full_name as workflow_dispatch inputs but does not forward them to the reusable workflow call. Post-explore.sh handles missing values gracefully (degraded behavior, not failure).

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.yml:107ALREADY_ACKED jq filter uses contains() for substring matching on comment IDs. Could cause false positive ack detection if one comment ID is a numeric substring of another.

  • [missing-runtime-mechanism] internal/scaffold/fullsend-repo/harness/explore.yaml:39TARGET_REPO_DIR not in explore harness runner_env. The reusable workflow sets it via setup-agent-env.sh, but it's not declared in the harness YAML like other env vars.

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-explore.sh:92gh workflow run refine.yml redirects stderr to /dev/null, swallowing diagnostic error messages. Same pattern in post-critique.sh.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/create-children.sh:108github_create_issue extracts issue number using grep -oP '/issues/\K[0-9]+'. Fragile if gh CLI output format changes.

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml:169/fs-create path dispatches create-children.yml with -f github_issue_number and -f repo_full_name, but create-children.yml does not declare these inputs. GitHub Actions silently drops them.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.yml:83 — Jira comment body content echoed to stdout without stop-commands guard. Limited blast radius (restricted permissions).
    Remediation: Wrap echo blocks in stop-commands guard, matching the pattern used in jira-dispatch.yml.

  • [authorization-bypass] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml:17 — Issue creators (including external contributors on public repos) can trigger the pipeline via follow-up comments. Likely intentional but worth documenting.

  • [schema-version-inconsistency] internal/scaffold/fullsend-repo/schemas/critique-result.schema.json:2 — All three new schemas use JSON Schema draft-07 while existing schemas use draft/2020-12.
    Remediation: Change to draft/2020-12 to match existing schemas.

  • [phase-clarity] docs/ADRs/0051-refinement-pipeline-architecture.md — ADR marked Accepted but uses Phase 1/Phase 2 framing extensively. Consider clarifying whether Phase 2 will supersede this ADR.

  • [stale-test-expectation] docs/plans/adr-0045-forge-portable-harness-phase2.md:407 — Test expectation states "DiscoverAgents returns 6 agents" but scaffold now has 9 agents.

  • [stale-doc] docs/ADRs/0052-jira-integration-model.md — Empty relates_to field. Should reference ADR 0051.

Info

  • [permission-manifest-change] internal/forge/github/types.go:139 — Three new GitHub App role configurations added (explore, refine, critique) with actions:write, contents:read, issues:write. Permissions follow least-privilege for the stated use case.

  • [credential-isolation] internal/scaffold/fullsend-repo/scripts/pre-explore.sh — Jira credentials correctly isolated to pre/post scripts on the trusted GHA runner. Agent sandbox does not receive credentials. Credential isolation design is sound.

  • [fail-closed] internal/scaffold/fullsend-repo/scripts/pre-explore.sh:50 — Prior fail-open finding addressed. Repo visibility now defaults to "public" on API failure, triggering the private-Jira-on-public-repo security block. Same fix applied in jira-comment-poller.yml and jira-dispatch.yml.

  • [concurrency-group-pattern-variation] internal/scaffold/fullsend-repo/.github/workflows/explore.yml:42 — New workflows use simplified concurrency group pattern (issue_key only) vs existing pattern (source_repo + issue number). Reasonable for cross-platform (GitHub + Jira) support.

  • [Artifact Naming Convention] internal/scaffold/fullsend-repo/.github/workflows/explore.yml — Artifact naming (fullsend-explore, fullsend-refine) is an implicit contract enforced by gh run download commands in downstream scripts. Consider documenting as part of the chaining protocol.

Previous run

Review

Findings

High

  • [protected-path] .github/workflows/reusable-create-children.yml, .github/workflows/reusable-critique.yml, .github/workflows/reusable-explore.yml, .github/workflows/reusable-refine.yml — Four reusable workflow files under .github/ are modified. This PR has no linked issue to justify modifying governance/infrastructure files. Human approval is required for all protected-path changes.
    Remediation: Link this PR to an authorizing issue that documents the decision to add the refinement pipeline, and ensure human reviewers explicitly approve the workflow changes.

Medium

  • [logic-error] internal/scaffold/fullsend-repo/scripts/create-children.sh:80 — The orphan parent_title validation uses jq inside() which performs substring matching on strings. [.parent_title] | inside($titles) returns true if parent_title is a substring of any title in the children array, not an exact match. A parent_title of "API" would falsely match a child titled "Add API Gateway", suppressing the orphan warning.
    Remediation: Replace select([.parent_title] | inside($titles) | not) with select(.parent_title as $pt | $titles | any(. == $pt) | not) for exact string matching.

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.ymlDISPATCHED counter is incremented inside a pipeline subshell (jq | while read), so the increment never propagates back to the parent shell. The counter remains 0 in logging output regardless of how many dispatches occur.
    Remediation: Use process substitution (while read ... < <(jq ...)) instead of a pipe, or remove the misleading counter.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/jira-dispatch.yml:66RAW_COMMAND from inputs.command || github.event.client_payload.command is echoed unsanitized via echo "Full command: ${RAW_COMMAND}". A repository_dispatch sender can embed :: workflow commands. Additionally, TARGET_REPO parsed from RAW_COMMAND is echoed inside ::notice:: without sanitization.
    Remediation: Sanitize RAW_COMMAND and TARGET_REPO before echoing by stripping :: sequences, or use stop-commands guards around echo statements with user-controlled values.

  • [fail-open] internal/scaffold/fullsend-repo/scripts/pre-explore.sh:50 — The private-Jira-on-public-repo safety check defaults REPO_VISIBILITY to "unknown" when gh api fails. Since the check only blocks when visibility equals "public", an API failure causes the check to pass, potentially allowing private Jira data to leak into public repo artifacts. Same pattern in jira-dispatch.yml and jira-comment-poller.yml.
    Remediation: Treat unknown visibility as unsafe — block on anything other than "private" or "internal", or fail the check when the API call fails.

  • [stale-doc] docs/reference/github-setup.md:112 — The --agents parameter default value lists fullsend,triage,coder,review,retro,prioritize but code now includes explore, refine, critique. Documentation would mislead operators about which agents are provisioned by default.
    Remediation: Update to include the three new agent roles.

  • [stale-doc] docs/reference/installation.md:246 — Same --agents parameter default value is stale, missing explore, refine, critique.
    Remediation: Update to include the three new agent roles.

  • [stale-doc] docs/ADRs/0033-per-repo-installation-mode.md:258 — Same --agents parameter default value is stale, missing explore, refine, critique.
    Remediation: Update to include the three new agent roles.

Low

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/explore.yml — The explore.yml scaffold accepts auto_create and repo_full_name as workflow_dispatch inputs but does not forward them via the with: block to the reusable workflow call. The auto_create value IS available via harness runner_env, but the input forwarding path is incomplete.

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.ymlALREADY_ACKED jq filter uses contains() for substring matching on comment IDs, which could cause false positives when one ID is a substring of another.

  • [missing-runtime-mechanism] internal/scaffold/fullsend-repo/harness/explore.yamlTARGET_REPO_DIR is not in explore harness runner_env, so the explore agent falls back to remote-only analysis via gh api even when a local checkout is available.

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-explore.sh:92 — The gh workflow run refine.yml call redirects stderr to /dev/null, swallowing diagnostic error messages. Same pattern in post-critique.sh.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/create-children.sh:108github_create_issue extracts the issue number from gh issue create output using grep -oP '/issues/\K[0-9]+'. If the output format changes, issue_number will be empty, preventing sub-issue linking.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.yml:83 — Jira comment content (BODY, AUTHOR, COMMAND_LINE) is echoed directly to GHA stdout without sanitization. Blast radius is limited (poller runs with restricted permissions).

  • [authorization-bypass] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml:17 — Issue creators (including external contributors on public repos) can re-trigger the pipeline via follow-up comments when refine-needs-input or human-refinement labels are present. Likely intentional but worth documenting.

  • [scope-coherence] docs/ADRs/0051-refinement-pipeline-architecture.md — ADR 0051 claims "Phase 1" but includes substantial Jira integration. The ADRs do clarify scope, but the Phase 1/Phase 2 framing could be clearer.

  • [architectural-divergence] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml — Separate dispatch workflows diverge from established dispatch pattern (ADRs 0026, 0041). ADR 0051 acknowledges this and defers consolidation to Phase 2 (blocked on Harness-driven dispatch routing: eliminate per-agent workflow files for custom agents #1985).

  • [documentation-coherence] docs/architecture.md — Mentions "Refinement" in the SDLC loop but does not describe which agents perform it. With ADR 0051 Accepted, architecture.md should be updated per AGENTS.md guidance.

  • [error-message-consistency] internal/scaffold/fullsend-repo/scripts/pre-explore.sh — New scripts use echo "ERROR:" pattern instead of ::error:: GitHub Actions annotation format established in comparable scripts like pre-code.sh.

  • [schema-version-inconsistency] internal/scaffold/fullsend-repo/schemas/critique-result.schema.json — All three new schemas use JSON Schema draft-07 while existing schemas use draft-2020-12.

  • [stale-doc] docs/ADRs/0051-refinement-pipeline-architecture.md — Empty relates_to field. Should reference ADRs 0018, 0020, 0026.

  • [stale-doc] docs/ADRs/0052-jira-integration-model.md — Empty relates_to field. Should reference ADRs 0005, 0017.

  • [stale-doc] docs/plans/adr-0045-forge-portable-harness-phase2.md:407 — Test expectation states "DiscoverAgents returns 6 agents" but scaffold now contains 9 harness files.

Info

  • [observation] internal/scaffold/fullsend-repo/scripts/post-critique.sh:175 — When review round limit is reached with a revise verdict, post-critique.sh mutates critique history to change the verdict to approved with escalated: true. The artifact no longer reflects the agent's actual verdict.

  • [permission-manifest-change] internal/forge/github/types.go:139 — Three new GitHub App role configurations added (explore, refine, critique) with actions:write, contents:read, issues:write. Permissions follow least-privilege for the stated use case.

  • [credential-isolation] internal/scaffold/fullsend-repo/scripts/pre-explore.sh — Jira credentials correctly isolated to pre/post scripts on the trusted GHA runner. Agent sandbox does not receive credentials. Credential isolation design is sound.


Labels: PR adds three new pipeline agents with dispatch workflows, harness configs, and supporting documentation.

Previous run

Review

Findings

High

  • [protected-path] .github/workflows/reusable-create-children.yml, .github/workflows/reusable-critique.yml, .github/workflows/reusable-explore.yml, .github/workflows/reusable-refine.yml — Four reusable workflow files under .github/ are modified. This PR has no linked issue to justify modifying governance/infrastructure files. Human approval is required for all protected-path changes.
    Remediation: Link this PR to an authorizing issue that documents the decision to add the refinement pipeline, and ensure human reviewers explicitly approve the workflow changes.

Medium

  • [logic-error] internal/scaffold/fullsend-repo/scripts/create-children.sh:80 — The orphan parent_title validation uses jq inside() which performs substring matching on strings. [.parent_title] | inside($titles) returns true if parent_title is a substring of any title in the children array, not an exact match. A parent_title of "API" would falsely match a child titled "Add API Gateway", suppressing the orphan warning.
    Remediation: Replace select([.parent_title] | inside($titles) | not) with select(.parent_title as $pt | $titles | any(. == $pt) | not) for exact string matching.

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.ymlDISPATCHED counter is incremented inside a pipeline subshell (jq | while read), so the increment never propagates back to the parent shell. The counter remains 0 in logging output regardless of how many dispatches occur.
    Remediation: Use process substitution (while read ... < <(jq ...)) instead of a pipe, or remove the misleading counter.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/jira-dispatch.yml:66RAW_COMMAND from inputs.command || github.event.client_payload.command is echoed unsanitized via echo "Full command: ${RAW_COMMAND}". A repository_dispatch sender can embed :: workflow commands. Additionally, TARGET_REPO parsed from RAW_COMMAND is echoed inside ::notice:: without sanitization.
    Remediation: Sanitize RAW_COMMAND and TARGET_REPO before echoing by stripping :: sequences, or use stop-commands guards around echo statements with user-controlled values.

  • [fail-open] internal/scaffold/fullsend-repo/scripts/pre-explore.sh:50 — The private-Jira-on-public-repo safety check defaults REPO_VISIBILITY to "unknown" when gh api fails. Since the check only blocks when visibility equals "public", an API failure causes the check to pass, potentially allowing private Jira data to leak into public repo artifacts. Same pattern in jira-dispatch.yml and jira-comment-poller.yml.
    Remediation: Treat unknown visibility as unsafe — block on anything other than "private" or "internal", or fail the check when the API call fails.

  • [stale-doc] docs/reference/github-setup.md:112 — The --agents parameter default value lists fullsend,triage,coder,review,retro,prioritize but code now includes explore, refine, critique. Documentation would mislead operators about which agents are provisioned by default.
    Remediation: Update to include the three new agent roles.

  • [stale-doc] docs/reference/installation.md:246 — Same --agents parameter default value is stale, missing explore, refine, critique.
    Remediation: Update to include the three new agent roles.

  • [stale-doc] docs/ADRs/0033-per-repo-installation-mode.md:258 — Same --agents parameter default value is stale, missing explore, refine, critique.
    Remediation: Update to include the three new agent roles.

Low

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/explore.yml — The explore.yml scaffold accepts auto_create and repo_full_name as workflow_dispatch inputs but does not forward them via the with: block to the reusable workflow call. The auto_create value IS available via harness runner_env, but the input forwarding path is incomplete.

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.ymlALREADY_ACKED jq filter uses contains() for substring matching on comment IDs, which could cause false positives when one ID is a substring of another.

  • [missing-runtime-mechanism] internal/scaffold/fullsend-repo/harness/explore.yamlTARGET_REPO_DIR is not in explore harness runner_env, so the explore agent falls back to remote-only analysis via gh api even when a local checkout is available.

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-explore.sh:92 — The gh workflow run refine.yml call redirects stderr to /dev/null, swallowing diagnostic error messages. Same pattern in post-critique.sh.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/create-children.sh:108github_create_issue extracts the issue number from gh issue create output using grep -oP '/issues/\K[0-9]+'. If the output format changes, issue_number will be empty, preventing sub-issue linking.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.yml:83 — Jira comment content (BODY, AUTHOR, COMMAND_LINE) is echoed directly to GHA stdout without sanitization. Blast radius is limited (poller runs with restricted permissions).

  • [authorization-bypass] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml:17 — Issue creators (including external contributors on public repos) can re-trigger the pipeline via follow-up comments when refine-needs-input or human-refinement labels are present. Likely intentional but worth documenting.

  • [scope-coherence] docs/ADRs/0051-refinement-pipeline-architecture.md — ADR 0051 claims "Phase 1" but includes substantial Jira integration. The ADRs do clarify scope, but the Phase 1/Phase 2 framing could be clearer.

  • [architectural-divergence] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml — Separate dispatch workflows diverge from established dispatch pattern (ADRs 0026, 0041). ADR 0051 acknowledges this and defers consolidation to Phase 2 (blocked on Harness-driven dispatch routing: eliminate per-agent workflow files for custom agents #1985).

  • [documentation-coherence] docs/architecture.md — Mentions "Refinement" in the SDLC loop but does not describe which agents perform it. With ADR 0051 Accepted, architecture.md should be updated per AGENTS.md guidance.

  • [error-message-consistency] internal/scaffold/fullsend-repo/scripts/pre-explore.sh — New scripts use echo "ERROR:" pattern instead of ::error:: GitHub Actions annotation format established in comparable scripts like pre-code.sh.

  • [schema-version-inconsistency] internal/scaffold/fullsend-repo/schemas/critique-result.schema.json — All three new schemas use JSON Schema draft-07 while existing schemas use draft-2020-12.

  • [stale-doc] docs/ADRs/0051-refinement-pipeline-architecture.md — Empty relates_to field. Should reference ADRs 0018, 0020, 0026.

  • [stale-doc] docs/ADRs/0052-jira-integration-model.md — Empty relates_to field. Should reference ADRs 0005, 0017.

  • [stale-doc] docs/plans/adr-0045-forge-portable-harness-phase2.md:407 — Test expectation states "DiscoverAgents returns 6 agents" but scaffold now contains 9 harness files.

Info

  • [observation] internal/scaffold/fullsend-repo/scripts/post-critique.sh:175 — When review round limit is reached with a revise verdict, post-critique.sh mutates critique history to change the verdict to approved with escalated: true. The artifact no longer reflects the agent's actual verdict.

  • [permission-manifest-change] internal/forge/github/types.go:139 — Three new GitHub App role configurations added (explore, refine, critique) with actions:write, contents:read, issues:write. Permissions follow least-privilege for the stated use case.

  • [credential-isolation] internal/scaffold/fullsend-repo/scripts/pre-explore.sh — Jira credentials correctly isolated to pre/post scripts on the trusted GHA runner. Agent sandbox does not receive credentials. Credential isolation design is sound.

Previous run (2)

Review

Findings

High

  • [protected-path] .github/workflows/reusable-{create-children,critique,explore,refine}.yml — Four reusable workflow files under .github/ are modified. This PR has no linked issue to justify modifying governance/infrastructure files. Human approval is required for all protected-path changes.
    Remediation: Link this PR to an authorizing issue that documents the product decision to add the refinement pipeline, and ensure human reviewers explicitly approve the workflow changes.

Medium

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.ymlCOMMENT_BODY from github.event.comment.body is set as an env var (mitigating expression injection), but echo "Full command: ${RAW_COMMAND}" outputs user-controlled values to GHA logs where embedded :: workflow commands would be interpreted by the runner.
    Remediation: Sanitize echo output for :: sequences or use stop-commands guards around echo statements with user-controlled values.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/jira-dispatch.ymlRAW_COMMAND from client_payload is echoed unsanitized. Extracted values passed to gh workflow run -f.
    Remediation: Sanitize RAW_COMMAND before echoing. Use stop-commands guards.

  • [fail-open] internal/scaffold/fullsend-repo/scripts/pre-explore.sh — The private-Jira-on-public-repo safety check defaults REPO_VISIBILITY to "unknown" when gh api fails. Since the check only blocks when visibility is "public", API failure causes the check to pass (fail-open). Same pattern in jira-comment-poller.yml and jira-dispatch.yml.
    Remediation: Change to fail-closed: replace || echo "unknown" with || echo "public" in all three locations, or add an explicit check that blocks on "unknown".

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/explore.yml — The explore.yml scaffold accepts auto_create and repo_full_name as workflow_dispatch inputs but does not forward them via the with: block to the reusable workflow call. These values flow through the harness runner_env path instead, which works but creates confusion about which path is authoritative.
    Remediation: Either add auto_create and repo_full_name as inputs to reusable-explore.yml, or remove the unused workflow_dispatch inputs to avoid confusion.

  • [stale-doc] docs/agents/critique.md — States auto_create=true (default) but every code path defaults auto_create to "false" (critique.yml, reusable-critique.yml, post-critique.sh). The documentation would mislead operators about the default behavior.
    Remediation: Change to state that auto_create=false is the default.

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.ymlDISPATCHED counter is incremented inside a pipeline subshell (jq | while read), so the increment never propagates back to the parent shell. The counter remains 0 in logging output regardless of how many dispatches occur.
    Remediation: Use process substitution (while read ... < <(jq ...)) instead of a pipe, or remove the misleading counter.

Low

  • [missing-runtime-mechanism] internal/scaffold/fullsend-repo/harness/explore.yamlTARGET_REPO_DIR is not in explore harness runner_env, so the explore agent falls back to remote-only analysis via gh api even when a local checkout is available. The agent prompt has a working fallback, but local filesystem access enables richer analysis.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.yml — Jira comment content (BODY, AUTHOR) is echoed directly to stdout. A Jira user could inject :: workflow commands, though the blast radius is limited (poller runs with actions:write and contents:read only).

  • [authorization-bypass] internal/scaffold/fullsend-repo/.github/workflows/refine-dispatch.yml — Issue creators (including external contributors on public repos) can re-trigger the pipeline via follow-up comments when refine-needs-input or human-refinement labels are present. Likely intentional (issue authors need to answer agent questions), but worth documenting.

  • [schema-version-inconsistency] internal/scaffold/fullsend-repo/schemas/critique-result.schema.json — New schemas use JSON Schema draft-07 while existing schemas use draft-2020-12. Inconsistency may cause validation differences across validators.

  • [error-message-consistency] internal/scaffold/fullsend-repo/scripts/pre-explore.sh — Error messages inconsistently use ERROR: prefix instead of ::error:: GHA annotation format used throughout the existing codebase.

  • [documentation-gap] docs/ADRs/0051-refinement-pipeline-architecture.md — Both ADR 0051 and 0052 have empty relates_to fields. ADR 0051 should relate to ADRs 0018, 0020, 0026; ADR 0052 to ADRs 0005, 0017.

  • [edge-case] internal/scaffold/fullsend-repo/.github/workflows/jira-comment-poller.ymlALREADY_ACKED jq filter uses contains() for substring matching on comment IDs, which could cause false positives when one ID is a substring of another.

Info

  • [observation] internal/scaffold/fullsend-repo/scripts/post-critique.sh — When review round limit is reached with a revise verdict, post-critique.sh mutates critique history to change the verdict to approved with escalated: true. The artifact no longer reflects the agent's actual verdict.

  • [permission-manifest-change] internal/forge/github/types.go — Three new GitHub App role configurations added (explore, refine, critique) with actions:write, contents:read, issues:write. Permissions follow least-privilege for the stated use case.


Labels: PR adds three new pipeline agents with dispatch workflows, harness configs, and supporting documentation.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

@fullsend-ai-review fullsend-ai-review Bot added type/feature New capability request component/dispatch Workflow dispatch and triggers component/harness Agent harness, config, and skills loading component/docs User-facing documentation go Pull requests that update go code new-agent-proposal Proposal for a new agent type labels Jun 18, 2026
- pre-explore.sh: Jira token preflight check — catches 401/403 early
  with clear error message instead of opaque pipeline crash
- create-children.sh: JQ dry-run validation — verifies children array
  structure (title, type, description) before touching any external API
- post-critique.sh: synthesized review history table on max-rounds
  escalation so humans see what each round debated, plus refine-stalled label
- sanitize-artifacts.sh: PII redaction metrics — counts and reports
  what was scrubbed per category, writes redaction-summary.json

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 6:52 PM UTC · Ended 6:56 PM UTC
Commit: 4e21a60 · View workflow run →

- Remove sanitize-artifacts.sh + test (dead: JIRA_PROJECT_VISIBILITY
  safety check blocks the scenario it was designed for)
- Remove scaffold.go, scaffold_test.go, Makefile references
- Remove unwired confidence_threshold from post-explore.sh dispatch
- Fix agent docs: control labels now match implementation, auto_create
  default corrected to false

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:59 PM UTC · Completed 7:16 PM UTC
Commit: d7d7bd9 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

# Validate parent_title references resolve within the tree
ORPHAN_REFS=$(jq -r '
[.children[].title] as $titles |
[.children[] | select(.parent_title != null and .parent_title != "") |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] logic-error

The orphan parent_title validation uses jq inside() which performs substring matching. [.parent_title] | inside($titles) returns true if parent_title is a substring of any title, not an exact match. A parent_title of 'API' would falsely match 'Add API Gateway', suppressing the orphan warning.

Suggested fix: Replace select([.parent_title] | inside($titles) | not) with select(.parent_title as $pt | $titles | any(. == $pt) | not).

echo "::error::Use a private config repo, or set JIRA_PROJECT_VISIBILITY=public for public Jira projects."
exit 1
fi
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] GHA-workflow-command-injection

RAW_COMMAND from inputs.command or github.event.client_payload.command is echoed unsanitized. A repository_dispatch sender can embed :: workflow commands. TARGET_REPO parsed from RAW_COMMAND is also echoed inside ::notice:: without sanitization.

Suggested fix: Sanitize RAW_COMMAND and TARGET_REPO by stripping :: sequences, or use stop-commands guards.

if [[ "$REPO_VISIBILITY" == "public" ]]; then
echo "::error::SECURITY: Private Jira source blocked — this repo (${CONFIG_REPO}) is public."
echo "::error::Private Jira data would leak into public workflow artifacts and logs."
echo "::error::Options:"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] fail-open

The private-Jira-on-public-repo safety check defaults REPO_VISIBILITY to 'unknown' when gh api fails. Since the check only blocks on 'public', API failure causes the check to pass (fail-open), potentially allowing private Jira data to leak into public repo artifacts. Same pattern in jira-dispatch.yml and jira-comment-poller.yml.

Suggested fix: Treat unknown visibility as unsafe: block on anything other than 'private' or 'internal', or fail the check when the API call fails.


This pipeline extends fullsend's existing patterns:

1. **Chained agents with artifact passing** (new): Existing agents are standalone — triage triggers code via a `ready-to-code` label, which fires a webhook, and the code agent reads the issue fresh from GitHub. No data passes between them. The refinement pipeline is different because each stage produces structured context that the next stage needs: explore produces `exploration_context.json` (~50-100KB of analyzed research), refine produces `refine-result.json` (the full decomposition plan with children, acceptance criteria, etc.), and critique needs both to evaluate the plan. Labels can signal "go/no-go" but can't carry a run ID for artifact correlation, so we use explicit `workflow_dispatch` chaining instead. For example, post-explore.sh chains to refine like this:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm, I think this is probably the decision. Adding new agents is non controversial, but having some agents depend on the execution details of others is a new idea. I suggest refactoring this so that the decision is adding this new pattern and the context sets up why that's necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — refactored. The ADR now leads with agent chaining as the decision (one agent's post-script triggers the next via workflow_dispatch, passing a run ID for artifact correlation). The three refinement agents are the first use of this pattern, not the decision itself.

Also added an Alternatives Considered section covering:

The single-workflow alternative is cross-referenced to the Deferred table as the strongest Phase 2 candidate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see issue comments were considered and rejected for the 65K limit, which makes sense. Were issue attachments considered though? JIRA has a proper attachment API (POST /rest/api/2/issue/{key}/attachments) and the size limits are way more generous than 65K.

GitHub issues are trickier — the UI supports file attachments up to 25MB but there's no public API for uploading them. So this might only work cleanly on the JIRA side.

If we could attach the exploration JSON to the JIRA ticket, then /fs-refine pulls it from there instead of snaking back to the workflow run. That'd also make /fs-explore useful as a standalone thing — you explore, the artifact lives on the ticket, and refinement can happen later (or not at all).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For GitHub, maybe we post a comment on the issue with a link back to the workflow artifact. The downstream agent navigates to the issue first, finds the link, follows it. Extra hop, but it decouples the agents from each other's execution details.

Right now the refine agent has to know that the explore agent runs as a GitHub Actions workflow and how to find its run. That coupling is the thing that feels off — other agents aren't aware of each other's runtime like that (except retro, which is arguably special). If the issue is always the rendezvous point, agents just need to know how to read from the issue tracker, which they already do.

…rame

- Change REPO_VISIBILITY fallback from "unknown" to "public" in
  pre-explore.sh, jira-dispatch.yml, jira-comment-poller.yml so
  gh api failures trigger the safety block (fail-closed)
- Add ::stop-commands:: guards around user-controlled echo blocks in
  refine-dispatch.yml and jira-dispatch.yml to prevent GHA command injection
- Convert jq|while pipes to process substitution in jira-comment-poller.yml
  so DISPATCHED counter propagates to parent shell
- Standardize ERROR: prefixes to ::error:: annotations in pre-explore.sh
- Reframe ADR 0051 per Ralph's feedback: decision is agent chaining as a
  new orchestration pattern, not adding 3 agents. Add Alternatives
  Considered section with single-workflow pipeline (#234, #1817),
  issue-comment data store, and repository_dispatch options

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:01 PM UTC · Completed 8:41 PM UTC
Commit: 6b8fed4 · View workflow run →

Comment thread docs/agents/explore.md
Comment on lines +26 to +28
| `/fs-refine` | Issue comment | Triggers the refinement pipeline starting with explore |

The `/fs-refine` command kicks off the full pipeline. Explore runs first, then automatically chains to [refine](refine.md). There is no separate command to run explore in isolation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This feels weird, that the agent's command is /fs-refine when the agent is the "explore" agent. Should it be /fs-explore?

I think what this really reveals to me is that explore agent is not useful on its own - as this is currently structured. Its only purpose is to be a prelude to the refine agent.

Perhaps we should restructure that and treat it as a standalone thing. i.e., what if you could just /fs-explore a feature and get an exploration json attached to the JIRA (or to the gh issue, if that's possible to upload attachments). Then /fs-refine would launch the refinement agent that takes in that attachment from the jira or from the github issue, rather than snaking back to the gh workflow run.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OTOH, if we want explore->refine->critique->(loop with refine until 👍) to be an uninterrupted thing, then perhaps it should just be one agent inside one giant claude code session - one harness file.


## Decision

We will introduce **agent chaining** as a new orchestration pattern in fullsend: the ability for one agent's post-script to trigger a subsequent agent via `workflow_dispatch`, passing a run ID that the next agent uses to download the previous stage's artifacts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this mean that the explore agent always triggers the refine agent and the refine agent always triggers the critique agent?

Is there anyway to "start" the workflow at the refinement agent, if, say, humans have feedback and want to tweak something without having to re-do all of the exploration?

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

--jq '[.[] | select(.status == "completed" and .conclusion == "success")] | .[0].databaseId // empty' \
2>/dev/null || true)

FOLLOWUP_ARGS=(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] logic-error

Follow-up path (IS_FOLLOWUP=true) searches for the most recent successful explore run without filtering by issue number. If multiple issues have had explore runs, the wrong exploration context will be attached to the refine stage.

Suggested fix: Add a displayTitle filter matching the issue number to the gh run list jq query, consistent with how /fs-create searches for refine runs.

# Validate parent_title references resolve within the tree
ORPHAN_REFS=$(jq -r '
[.children[].title] as $titles |
[.children[] | select(.parent_title != null and .parent_title != "") |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] logic-error

Orphan parent_title validation uses jq inside() for substring matching instead of exact match. Runtime behavior is correct (creation loop uses exact match), but validation warning is misleading.

Suggested fix: Replace inside() with exact matching.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/dispatch Workflow dispatch and triggers component/docs User-facing documentation component/harness Agent harness, config, and skills loading go Pull requests that update go code new-agent-proposal Proposal for a new agent type type/feature New capability request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants