diff --git a/.github/aw/orchestrate-agentic-campaign.md b/.github/aw/orchestrate-agentic-campaign.md index e0c5e56a195..be2f13b8973 100644 --- a/.github/aw/orchestrate-agentic-campaign.md +++ b/.github/aw/orchestrate-agentic-campaign.md @@ -183,6 +183,7 @@ and synchronizing campaign state into a GitHub Project board. 3) Parse discovered items from the manifest: - Each item has: url, content_type (issue/pull_request/discussion), number, repo, created_at, updated_at, state + - Items discovered via tracker-id also have: worker_workflow (the workflow name that created the item) - Closed items have: closed_at (for issues) or merged_at (for PRs) - Items are pre-sorted by updated_at for deterministic processing diff --git a/.github/aw/update-agentic-campaign-project.md b/.github/aw/update-agentic-campaign-project.md index 8ed4c79a665..0c8f0ca61b2 100644 --- a/.github/aw/update-agentic-campaign-project.md +++ b/.github/aw/update-agentic-campaign-project.md @@ -53,7 +53,7 @@ Use **content number** (integer), never the URL as an identifier. These rules apply to any time you write fields: - `campaign_id`: always `{{.CampaignID}}` -- `worker_workflow`: workflow ID if known, else `"unknown"` +- `worker_workflow`: use `worker_workflow` from discovery manifest if present, else `"unknown"` - `repository`: extract `owner/repo` from the issue/PR URL - `priority`: default `Medium` unless explicitly known - `size`: default `Medium` unless explicitly known diff --git a/actions/setup/js/campaign_discovery.cjs b/actions/setup/js/campaign_discovery.cjs index 639958151c6..83dcfca155f 100644 --- a/actions/setup/js/campaign_discovery.cjs +++ b/actions/setup/js/campaign_discovery.cjs @@ -79,9 +79,10 @@ function saveCursor(cursorPath, cursor) { * Normalize a discovered item to standard format * @param {any} item - Raw GitHub item (issue, PR, or discussion) * @param {string} contentType - Type: "issue", "pull_request", or "discussion" + * @param {string | undefined} workerWorkflow - Worker workflow identifier (optional) * @returns {any} Normalized item */ -function normalizeItem(item, contentType) { +function normalizeItem(item, contentType, workerWorkflow) { const normalized = { url: item.html_url || item.url, content_type: contentType, @@ -93,6 +94,11 @@ function normalizeItem(item, contentType) { title: item.title, }; + // Add worker workflow if available + if (workerWorkflow) { + normalized.worker_workflow = workerWorkflow; + } + // Add closed/merged dates if (item.closed_at) { normalized.closed_at = item.closed_at; @@ -187,7 +193,7 @@ async function searchByTrackerId(octokit, trackerId, repos, orgs, maxItems, maxP // Determine if it's a PR or issue const contentType = item.pull_request ? "pull_request" : "issue"; - const normalized = normalizeItem(item, contentType); + const normalized = normalizeItem(item, contentType, trackerId); items.push(normalized); } @@ -277,7 +283,8 @@ async function searchByLabel(octokit, label, repos, orgs, maxItems, maxPages, cu // Determine if it's a PR or issue const contentType = item.pull_request ? "pull_request" : "issue"; - const normalized = normalizeItem(item, contentType); + // Label-based discovery doesn't provide workflow information + const normalized = normalizeItem(item, contentType, undefined); items.push(normalized); } diff --git a/actions/setup/js/campaign_discovery.test.cjs b/actions/setup/js/campaign_discovery.test.cjs index 31f1962e476..263576326cf 100644 --- a/actions/setup/js/campaign_discovery.test.cjs +++ b/actions/setup/js/campaign_discovery.test.cjs @@ -25,7 +25,7 @@ describe("campaign_discovery", () => { }); describe("normalizeItem", () => { - it("should normalize an issue", () => { + it("should normalize an issue without worker_workflow", () => { const issue = { html_url: "https://github.com/owner/repo/issues/1", number: 1, @@ -50,6 +50,32 @@ describe("campaign_discovery", () => { }); }); + it("should normalize an issue with worker_workflow", () => { + const issue = { + html_url: "https://github.com/owner/repo/issues/1", + number: 1, + repository: { full_name: "owner/repo" }, + created_at: "2025-01-01T00:00:00Z", + updated_at: "2025-01-02T00:00:00Z", + state: "open", + title: "Test Issue", + }; + + const normalized = normalizeItem(issue, "issue", "security-scanner"); + + expect(normalized).toEqual({ + url: "https://github.com/owner/repo/issues/1", + content_type: "issue", + number: 1, + repo: "owner/repo", + created_at: "2025-01-01T00:00:00Z", + updated_at: "2025-01-02T00:00:00Z", + state: "open", + title: "Test Issue", + worker_workflow: "security-scanner", + }); + }); + it("should normalize a pull request with merged_at", () => { const pr = { html_url: "https://github.com/owner/repo/pull/2", @@ -77,6 +103,34 @@ describe("campaign_discovery", () => { }); }); + it("should normalize a pull request with worker_workflow", () => { + const pr = { + html_url: "https://github.com/owner/repo/pull/2", + number: 2, + repository: { full_name: "owner/repo" }, + created_at: "2025-01-01T00:00:00Z", + updated_at: "2025-01-02T00:00:00Z", + state: "closed", + title: "Test PR", + merged_at: "2025-01-03T00:00:00Z", + }; + + const normalized = normalizeItem(pr, "pull_request", "fix-pr-worker"); + + expect(normalized).toEqual({ + url: "https://github.com/owner/repo/pull/2", + content_type: "pull_request", + number: 2, + repo: "owner/repo", + created_at: "2025-01-01T00:00:00Z", + updated_at: "2025-01-02T00:00:00Z", + state: "closed", + title: "Test PR", + merged_at: "2025-01-03T00:00:00Z", + worker_workflow: "fix-pr-worker", + }); + }); + it("should normalize a closed issue with closed_at", () => { const issue = { html_url: "https://github.com/owner/repo/issues/3", diff --git a/pkg/cli/templates/orchestrate-agentic-campaign.md b/pkg/cli/templates/orchestrate-agentic-campaign.md index e0c5e56a195..be2f13b8973 100644 --- a/pkg/cli/templates/orchestrate-agentic-campaign.md +++ b/pkg/cli/templates/orchestrate-agentic-campaign.md @@ -183,6 +183,7 @@ and synchronizing campaign state into a GitHub Project board. 3) Parse discovered items from the manifest: - Each item has: url, content_type (issue/pull_request/discussion), number, repo, created_at, updated_at, state + - Items discovered via tracker-id also have: worker_workflow (the workflow name that created the item) - Closed items have: closed_at (for issues) or merged_at (for PRs) - Items are pre-sorted by updated_at for deterministic processing diff --git a/pkg/cli/templates/update-agentic-campaign-project.md b/pkg/cli/templates/update-agentic-campaign-project.md index 8ed4c79a665..0c8f0ca61b2 100644 --- a/pkg/cli/templates/update-agentic-campaign-project.md +++ b/pkg/cli/templates/update-agentic-campaign-project.md @@ -53,7 +53,7 @@ Use **content number** (integer), never the URL as an identifier. These rules apply to any time you write fields: - `campaign_id`: always `{{.CampaignID}}` -- `worker_workflow`: workflow ID if known, else `"unknown"` +- `worker_workflow`: use `worker_workflow` from discovery manifest if present, else `"unknown"` - `repository`: extract `owner/repo` from the issue/PR URL - `priority`: default `Medium` unless explicitly known - `size`: default `Medium` unless explicitly known