From 63c87c7b202577a968ead553988dfed7d5828329 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 8 Mar 2026 19:03:10 +0000
Subject: [PATCH 1/3] Initial plan
From 08039798a340b6c90bddccb9f51af34078eaeaa2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 8 Mar 2026 19:19:47 +0000
Subject: [PATCH 2/3] fix: create missing campaign_discovery.cjs module
Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
---
actions/setup/js/campaign_discovery.cjs | 197 ++++++++++++++++++++++++
1 file changed, 197 insertions(+)
create mode 100644 actions/setup/js/campaign_discovery.cjs
diff --git a/actions/setup/js/campaign_discovery.cjs b/actions/setup/js/campaign_discovery.cjs
new file mode 100644
index 00000000000..b6a24ed5362
--- /dev/null
+++ b/actions/setup/js/campaign_discovery.cjs
@@ -0,0 +1,197 @@
+// @ts-check
+///
+
+/**
+ * Campaign Discovery
+ *
+ * This module precomputes campaign discovery data by:
+ * 1. Searching for issues/PRs labeled with the tracker label in specified repos
+ * 2. Respecting pagination budget (max items, max pages)
+ * 3. Writing discovery results to a JSON file for the AI agent to consume
+ * 4. Setting step outputs with summary information
+ *
+ * Environment variables:
+ * GH_AW_CAMPAIGN_ID - Campaign identifier (required)
+ * GH_AW_TRACKER_LABEL - Label used to track campaign items (required)
+ * GH_AW_DISCOVERY_REPOS - Comma-separated list of "owner/repo" to search (required)
+ * GH_AW_CURSOR_PATH - Path to cursor JSON file for pagination state (optional)
+ * GH_AW_MAX_DISCOVERY_ITEMS - Maximum items to discover per run (default: 50)
+ * GH_AW_MAX_DISCOVERY_PAGES - Maximum pages to search per repo (default: 3)
+ * GH_AW_PROJECT_URL - URL of the GitHub Project board (optional)
+ * GH_AW_WORKFLOWS - Comma-separated list of associated workflow names (optional)
+ */
+
+const fs = require("fs");
+const path = require("path");
+const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs");
+
+/** Default output directory for temporary gh-aw files */
+const GH_AW_TMP_DIR = "/tmp/gh-aw";
+
+/** Output filename for discovery results */
+const DISCOVERY_OUTPUT_FILENAME = "campaign-discovery.json";
+
+/**
+ * Main entry point for campaign discovery
+ * @returns {Promise}
+ */
+async function main() {
+ const campaignId = process.env.GH_AW_CAMPAIGN_ID;
+ const trackerLabel = process.env.GH_AW_TRACKER_LABEL;
+ const discoveryRepos = process.env.GH_AW_DISCOVERY_REPOS;
+ const cursorPath = process.env.GH_AW_CURSOR_PATH;
+ const maxItemsStr = process.env.GH_AW_MAX_DISCOVERY_ITEMS ?? "50";
+ const maxPagesStr = process.env.GH_AW_MAX_DISCOVERY_PAGES ?? "3";
+ const projectUrl = process.env.GH_AW_PROJECT_URL;
+ const workflowsEnv = process.env.GH_AW_WORKFLOWS;
+
+ if (!campaignId) {
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_CAMPAIGN_ID not set`);
+ return;
+ }
+
+ if (!trackerLabel) {
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_TRACKER_LABEL not set`);
+ return;
+ }
+
+ if (!discoveryRepos) {
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_DISCOVERY_REPOS not set`);
+ return;
+ }
+
+ const maxItems = parseInt(maxItemsStr, 10);
+ const maxPages = parseInt(maxPagesStr, 10);
+
+ if (isNaN(maxItems) || maxItems < 1) {
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_MAX_DISCOVERY_ITEMS must be a positive integer, got "${maxItemsStr}"`);
+ return;
+ }
+
+ if (isNaN(maxPages) || maxPages < 1) {
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_MAX_DISCOVERY_PAGES must be a positive integer, got "${maxPagesStr}"`);
+ return;
+ }
+
+ core.info(`Campaign Discovery: ${campaignId}`);
+ core.info(`Tracker label: ${trackerLabel}`);
+ core.info(`Discovery repos: ${discoveryRepos}`);
+ core.info(`Max items: ${maxItems}, Max pages: ${maxPages}`);
+
+ // Parse workflows list
+ const workflows = workflowsEnv
+ ? workflowsEnv
+ .split(",")
+ .map(w => w.trim())
+ .filter(w => w)
+ : [];
+
+ // Parse repos list
+ const repos = discoveryRepos
+ .split(",")
+ .map(r => r.trim())
+ .filter(r => r);
+
+ // Search for issues/PRs with tracker label across all discovery repos
+ /** @type {Array<{id: number, number: number, title: string, state: string, html_url: string, created_at: string, updated_at: string, labels: string[], repo: string, is_pr: boolean}>} */
+ const allItems = [];
+
+ for (const repoPath of repos) {
+ const parts = repoPath.split("/");
+ if (parts.length !== 2) {
+ core.warning(`Invalid repo format: "${repoPath}", expected "owner/repo" — skipping`);
+ continue;
+ }
+
+ const [owner, repo] = parts;
+ core.info(`Searching ${owner}/${repo} for issues with label: ${trackerLabel}`);
+
+ let page = 1;
+
+ while (page <= maxPages && allItems.length < maxItems) {
+ const perPage = Math.min(30, maxItems - allItems.length);
+
+ try {
+ const response = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ labels: trackerLabel,
+ state: "all",
+ sort: "updated",
+ direction: "desc",
+ per_page: perPage,
+ page,
+ });
+
+ if (response.data.length === 0) {
+ core.info(`No more items on page ${page} for ${owner}/${repo}`);
+ break;
+ }
+
+ for (const item of response.data) {
+ if (allItems.length >= maxItems) break;
+ allItems.push({
+ id: item.id,
+ number: item.number,
+ title: item.title,
+ state: item.state,
+ html_url: item.html_url,
+ created_at: item.created_at,
+ updated_at: item.updated_at,
+ labels: item.labels.map(l => (typeof l === "string" ? l : (l.name ?? ""))),
+ repo: `${owner}/${repo}`,
+ is_pr: !!item.pull_request,
+ });
+ }
+
+ core.info(`Page ${page}: fetched ${response.data.length} items (total so far: ${allItems.length})`);
+
+ if (response.data.length < perPage) {
+ break; // No more pages available
+ }
+
+ page++;
+ } catch (err) {
+ core.error(`${ERR_API}: Failed to search ${owner}/${repo} page ${page}: ${getErrorMessage(err)}`);
+ break;
+ }
+ }
+ }
+
+ core.info(`Discovery complete: found ${allItems.length} tracked items`);
+
+ // Write discovery results to a file for the AI agent to consume
+ /** @type {{campaign_id: string, timestamp: string, tracker_label: string, project_url: string | undefined, workflows: string[], items_count: number, items: typeof allItems}} */
+ const discoveryData = {
+ campaign_id: campaignId,
+ timestamp: new Date().toISOString(),
+ tracker_label: trackerLabel,
+ project_url: projectUrl,
+ workflows,
+ items_count: allItems.length,
+ items: allItems,
+ };
+
+ const discoveryOutputPath = path.join(GH_AW_TMP_DIR, DISCOVERY_OUTPUT_FILENAME);
+
+ try {
+ fs.mkdirSync(GH_AW_TMP_DIR, { recursive: true });
+ fs.writeFileSync(discoveryOutputPath, JSON.stringify(discoveryData, null, 2));
+ core.info(`Wrote discovery data to ${discoveryOutputPath}`);
+ } catch (err) {
+ core.warning(`Failed to write discovery data to ${discoveryOutputPath}: ${getErrorMessage(err)}`);
+ }
+
+ // Set step outputs for downstream steps or summary
+ core.setOutput("items_count", String(allItems.length));
+ core.setOutput("campaign_id", campaignId);
+ core.setOutput("discovery_file", discoveryOutputPath);
+
+ core.info(`✓ Campaign discovery complete: ${allItems.length} item(s) found for campaign "${campaignId}"`);
+ if (cursorPath) {
+ core.info(`Note: Cursor path configured at ${cursorPath} (managed by repo-memory)`);
+ }
+}
+
+module.exports = { main };
From 2f8054357d68843fc1c0b506ae3f4aca9036c712 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 8 Mar 2026 21:25:41 +0000
Subject: [PATCH 3/3] fix: remove campaign_discovery.cjs and inline logic in
campaign workflow
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
...ecurity-alert-burndown.campaign.g.lock.yml | 9 +-
.../security-alert-burndown.campaign.g.md | 57 ++++-
actions/setup/README.md | 6 +-
actions/setup/js/campaign_discovery.cjs | 197 ------------------
4 files changed, 57 insertions(+), 212 deletions(-)
delete mode 100644 actions/setup/js/campaign_discovery.cjs
diff --git a/.github/workflows/security-alert-burndown.campaign.g.lock.yml b/.github/workflows/security-alert-burndown.campaign.g.lock.yml
index ef97cce30ec..cd3004660cd 100644
--- a/.github/workflows/security-alert-burndown.campaign.g.lock.yml
+++ b/.github/workflows/security-alert-burndown.campaign.g.lock.yml
@@ -23,7 +23,7 @@
#
# Orchestrator workflow for campaign 'security-alert-burndown'
#
-# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"44e9ad89fc3f881e52e25ebc92ed461a8df570529c59e8a155cfb0e503531a80","strict":true}
+# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"4cabee9e7e0a3b1a2f3c07dce2b2a763a9e8aeadbb3e8228389b45f8255ac805","strict":true}
name: "Security Alert Burndown"
"on":
@@ -297,12 +297,7 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- script: |-
-
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/campaign_discovery.cjs');
- await main();
+ script: "const fs = require(\"fs\");\nconst path = require(\"path\");\n\nconst campaignId = process.env.GH_AW_CAMPAIGN_ID;\nconst trackerLabel = process.env.GH_AW_TRACKER_LABEL;\nconst discoveryRepos = process.env.GH_AW_DISCOVERY_REPOS;\nconst cursorPath = process.env.GH_AW_CURSOR_PATH;\nconst maxItems = parseInt(process.env.GH_AW_MAX_DISCOVERY_ITEMS ?? \"50\", 10);\nconst maxPages = parseInt(process.env.GH_AW_MAX_DISCOVERY_PAGES ?? \"3\", 10);\nconst projectUrl = process.env.GH_AW_PROJECT_URL;\nconst workflows = (process.env.GH_AW_WORKFLOWS ?? \"\").split(\",\").map(w => w.trim()).filter(Boolean);\n\nif (!campaignId || !trackerLabel || !discoveryRepos) {\n core.setFailed(\"Missing required environment variables: GH_AW_CAMPAIGN_ID, GH_AW_TRACKER_LABEL, GH_AW_DISCOVERY_REPOS\");\n return;\n}\n\nconst repos = discoveryRepos.split(\",\").map(r => r.trim()).filter(Boolean);\nconst allItems = [];\n\nfor (const repoPath of repos) {\n const parts = repoPath.split(\"/\");\n if (parts.length !== 2) { core.warning(`Invalid repo format: \"${repoPath}\" — skipping`); continue; }\n const [owner, repo] = parts;\n let page = 1;\n while (page <= maxPages && allItems.length < maxItems) {\n const perPage = Math.min(30, maxItems - allItems.length);\n try {\n const response = await github.rest.issues.listForRepo({ owner, repo, labels: trackerLabel, state: \"all\", sort: \"updated\", direction: \"desc\", per_page: perPage, page });\n if (response.data.length === 0) break;\n for (const item of response.data) {\n if (allItems.length >= maxItems) break;\n allItems.push({ id: item.id, number: item.number, title: item.title, state: item.state, html_url: item.html_url, created_at: item.created_at, updated_at: item.updated_at, labels: item.labels.map(l => typeof l === \"string\" ? l : (l.name ?? \"\")), repo: `${owner}/${repo}`, is_pr: !!item.pull_request });\n }\n if (response.data.length < perPage) break;\n page++;\n } catch (err) { core.error(`Failed to search ${owner}/${repo} page ${page}: ${err.message}`); break; }\n }\n}\n\nconst GH_AW_TMP_DIR = \"/tmp/gh-aw\";\nconst discoveryOutputPath = path.join(GH_AW_TMP_DIR, \"campaign-discovery.json\");\nconst discoveryData = { campaign_id: campaignId, timestamp: new Date().toISOString(), tracker_label: trackerLabel, project_url: projectUrl, workflows, items_count: allItems.length, items: allItems };\ntry {\n fs.mkdirSync(GH_AW_TMP_DIR, { recursive: true });\n fs.writeFileSync(discoveryOutputPath, JSON.stringify(discoveryData, null, 2));\n} catch (err) { core.warning(`Failed to write discovery data: ${err.message}`); }\n\ncore.setOutput(\"items_count\", String(allItems.length));\ncore.setOutput(\"campaign_id\", campaignId);\ncore.setOutput(\"discovery_file\", discoveryOutputPath);\ncore.info(`✓ Campaign discovery complete: ${allItems.length} item(s) found for campaign \"${campaignId}\"`);"
# Repo memory git-based storage configuration from frontmatter processed below
- name: Clone repo-memory branch (campaigns)
diff --git a/.github/workflows/security-alert-burndown.campaign.g.md b/.github/workflows/security-alert-burndown.campaign.g.md
index 47b03ffd39e..d9ae3e084f6 100644
--- a/.github/workflows/security-alert-burndown.campaign.g.md
+++ b/.github/workflows/security-alert-burndown.campaign.g.md
@@ -57,11 +57,58 @@ steps:
with:
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
-
- const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io);
- const { main } = require('/opt/gh-aw/actions/campaign_discovery.cjs');
- await main();
+ const fs = require("fs");
+ const path = require("path");
+
+ const campaignId = process.env.GH_AW_CAMPAIGN_ID;
+ const trackerLabel = process.env.GH_AW_TRACKER_LABEL;
+ const discoveryRepos = process.env.GH_AW_DISCOVERY_REPOS;
+ const cursorPath = process.env.GH_AW_CURSOR_PATH;
+ const maxItems = parseInt(process.env.GH_AW_MAX_DISCOVERY_ITEMS ?? "50", 10);
+ const maxPages = parseInt(process.env.GH_AW_MAX_DISCOVERY_PAGES ?? "3", 10);
+ const projectUrl = process.env.GH_AW_PROJECT_URL;
+ const workflows = (process.env.GH_AW_WORKFLOWS ?? "").split(",").map(w => w.trim()).filter(Boolean);
+
+ if (!campaignId || !trackerLabel || !discoveryRepos) {
+ core.setFailed("Missing required environment variables: GH_AW_CAMPAIGN_ID, GH_AW_TRACKER_LABEL, GH_AW_DISCOVERY_REPOS");
+ return;
+ }
+
+ const repos = discoveryRepos.split(",").map(r => r.trim()).filter(Boolean);
+ const allItems = [];
+
+ for (const repoPath of repos) {
+ const parts = repoPath.split("/");
+ if (parts.length !== 2) { core.warning(`Invalid repo format: "${repoPath}" — skipping`); continue; }
+ const [owner, repo] = parts;
+ let page = 1;
+ while (page <= maxPages && allItems.length < maxItems) {
+ const perPage = Math.min(30, maxItems - allItems.length);
+ try {
+ const response = await github.rest.issues.listForRepo({ owner, repo, labels: trackerLabel, state: "all", sort: "updated", direction: "desc", per_page: perPage, page });
+ if (response.data.length === 0) break;
+ for (const item of response.data) {
+ if (allItems.length >= maxItems) break;
+ allItems.push({ id: item.id, number: item.number, title: item.title, state: item.state, html_url: item.html_url, created_at: item.created_at, updated_at: item.updated_at, labels: item.labels.map(l => typeof l === "string" ? l : (l.name ?? "")), repo: `${owner}/${repo}`, is_pr: !!item.pull_request });
+ }
+ if (response.data.length < perPage) break;
+ page++;
+ } catch (err) { core.error(`Failed to search ${owner}/${repo} page ${page}: ${err.message}`); break; }
+ }
+ }
+
+ const GH_AW_TMP_DIR = "/tmp/gh-aw";
+ const discoveryOutputPath = path.join(GH_AW_TMP_DIR, "campaign-discovery.json");
+ const discoveryData = { campaign_id: campaignId, timestamp: new Date().toISOString(), tracker_label: trackerLabel, project_url: projectUrl, workflows, items_count: allItems.length, items: allItems };
+ try {
+ fs.mkdirSync(GH_AW_TMP_DIR, { recursive: true });
+ fs.writeFileSync(discoveryOutputPath, JSON.stringify(discoveryData, null, 2));
+ } catch (err) { core.warning(`Failed to write discovery data: ${err.message}`); }
+
+ core.setOutput("items_count", String(allItems.length));
+ core.setOutput("campaign_id", campaignId);
+ core.setOutput("discovery_file", discoveryOutputPath);
+ core.info(`✓ Campaign discovery complete: ${allItems.length} item(s) found for campaign "${campaignId}"`);
---
diff --git a/actions/setup/README.md b/actions/setup/README.md
index 7adac3541e4..5bec261ce2b 100644
--- a/actions/setup/README.md
+++ b/actions/setup/README.md
@@ -7,7 +7,7 @@ This action copies workflow script files to the agent environment.
This action runs in all workflow jobs to provide scripts that can be used instead of being inlined in the workflow. This includes scripts for activation jobs, agent jobs, and safe-output jobs.
The action copies:
-- 117 `.cjs` JavaScript files from the `js/` directory
+- 226 `.cjs` JavaScript files from the `js/` directory
- 7 `.sh` shell scripts from the `sh/` directory
All files are copied to the destination directory (default: `/tmp/gh-aw/actions`). These files are generated by running `make actions-build` and are committed to the repository.
@@ -35,7 +35,7 @@ Default: `/tmp/gh-aw/actions`
### `files-copied`
-The number of files copied to the destination directory (should be 124: 117 JavaScript files + 7 shell scripts).
+The number of files copied to the destination directory (should be 233: 226 JavaScript files + 7 shell scripts).
## Example
@@ -56,7 +56,7 @@ steps:
This action copies files from `actions/setup/`, including:
-### JavaScript Files (117 files from `js/`)
+### JavaScript Files (226 files from `js/`)
- Activation job scripts (check_stop_time, check_skip_if_match, check_command_position, etc.)
- Agent job scripts (compute_text, create_issue, create_pull_request, etc.)
- Safe output scripts (safe_outputs_*, safe_inputs_*, messages, etc.)
diff --git a/actions/setup/js/campaign_discovery.cjs b/actions/setup/js/campaign_discovery.cjs
deleted file mode 100644
index b6a24ed5362..00000000000
--- a/actions/setup/js/campaign_discovery.cjs
+++ /dev/null
@@ -1,197 +0,0 @@
-// @ts-check
-///
-
-/**
- * Campaign Discovery
- *
- * This module precomputes campaign discovery data by:
- * 1. Searching for issues/PRs labeled with the tracker label in specified repos
- * 2. Respecting pagination budget (max items, max pages)
- * 3. Writing discovery results to a JSON file for the AI agent to consume
- * 4. Setting step outputs with summary information
- *
- * Environment variables:
- * GH_AW_CAMPAIGN_ID - Campaign identifier (required)
- * GH_AW_TRACKER_LABEL - Label used to track campaign items (required)
- * GH_AW_DISCOVERY_REPOS - Comma-separated list of "owner/repo" to search (required)
- * GH_AW_CURSOR_PATH - Path to cursor JSON file for pagination state (optional)
- * GH_AW_MAX_DISCOVERY_ITEMS - Maximum items to discover per run (default: 50)
- * GH_AW_MAX_DISCOVERY_PAGES - Maximum pages to search per repo (default: 3)
- * GH_AW_PROJECT_URL - URL of the GitHub Project board (optional)
- * GH_AW_WORKFLOWS - Comma-separated list of associated workflow names (optional)
- */
-
-const fs = require("fs");
-const path = require("path");
-const { getErrorMessage } = require("./error_helpers.cjs");
-const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs");
-
-/** Default output directory for temporary gh-aw files */
-const GH_AW_TMP_DIR = "/tmp/gh-aw";
-
-/** Output filename for discovery results */
-const DISCOVERY_OUTPUT_FILENAME = "campaign-discovery.json";
-
-/**
- * Main entry point for campaign discovery
- * @returns {Promise}
- */
-async function main() {
- const campaignId = process.env.GH_AW_CAMPAIGN_ID;
- const trackerLabel = process.env.GH_AW_TRACKER_LABEL;
- const discoveryRepos = process.env.GH_AW_DISCOVERY_REPOS;
- const cursorPath = process.env.GH_AW_CURSOR_PATH;
- const maxItemsStr = process.env.GH_AW_MAX_DISCOVERY_ITEMS ?? "50";
- const maxPagesStr = process.env.GH_AW_MAX_DISCOVERY_PAGES ?? "3";
- const projectUrl = process.env.GH_AW_PROJECT_URL;
- const workflowsEnv = process.env.GH_AW_WORKFLOWS;
-
- if (!campaignId) {
- core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_CAMPAIGN_ID not set`);
- return;
- }
-
- if (!trackerLabel) {
- core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_TRACKER_LABEL not set`);
- return;
- }
-
- if (!discoveryRepos) {
- core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_DISCOVERY_REPOS not set`);
- return;
- }
-
- const maxItems = parseInt(maxItemsStr, 10);
- const maxPages = parseInt(maxPagesStr, 10);
-
- if (isNaN(maxItems) || maxItems < 1) {
- core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_MAX_DISCOVERY_ITEMS must be a positive integer, got "${maxItemsStr}"`);
- return;
- }
-
- if (isNaN(maxPages) || maxPages < 1) {
- core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_MAX_DISCOVERY_PAGES must be a positive integer, got "${maxPagesStr}"`);
- return;
- }
-
- core.info(`Campaign Discovery: ${campaignId}`);
- core.info(`Tracker label: ${trackerLabel}`);
- core.info(`Discovery repos: ${discoveryRepos}`);
- core.info(`Max items: ${maxItems}, Max pages: ${maxPages}`);
-
- // Parse workflows list
- const workflows = workflowsEnv
- ? workflowsEnv
- .split(",")
- .map(w => w.trim())
- .filter(w => w)
- : [];
-
- // Parse repos list
- const repos = discoveryRepos
- .split(",")
- .map(r => r.trim())
- .filter(r => r);
-
- // Search for issues/PRs with tracker label across all discovery repos
- /** @type {Array<{id: number, number: number, title: string, state: string, html_url: string, created_at: string, updated_at: string, labels: string[], repo: string, is_pr: boolean}>} */
- const allItems = [];
-
- for (const repoPath of repos) {
- const parts = repoPath.split("/");
- if (parts.length !== 2) {
- core.warning(`Invalid repo format: "${repoPath}", expected "owner/repo" — skipping`);
- continue;
- }
-
- const [owner, repo] = parts;
- core.info(`Searching ${owner}/${repo} for issues with label: ${trackerLabel}`);
-
- let page = 1;
-
- while (page <= maxPages && allItems.length < maxItems) {
- const perPage = Math.min(30, maxItems - allItems.length);
-
- try {
- const response = await github.rest.issues.listForRepo({
- owner,
- repo,
- labels: trackerLabel,
- state: "all",
- sort: "updated",
- direction: "desc",
- per_page: perPage,
- page,
- });
-
- if (response.data.length === 0) {
- core.info(`No more items on page ${page} for ${owner}/${repo}`);
- break;
- }
-
- for (const item of response.data) {
- if (allItems.length >= maxItems) break;
- allItems.push({
- id: item.id,
- number: item.number,
- title: item.title,
- state: item.state,
- html_url: item.html_url,
- created_at: item.created_at,
- updated_at: item.updated_at,
- labels: item.labels.map(l => (typeof l === "string" ? l : (l.name ?? ""))),
- repo: `${owner}/${repo}`,
- is_pr: !!item.pull_request,
- });
- }
-
- core.info(`Page ${page}: fetched ${response.data.length} items (total so far: ${allItems.length})`);
-
- if (response.data.length < perPage) {
- break; // No more pages available
- }
-
- page++;
- } catch (err) {
- core.error(`${ERR_API}: Failed to search ${owner}/${repo} page ${page}: ${getErrorMessage(err)}`);
- break;
- }
- }
- }
-
- core.info(`Discovery complete: found ${allItems.length} tracked items`);
-
- // Write discovery results to a file for the AI agent to consume
- /** @type {{campaign_id: string, timestamp: string, tracker_label: string, project_url: string | undefined, workflows: string[], items_count: number, items: typeof allItems}} */
- const discoveryData = {
- campaign_id: campaignId,
- timestamp: new Date().toISOString(),
- tracker_label: trackerLabel,
- project_url: projectUrl,
- workflows,
- items_count: allItems.length,
- items: allItems,
- };
-
- const discoveryOutputPath = path.join(GH_AW_TMP_DIR, DISCOVERY_OUTPUT_FILENAME);
-
- try {
- fs.mkdirSync(GH_AW_TMP_DIR, { recursive: true });
- fs.writeFileSync(discoveryOutputPath, JSON.stringify(discoveryData, null, 2));
- core.info(`Wrote discovery data to ${discoveryOutputPath}`);
- } catch (err) {
- core.warning(`Failed to write discovery data to ${discoveryOutputPath}: ${getErrorMessage(err)}`);
- }
-
- // Set step outputs for downstream steps or summary
- core.setOutput("items_count", String(allItems.length));
- core.setOutput("campaign_id", campaignId);
- core.setOutput("discovery_file", discoveryOutputPath);
-
- core.info(`✓ Campaign discovery complete: ${allItems.length} item(s) found for campaign "${campaignId}"`);
- if (cursorPath) {
- core.info(`Note: Cursor path configured at ${cursorPath} (managed by repo-memory)`);
- }
-}
-
-module.exports = { main };