feat(agents): add gh-classify agent - #619
Conversation
Port the GitHub issue classification agent from fullsend-ai/.fullsend customized/ into the shared agents repo, matching the scribe extraction pattern (agents#10). Keeps org category docs and the workflow in .fullsend. Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
🤖 Review · |
Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
🤖 Finished Review · ✅ Success · Started 11:22 AM UTC · Completed 11:38 AM UTC |
ReviewFindingsHigh
Medium
Low
Labels: PR adds a new agent with protected infrastructure files requiring human review Next steps:
Previous runReviewFindingsHigh
Low
Next steps:
Previous run (2)ReviewFindingsHigh
Medium
Low
Labels: PR adds a new agent (gh-classify) with supporting infrastructure Next steps:
|
Document categories format, harden post/pre scripts, expand tests, and sync README agent table. Leave triage role/slug intentional. Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the medium/low review findings in the latest push. Leaving the two high findings as-is: Why the high findings don't apply here
Fixed
Not changing
|
|
🤖 Finished Review · ✅ Success · Started 11:45 AM UTC · Completed 12:01 PM UTC |
waynesun09
left a comment
There was a problem hiding this comment.
Additional review findings (independent pass) — 9 items not previously flagged by the existing fullsend-ai-review bot pass, covering a candidate-set validation gap in the post-script, an error-handling bug under set -e, test-coverage gaps, and a few documentation/consistency issues. See inline comments.
Host candidate-set guard, set -e GraphQL resilience, per-repo categories docs/skill fallback, Read/Write skill guidance, and expanded tests. Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
🤖 Finished Review · ✅ Success · Started 1:53 PM UTC · Completed 2:09 PM UTC |
| model: opus | ||
| image: ghcr.io/fullsend-ai/fullsend-sandbox:latest | ||
| policy: policies/gh-classify.yaml | ||
| role: triage |
There was a problem hiding this comment.
[high] role-slug-collision
role: triage and slug: fullsend-ai-triage collide with the existing triage agent (harness/triage.yaml:8-9). Every other agent in the repo uses a unique role/slug pair (except code/fix which share coder intentionally as closely related agents). The triage and classify agents are functionally distinct — triage assesses issue sufficiency and applies control labels, while classify assigns project board categories. This collision risks silent misdispatch if the harness resolves agents by role or slug.
Suggested fix: Use distinct identifiers, e.g., role: classify and slug: fullsend-ai-classify.
| - src: env/gh-classify.env | ||
| dest: /sandbox/workspace/.env.d/gh-classify.env | ||
| expand: true | ||
| - src: ${CLASSIFY_CATEGORIES_PATH} |
There was a problem hiding this comment.
[low] path-traversal
CLASSIFY_CATEGORIES_PATH is used as a host_files source path without path validation in this PR. This is a standard pattern across the repo (other agents use ${GOOGLE_APPLICATION_CREDENTIALS}, ${GCP_OIDC_TOKEN_FILE}, etc. in host_files.src the same way), and workflow dispatch inputs require authorized collaborator access.
| exit 1 | ||
| fi | ||
| else | ||
| # Backward compatibility: scan iteration-N/ subdirectories for the last |
There was a problem hiding this comment.
[low] edge-case
The backward-compat fallback for dir in iteration-*/output comment states 'glob order = naturally ascending iteration numbers,' but bash glob expansion is lexicographic, not numeric. For iteration counts >= 10, iteration-10 sorts before iteration-2. Mitigated by max_iterations: 2 in the harness config.
|
|
||
| # Log mode info (::notice:: only renders in GitHub Actions). | ||
| if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then | ||
| echo "::notice::Classify agent — mode=${CLASSIFY_MODE}, repo=${CLASSIFY_SOURCE_REPO}" |
There was a problem hiding this comment.
[low] workflow-command-injection
CLASSIFY_MODE and CLASSIFY_SOURCE_REPO are interpolated into ::notice:: workflow commands. Both are validated before emission (strict case statement and regex). Risk is negligible.
| .[] | select(.status == "classified") | | ||
| "| #\(.issue_number) | \(.workstream_category // "—") | \((.confidence * 100) | floor)% | \(.category_action) |" | ||
| ' "${REPORT_FILE}" 2>/dev/null || echo "| — | — | — | — |" | ||
| echo "" |
There was a problem hiding this comment.
[low] content-injection
The agent's reasoning field flows into the Step Summary markdown table with partial sanitization (pipe, newline, backtick, angle bracket escaping, 100-char truncation). GitHub also applies HTML sanitization.
| if [[ -n "${FILTER_CATEGORY}" ]]; then | ||
| echo "" | ||
| echo "**Filter:** ${FILTER_CATEGORY}" | ||
| fi |
There was a problem hiding this comment.
[low] content-injection
FILTER_CATEGORY is interpolated unsanitized into Step Summary markdown. Limited attack surface (authorized users only, GitHub HTML sanitization applies).
| if [[ ${#CLASSIFIED_LINES[@]} -gt 0 ]]; then | ||
| echo "CLASSIFIED (${#CLASSIFIED_LINES[@]} issues)" | ||
| echo "------------------------------------------------------------" | ||
| for line in "${CLASSIFIED_LINES[@]}"; do |
There was a problem hiding this comment.
[low] token-scrubbing
In the set_project_field error handler, PROJECT_GH_TOKEN is scrubbed via sed regex. If the token contained sed metacharacters, the substitution could fail. In practice, GitHub tokens are alphanumeric.
| | **Scribe** | Maps meeting notes to the GitHub issue backlog | Schedule, `/fs-scribe` | | ||
| | **GitHub Classify** | Classifies GitHub issues into project board categories | Workflow dispatch, issue shim | | ||
|
|
||
| See [`docs/`](docs/) for detailed documentation on each agent. |
There was a problem hiding this comment.
[low] scope-creep
Both README.md and docs/README.md add a Scribe row alongside gh-classify. Scribe already exists on main but was missing from these tables — a minor doc gap fix bundled with the gh-classify entries.
| @@ -0,0 +1,58 @@ | |||
| --- | |||
There was a problem hiding this comment.
[low] naming-convention
The gh- prefix departs from the established naming convention; all other agents use unprefixed names (triage, scribe, code, fix, review, retro, prioritize).
waynesun09
left a comment
There was a problem hiding this comment.
Review-only pass (deduped against existing inline comments/reviews). 9 additional findings covering: a role-permission gap that would 403 every project-board write in the documented default install path, a sandbox credential/network-policy exposure inconsistent with the PR's own security model and prior ADR guidance, a sandbox-side fallback that silently upgrades "unclassified" mode to "all" mode, dry-run mode not exercising config validation, missing timeouts on GraphQL calls, and Step Summary reporting gaps/mislabeling.
| model: opus | ||
| image: ghcr.io/fullsend-ai/fullsend-sandbox:latest | ||
| policy: policies/gh-classify.yaml | ||
| role: triage |
There was a problem hiding this comment.
[CRITICAL] role: triage has no organization_projects permission — every project-board write (and read) will 403
gh-classify.yaml declares role: triage. In fullsend-ai/fullsend's internal/mintcore/github.go, canonicalRolePermissions["triage"] is {"contents":"read","issues":"write","metadata":"read"} — no organization_projects scope at all, read or write. By contrast the prioritize role, used by the structurally similar prioritize agent for the same kind of Projects V2 field write, explicitly includes "organization_projects":"write".
In scripts/pre-gh-classify.sh, the project-metadata GraphQL query (querying organization(login: $org) { projectV2(...) }) and the already-classified-issue query both run with PROJECT_GH_TOKEN="${GH_TOKEN}" (falls back to plain GH_TOKEN whenever CLASSIFY_PROJECT_TOKEN is unset — i.e. the default, documented same-org install path), and the code already has a designed fallback for when that query fails (PROJECT_ACCESS_OK="false" → "treating all issues as unclassified"). In scripts/post-gh-classify.sh, set_project_field()'s addProjectV2ItemById/updateProjectV2ItemFieldValue mutations also use the same PROJECT_GH_TOKEN="${CLASSIFY_PROJECT_TOKEN:-${GH_TOKEN}}.
Net effect: in the primary, documented same-org path (no CLASSIFY_PROJECT_TOKEN configured), every live run will fail to discover project metadata and every set_project_field call will 403 — the agent's core function (assigning a project-board category) cannot work as merged.
Failure scenario: Install gh-classify same-org with no CLASSIFY_PROJECT_TOKEN (the documented default). The pre-script's project-metadata GraphQL query returns a permission error (no organization_projects scope on the triage-role token), so project-meta.json ends up with null project_id/field_id. Every post-script set_project_field() call then hits the empty-ID guard and returns 1; the run's Step Summary shows every classified issue with an error and zero actual category writes ever happen.
Suggestion: Add a classify role to fullsend's canonicalRolePermissions with organization_projects: write (plus issues read/write), and register a matching GitHub App/slug, or reuse role: prioritize directly. This is a cross-repo dependency that must land before or alongside this PR.
| CLASSIFY_MIN_CONFIDENCE: ${CLASSIFY_MIN_CONFIDENCE} | ||
| CLASSIFY_DRY_RUN: ${CLASSIFY_DRY_RUN} | ||
| sandbox: | ||
| GH_TOKEN: "${GH_TOKEN}" |
There was a problem hiding this comment.
[CRITICAL] Sandbox is given the same write-capable GH_TOKEN plus full read-write GitHub network access, contradicting the PR's own "writes never happen in the sandbox" security model
This line passes the identical ${GH_TOKEN} into both env.runner (used by pre/post scripts for issues:write + org projects writes) and env.sandbox. policies/gh-classify.yaml's github_api network policy (lines 37-49) grants access: read-write to api.github.com and github.com for the **/gh binary — the exact binary allowlisted in tools: Bash(gh,jq) for the sandboxed agent.
docs/gh-classify.md's security-model table and this PR's own self-review assert that project writes never happen inside the sandbox, but nothing at the credential or network layer enforces that — the sandboxed agent (which processes untrusted, attacker-influenced issue content per this PR's own threat framing) can call gh issue edit/close/comment or an arbitrary GraphQL mutation directly with the live token; the prompt's prose instruction ("NEVER modify issue content, labels, or state") is the only barrier.
This is precisely the "scoped token in sandbox" pattern that fullsend's own ADR 0017 (Credential Isolation for Sandboxed Agents) explicitly rejected — "any credential present in the sandbox must be assumed exfiltrable" — in favor of prefetch + post-process as "the expected model for any new agent." The immediately-preceding sibling scribe agent already follows that default: policies/scribe.yaml has no github_api network policy at all, getting all GitHub data via host-side pre-fetch. gh-classify's own pre-script already computes open-issues.json and project-meta.json on the host but does not mount them into the sandbox, so the skill has the sandboxed agent re-discover the same data live with the injected token instead. (Note: the pre-existing triage.yaml/policies/triage.yaml has the identical pattern already merged, so this PR did not invent the hole — but it reproduces it wholesale in brand-new files, and the PR's security self-review doesn't discuss the trade-off.)
Failure scenario: An issue body/comment contains a prompt-injection payload instructing the agent to run gh issue comment <n> --body ... or an equivalent GraphQL mutation using the injected GH_TOKEN. The github_api network policy permits read-write to api.github.com from the gh binary inside the sandbox, and the token itself carries issues:write — the write succeeds with no technical control blocking it, only the prompt's prose instruction which the injection is specifically trying to override.
Suggestion: Either (a) mount the pre-script's already-computed open-issues.json/project-meta.json/already-classified list into the sandbox as host_files (as scribe does) and drop GH_TOKEN plus the github_api network policy from env.sandbox entirely, or (b) if live sandbox reads must be kept, mint a distinct, read-only-scoped token for env.sandbox separate from the write-capable token used by env.runner/post-script, and scope the github_api network policy to read-only.
|
|
||
| Then **exclude issues that are already classified** on the project | ||
| board. The pre-script identifies these on the host, but you are in a | ||
| sandbox and must discover them yourself. Query the project board to |
There was a problem hiding this comment.
[HIGH] Sandbox-side already-classified discovery degrades the same way as the pre-script, silently turning "unclassified" mode into "all" mode every run
This step instructs the sandboxed agent to independently query the org Project board via GraphQL using GH_TOKEN to exclude already-classified issues in batch (unclassified/all) modes: "If the project query fails (permissions, network), log a warning and continue with the open-issue list you already fetched." Given the CRITICAL role-permission gap on harness/gh-classify.yaml:7 (role: triage has no organization_projects scope at all), this query will always fail in the sandbox too, exactly as it does in the pre-script. The fallback is graceful (no crash) and there is a host-side safety net (the post-script rejects out-of-candidate issue_numbers), so this doesn't cause incorrect writes — but it does mean unclassified mode silently behaves like all mode on every single run: the entire open-issue backlog gets re-evaluated (repeated LLM inference cost) and every classified issue's project-write attempt still fails per the permission-gap finding.
Failure scenario: Same-org install running in unclassified mode on a repo with hundreds of already-classified issues. Both the pre-script's and the sandbox's project-board queries fail due to the missing organization_projects permission; every run re-evaluates the full open-issue backlog instead of only the genuinely new/unclassified ones, multiplying inference cost and producing hundreds of failed project-write attempts per run.
Suggestion: Fix the root role-permission gap. Independently, surface a loud, visible warning (in the GitHub Step Summary, not just stdout/log text) whenever project access is unavailable, so a broken deployment silently reprocessing the whole backlog is visible to operators.
| if [[ -n "${CATEGORY}" && "${CATEGORY}" != "null" ]]; then | ||
| PASSES_THRESHOLD=$(printf '%s >= %s\n' "${CONFIDENCE}" "${MIN_CONFIDENCE}" | bc -l 2>/dev/null || echo "0") | ||
| if [[ "${PASSES_THRESHOLD}" == "1" ]]; then | ||
| if [[ "${DRY_RUN}" == "true" ]]; then |
There was a problem hiding this comment.
[HIGH] Dry-run mode never exercises set_project_field's validation path, so a broken project/field config reports clean "would-set" results
In the per-issue loop, dry-run goes straight to ((CLASSIFIED++)); ISSUE_STATUS="classified"; CATEGORY_ACTION="would-set", while the live-run branch calls set_project_field, which contains ALL of the actual validation (PROJECT_ID/FIELD_ID presence check, and the category→option_id lookup against project-meta.json). So set_project_field is never invoked when CLASSIFY_DRY_RUN is true. A misconfigured CLASSIFY_PROJECT_NUMBER/CLASSIFY_FIELD_NAME, a project the token can't see, or a category name that doesn't match any project single-select option all report a clean "would-set" during dry-run preview, then silently produce error status the moment CLASSIFY_DRY_RUN flips to false on a live run. This defeats dry-run's documented purpose as a pre-flight config check (docs/gh-classify.md: "Dry-run mode previews decisions without writing to the project board"), and scripts/post-gh-classify-test.sh has no test exercising dry-run against an invalid/incomplete project-meta.json or an unmapped category name — all its dry-run tests use a fully valid project-meta.json.
Failure scenario: An operator sets CLASSIFY_PROJECT_NUMBER to the wrong project number and runs with CLASSIFY_DRY_RUN=true to preview. Every issue shows "would-set" with no warning. They flip to CLASSIFY_DRY_RUN=false expecting the same results, but every single classification now fails with "Failed to add or find #N on project" because PROJECT_ID never resolved — a misconfiguration invisible in preview.
Suggestion: In dry-run mode, still run the read-only parts of set_project_field (the PROJECT_ID/FIELD_ID presence check and the category→option_id lookup) so misconfiguration and unmapped category names surface as warnings during preview. Add a test fixture with an incomplete project-meta.json or an unknown category name run under CLASSIFY_DRY_RUN=true.
| # project it returns the existing item. If the mutation fails for any | ||
| # reason, fall back to querying for the existing item. | ||
| local item_id | ||
| item_id=$(GH_TOKEN="${PROJECT_GH_TOKEN}" gh api graphql -f query=' |
There was a problem hiding this comment.
[MEDIUM] GitHub API calls inside set_project_field have no timeout, unlike the pre-script's consistent 30s bound
Every gh api/gh api graphql call inside set_project_field() — the node_id lookup, the addProjectV2ItemById mutation (this line), the paginated item-search fallback, and the updateProjectV2ItemFieldValue mutation — runs with no timeout wrapper. By contrast, every equivalent call in scripts/pre-gh-classify.sh is consistently wrapped in timeout 30. Since set_project_field runs synchronously per classified issue inside the main loop (potentially many issues in all/unclassified mode), a single hung network call stalls the entire post-script until the harness-level timeout_minutes: 15 kills the whole job — discarding the Step Summary/report for every unprocessed issue, a much worse failure mode than the per-call bound already used elsewhere in this same PR.
Failure scenario: GitHub's GraphQL API experiences a slow response or hangs mid-request while processing issue #50 of 200 in all mode. With no per-call timeout, the post-script blocks indefinitely on that one call until the harness's 15-minute job timeout fires, killing the entire run — the 49 already-processed issues' results are still in memory but the report-write and Step Summary steps never execute because the script never reaches them.
Suggestion: Wrap the gh api / gh api graphql invocations in set_project_field with the same timeout 30 pattern used in pre-gh-classify.sh, so a single stuck call degrades to a per-issue error instead of consuming the whole run's time budget.
| echo "| Issue | Category | Confidence | Action |" | ||
| echo "|------:|----------|:----------:|--------|" | ||
| jq -r ' | ||
| .[] | select(.status == "classified") | |
There was a problem hiding this comment.
[MEDIUM] Out-of-candidate-set rejections (the prompt-injection/mode-drift defense) are invisible in the GitHub Step Summary
The host-side defense that rejects agent-returned issue_numbers outside the pre-script's authoritative candidate set works correctly and is recorded with status: "out-of-candidate" in classify-report.json, and is printed to stdout — but it never appears in the GITHUB_STEP_SUMMARY. The Step Summary's Metrics table has no "Outside candidates" row, and both of its jq-filtered detail tables (select(.status == "classified") here, and select(.status == "skipped") further down) exclude status == "out-of-candidate" rows entirely. A maintainer reviewing the run via the normal GitHub Actions Step Summary UI (as opposed to raw logs/artifacts) has no visibility that the agent attempted to act on an issue it was never authorized to touch — exactly the scenario this candidate-set check exists to catch.
Failure scenario: A prompt injection causes the agent to return a classification for an issue number outside the authoritative candidate set. The post-script correctly blocks the write, but a maintainer who only checks the GitHub Actions Step Summary UI (not raw logs or the JSON artifact) sees a clean run with no indication that a blocked injection/mode-drift attempt occurred.
Suggestion: Add an "Outside candidates" row to the Step Summary Metrics table, and/or a dedicated Step Summary table for status == "out-of-candidate" rows, so a blocked attempt surfaces where operators actually look.
| echo "| Issue | Confidence | Reasoning |" | ||
| echo "|------:|:----------:|-----------|" | ||
| jq -r ' | ||
| .[] | select(.status == "skipped") | |
There was a problem hiding this comment.
[MEDIUM] "Skipped issues (below threshold)" Step Summary table conflates below-threshold, filter-mismatch, and unclassifiable results under one misleading heading
ISSUE_STATUS defaults to "skipped" and is only overridden to classified, error, or out-of-candidate; it is never overridden for the below-threshold, filter-mismatch, or unclassifiable (null-category) cases, even though CATEGORY_ACTION does distinguish them (below-threshold, filter-mismatch, unclassifiable). This table filters purely on status == "skipped", so it silently mixes in issues skipped due to a CLASSIFY_FILTER_CATEGORY mismatch or a confident null classification under a heading that specifically claims "below threshold" — misrepresenting agent behavior to anyone reading the summary. A confident null/filter-mismatch result looks identical in that table to a genuinely borderline low-confidence guess.
Failure scenario: A run with CLASSIFY_FILTER_CATEGORY="Bug fixes" set classifies several issues into other categories (filter-mismatch, high confidence) and a few issues as genuinely ambiguous (unclassifiable, low confidence). Both groups appear together in the Step Summary's "Skipped issues (below threshold)" table with no way to tell which is which from that view alone, even though the underlying report data distinguishes them.
Suggestion: Rename the table to something reason-agnostic (e.g. "Skipped issues") or split it by category_action (below-threshold, filter-mismatch, unclassifiable) the same way the stdout output already does with separate line arrays.
|
|
||
| unclassified) | ||
| echo "Discovering unclassified issues via GitHub Project..." | ||
| PROJECT_NUMBER="${CLASSIFY_PROJECT_NUMBER:-1}" |
There was a problem hiding this comment.
[MEDIUM] Hardcoded org-specific defaults (CLASSIFY_PROJECT_NUMBER=1, CLASSIFY_FIELD_NAME="Workstream Category") baked into a PR explicitly framed as a reusable, general-purpose agent
PROJECT_NUMBER="${CLASSIFY_PROJECT_NUMBER:-1}" / FIELD_NAME="${CLASSIFY_FIELD_NAME:-Workstream Category}" appear twice (here and again around lines 231-232) as defaults. The PR description explicitly frames docs/workstream-categories.md as org-specific and deliberately keeps it out of this shared repo ("Org-specific ... stay in .fullsend"), acknowledging the agent is meant to be reusable across organizations/installs — yet the project number and the literal field name "Workstream Category" are fullsend-ai's own board layout, hardcoded as defaults with no ADR/issue tying them to a cross-org convention other installs are expected to follow.
Failure scenario: A different org installs gh-classify without reading the fine print on CLASSIFY_PROJECT_NUMBER/CLASSIFY_FIELD_NAME, assumes the defaults are sensible general-purpose values (since they read like ordinary defaults rather than fullsend-ai-specific ones), and the agent silently targets project #1 with a field named "Workstream Category" that doesn't exist in their org's board — degrading to the same silent no-op-write failure mode as the permission-gap finding on harness/gh-classify.yaml:7, making the two indistinguishable to an operator.
Suggestion: Either make these required (no default) so every install must declare its own project number/field name, or clearly document in docs/gh-classify.md that these specific defaults reflect the fullsend-ai org's own board and other installs must override them.
|
|
||
| # Normalize sentinel values used to satisfy fullsend runner_env validation. | ||
| # The runner rejects empty env vars, so the workflow provides non-empty | ||
| # sentinels for optional vars. Convert them back to empty here. |
There was a problem hiding this comment.
[MEDIUM] env.runner/env.sandbox sentinel-value contract depends entirely on a not-yet-written companion workflow, and is untested via the actual sentinel path
These lines normalize sentinel values (CLASSIFY_ISSUE_NUMBER == "0", CLASSIFY_FILTER_CATEGORY == "__all__", CLASSIFY_PROJECT_TOKEN == "__none__") with the comment "The runner rejects empty env vars, so the workflow provides non-empty sentinels for optional vars" — but harness/gh-classify.yaml's env.runner/env.sandbox blocks do plain ${VAR} interpolation with no :- default fallback, and the PR description explicitly defers .github/workflows/gh-classify.yml (the only place these sentinel values would actually be supplied) to a follow-up PR in .fullsend. scripts/post-gh-classify-test.sh contains no reference to 0, __all__, or __none__ as sentinel inputs — it only exercises the scripts with already-resolved real values, never through the sentinel-normalization path itself. So the correctness of this contract is entirely unverified within this PR and depends on a workflow file this PR does not include or test against.
Failure scenario: The follow-up .fullsend workflow PR uses a different placeholder (e.g. an empty string, or "none" instead of "none") for an optional var, or omits the sentinel for one field. Nothing in this repo's test suite or harness config would catch the mismatch — the pre/post scripts would treat the literal placeholder string as a real value (e.g. attempting to filter by category "all") with no test ever exercising that path.
Suggestion: Either add the :- defaults directly in harness/gh-classify.yaml so the sentinel contract is self-contained in this repo, or explicitly flag in the PR/docs that this harness cannot be safely registered until the companion .fullsend workflow (with matching sentinel values) lands, and add a test that runs the scripts through the sentinel values themselves (e.g. CLASSIFY_ISSUE_NUMBER=0, CLASSIFY_FILTER_CATEGORY=all) rather than only through already-resolved real values.
|
Closing this PR as we have discussed that this gh-classify agent is not currently being used. We will have record of it in the git history if needed to be revived one day. |
|
🤖 Finished Retro · ✅ Success · Started 8:00 PM UTC · Completed 8:21 PM UTC |
Retro: PR #619 —
|
Summary
gh-classifyagent fromfullsend-ai/.fullsendcustomized/into the shared agents repo (same pattern as scribe in feat(agents): add scribe agent #10).issue-classificationskill, env file, docs, and post-script unit tests.common/env/gcp-vertex.env,validation_loop.schema,env.runner/env.sandbox, andFULLSEND_VALIDATED_ITERATION_DIRin the post-script.CLASSIFY_PROJECT_TOKENrunner-only (not exported into the sandbox env file).docs/workstream-categories.mdand.github/workflows/gh-classify.ymlstay in.fullsend(follow-up: register remote harness + drop customized overlay, like Add evidence-for content check to retro validation loop #101/Code agent should verify CI workflow installs new test dependencies it introduces #138 for scribe).Review notes
Self-review before open:
.fullsendregistration in this PRdocs/README.mdTest plan
bash scripts/post-gh-classify-test.shpasses locally.fullsendPR to appendagents:entry pinned to merge SHA and removecustomized/*gh-classify*overlay