From 01c1cae8ca81384e7e18a5fa64c0e33762b8e94c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 04:24:34 +0000 Subject: [PATCH 1/5] Initial plan From 2e1f2dc3bb6953229a41ccb3a57bc9bbffc47b04 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 04:33:18 +0000 Subject: [PATCH 2/5] Document project view creation feature in safe-outputs and project-management guides Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- docs/package-lock.json | 19 +++- .../guides/campaigns/project-management.md | 36 ++++++ .../content/docs/reference/safe-outputs.md | 105 ++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 4b70cd48e8..7c841e7ce4 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -165,6 +165,7 @@ "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.35.2.tgz", "integrity": "sha512-curGghoW4s5pCbW2tINsJPoxEYPan87ptCOv7GZ+S24N3J6AyaOu/OsjZDEMaIpo3ZlObM5DQn+w7iXl3drDhQ==", "license": "MIT", + "peer": true, "dependencies": { "@astrojs/markdown-remark": "^6.3.1", "@astrojs/mdx": "^4.2.3", @@ -399,6 +400,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -1396,6 +1398,7 @@ "integrity": "sha512-DNCbwkAKugzCtiHJg/7DciIRwnKwAI2QH3VWWC1cVxoBBQTPnH5D9HcWqpDdduUqnCuW2PY78afVo+QlaInDdQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@csstools/postcss-is-pseudo-class": "^5.0.3", "cssesc": "^3.0.0", @@ -2437,6 +2440,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2620,6 +2624,7 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.9.tgz", "integrity": "sha512-XLDXxu0282cC/oYHswWZm3johGlRvk9rLRS7pWVWSne+HsZe9JgrpHI+vewAJSSNHBGd1aCyaQOElT5RNGe7IQ==", "license": "MIT", + "peer": true, "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.5", @@ -3206,6 +3211,7 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", @@ -3600,6 +3606,7 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -4000,6 +4007,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -4240,7 +4248,8 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1534754.tgz", "integrity": "sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/dfa": { "version": "1.2.0", @@ -6153,6 +6162,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -6555,6 +6565,7 @@ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.1.tgz", "integrity": "sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==", "license": "MIT", + "peer": true, "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", @@ -7972,6 +7983,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8086,6 +8098,7 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8723,6 +8736,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -9998,6 +10012,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -10288,6 +10303,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -10426,6 +10442,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/docs/src/content/docs/guides/campaigns/project-management.md b/docs/src/content/docs/guides/campaigns/project-management.md index 90257ec5b2..cb8e78a60d 100644 --- a/docs/src/content/docs/guides/campaigns/project-management.md +++ b/docs/src/content/docs/guides/campaigns/project-management.md @@ -168,6 +168,42 @@ The campaign generator creates three views automatically: 2. **Task Tracker** (Table view) - Detailed tracking with filtering 3. **Progress Board** (Board view) - Kanban-style progress tracking +### Creating views programmatically + +Views can be created programmatically using the `update-project` safe output with `operation: "create_view"`. This is useful for custom campaign setups or automating project board configuration. + +**Example: Create a roadmap view** +```yaml +safe-outputs: + update-project: + github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} +``` + +```javascript +// In agent workflow +update_project({ + project: "https://github.com/orgs/myorg/projects/42", + operation: "create_view", + view: { + name: "Q1 Sprint Timeline", + layout: "roadmap", + filter: "is:issue is:open" + } +}); +``` + +**View layouts:** +- `table` — List view with customizable columns +- `board` — Kanban-style cards grouped by field +- `roadmap` — Timeline with date-based swimlanes + +**Common filters:** +- `is:issue,is:pull_request` — Show both issues and PRs +- `label:campaign:migration-q1` — Filter by campaign label +- `status:"In Progress"` — Show items with specific status + +See [Safe Outputs Reference](/gh-aw/reference/safe-outputs/#creating-project-views) for complete documentation on view creation parameters and examples. + **Customization tips:** **Multi-Workflow Campaign**: Use Roadmap grouped by Worker/Workflow for timeline distribution, Task Tracker sliced by Priority+Status for urgent items, Progress Board grouped by Status for progress tracking. diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 62855748c0..e4fdbf5cb6 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -348,6 +348,111 @@ fields: > [!NOTE] > Field names are case-insensitive and automatically normalized (e.g., `story_points` matches `Story Points`). +#### Creating Project Views + +The `update-project` tool can create custom views (table, board, or roadmap layouts) on GitHub Projects v2 boards programmatically. This is useful for setting up campaign dashboards, creating custom filtering views, or automating project board configuration. + +To create a view, set `operation: "create_view"` and provide view configuration in the `view` parameter. + +**Basic view creation:** +```javascript +update_project({ + project: "https://github.com/orgs/myorg/projects/42", + operation: "create_view", + view: { + name: "Campaign Roadmap", + layout: "roadmap", + filter: "is:issue,is:pull_request" + } +}); +``` + +##### View Configuration Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | string | Yes | Name of the view (e.g., "Sprint Board", "Task Tracker") | +| `layout` | string | Yes | View layout: `table`, `board`, or `roadmap` | +| `filter` | string | No | Filter query for items (e.g., `is:issue is:open`, `label:bug`) | +| `visible_fields` | array | No | Array of field IDs to display (table/board only, not applicable to roadmap) | +| `description` | string | No | Human description (not supported by GitHub API, will be ignored) | + +##### View Layouts + +**Table View** — Detailed list with customizable columns: +```javascript +update_project({ + project: "https://github.com/orgs/myorg/projects/42", + operation: "create_view", + view: { + name: "Task Tracker", + layout: "table", + filter: "is:issue is:open" + } +}); +``` + +**Board View** — Kanban-style cards grouped by status: +```javascript +update_project({ + project: "https://github.com/orgs/myorg/projects/42", + operation: "create_view", + view: { + name: "Progress Board", + layout: "board", + filter: "is:issue" + } +}); +``` + +**Roadmap View** — Timeline visualization with swimlanes: +```javascript +update_project({ + project: "https://github.com/orgs/myorg/projects/42", + operation: "create_view", + view: { + name: "Sprint Timeline", + layout: "roadmap", + filter: "is:issue,is:pull_request" + } +}); +``` + +##### View Filters + +Filters use GitHub's project query syntax to show specific items. Common patterns: + +- `is:issue is:open` — Open issues only +- `is:pull_request` — Pull requests only +- `is:issue,is:pull_request` — Both issues and PRs +- `label:bug` — Items with bug label +- `assignee:@me` — Items assigned to viewer +- `status:"In Progress"` — Items with specific status field value + +Multiple filters can be combined with spaces (AND logic) or commas (OR logic). + +##### Field Visibility (Table/Board Only) + +The `visible_fields` parameter controls which custom fields appear in table and board views. This requires field IDs, which can be obtained from the project's GraphQL API or by inspecting existing views. + +> [!NOTE] +> The `visible_fields` parameter is not applicable to roadmap views and will be ignored if provided. + +##### Outputs + +View creation exposes these outputs: +- `view-id` — Unique identifier for the created view +- `view-url` — Direct URL to the view + +##### Campaign Generator Integration + +The [Campaign Generator](/gh-aw/guides/campaigns/) automatically creates three default views when setting up a campaign: +1. **Campaign Roadmap** (roadmap layout) — Timeline visualization +2. **Task Tracker** (table layout) — Detailed tracking with filtering +3. **Progress Board** (board layout) — Kanban-style progress tracking + +For manual view configuration and advanced filtering strategies, see [Project Management Guide](/gh-aw/guides/campaigns/project-management/). + ### Project Board Copy (`copy-project:`) From 765b23655691f9b5b91b917e621d96d2742d6b13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 04:44:37 +0000 Subject: [PATCH 3/5] Revert programmatic view creation documentation per feedback Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .../guides/campaigns/project-management.md | 36 ------ .../content/docs/reference/safe-outputs.md | 105 ------------------ 2 files changed, 141 deletions(-) diff --git a/docs/src/content/docs/guides/campaigns/project-management.md b/docs/src/content/docs/guides/campaigns/project-management.md index cb8e78a60d..90257ec5b2 100644 --- a/docs/src/content/docs/guides/campaigns/project-management.md +++ b/docs/src/content/docs/guides/campaigns/project-management.md @@ -168,42 +168,6 @@ The campaign generator creates three views automatically: 2. **Task Tracker** (Table view) - Detailed tracking with filtering 3. **Progress Board** (Board view) - Kanban-style progress tracking -### Creating views programmatically - -Views can be created programmatically using the `update-project` safe output with `operation: "create_view"`. This is useful for custom campaign setups or automating project board configuration. - -**Example: Create a roadmap view** -```yaml -safe-outputs: - update-project: - github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} -``` - -```javascript -// In agent workflow -update_project({ - project: "https://github.com/orgs/myorg/projects/42", - operation: "create_view", - view: { - name: "Q1 Sprint Timeline", - layout: "roadmap", - filter: "is:issue is:open" - } -}); -``` - -**View layouts:** -- `table` — List view with customizable columns -- `board` — Kanban-style cards grouped by field -- `roadmap` — Timeline with date-based swimlanes - -**Common filters:** -- `is:issue,is:pull_request` — Show both issues and PRs -- `label:campaign:migration-q1` — Filter by campaign label -- `status:"In Progress"` — Show items with specific status - -See [Safe Outputs Reference](/gh-aw/reference/safe-outputs/#creating-project-views) for complete documentation on view creation parameters and examples. - **Customization tips:** **Multi-Workflow Campaign**: Use Roadmap grouped by Worker/Workflow for timeline distribution, Task Tracker sliced by Priority+Status for urgent items, Progress Board grouped by Status for progress tracking. diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index e4fdbf5cb6..62855748c0 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -348,111 +348,6 @@ fields: > [!NOTE] > Field names are case-insensitive and automatically normalized (e.g., `story_points` matches `Story Points`). -#### Creating Project Views - -The `update-project` tool can create custom views (table, board, or roadmap layouts) on GitHub Projects v2 boards programmatically. This is useful for setting up campaign dashboards, creating custom filtering views, or automating project board configuration. - -To create a view, set `operation: "create_view"` and provide view configuration in the `view` parameter. - -**Basic view creation:** -```javascript -update_project({ - project: "https://github.com/orgs/myorg/projects/42", - operation: "create_view", - view: { - name: "Campaign Roadmap", - layout: "roadmap", - filter: "is:issue,is:pull_request" - } -}); -``` - -##### View Configuration Parameters - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `name` | string | Yes | Name of the view (e.g., "Sprint Board", "Task Tracker") | -| `layout` | string | Yes | View layout: `table`, `board`, or `roadmap` | -| `filter` | string | No | Filter query for items (e.g., `is:issue is:open`, `label:bug`) | -| `visible_fields` | array | No | Array of field IDs to display (table/board only, not applicable to roadmap) | -| `description` | string | No | Human description (not supported by GitHub API, will be ignored) | - -##### View Layouts - -**Table View** — Detailed list with customizable columns: -```javascript -update_project({ - project: "https://github.com/orgs/myorg/projects/42", - operation: "create_view", - view: { - name: "Task Tracker", - layout: "table", - filter: "is:issue is:open" - } -}); -``` - -**Board View** — Kanban-style cards grouped by status: -```javascript -update_project({ - project: "https://github.com/orgs/myorg/projects/42", - operation: "create_view", - view: { - name: "Progress Board", - layout: "board", - filter: "is:issue" - } -}); -``` - -**Roadmap View** — Timeline visualization with swimlanes: -```javascript -update_project({ - project: "https://github.com/orgs/myorg/projects/42", - operation: "create_view", - view: { - name: "Sprint Timeline", - layout: "roadmap", - filter: "is:issue,is:pull_request" - } -}); -``` - -##### View Filters - -Filters use GitHub's project query syntax to show specific items. Common patterns: - -- `is:issue is:open` — Open issues only -- `is:pull_request` — Pull requests only -- `is:issue,is:pull_request` — Both issues and PRs -- `label:bug` — Items with bug label -- `assignee:@me` — Items assigned to viewer -- `status:"In Progress"` — Items with specific status field value - -Multiple filters can be combined with spaces (AND logic) or commas (OR logic). - -##### Field Visibility (Table/Board Only) - -The `visible_fields` parameter controls which custom fields appear in table and board views. This requires field IDs, which can be obtained from the project's GraphQL API or by inspecting existing views. - -> [!NOTE] -> The `visible_fields` parameter is not applicable to roadmap views and will be ignored if provided. - -##### Outputs - -View creation exposes these outputs: -- `view-id` — Unique identifier for the created view -- `view-url` — Direct URL to the view - -##### Campaign Generator Integration - -The [Campaign Generator](/gh-aw/guides/campaigns/) automatically creates three default views when setting up a campaign: -1. **Campaign Roadmap** (roadmap layout) — Timeline visualization -2. **Task Tracker** (table layout) — Detailed tracking with filtering -3. **Progress Board** (board layout) — Kanban-style progress tracking - -For manual view configuration and advanced filtering strategies, see [Project Management Guide](/gh-aw/guides/campaigns/project-management/). - ### Project Board Copy (`copy-project:`) From ee30edb47c0c6ed0ab00a2b9d0d90b60eaeb085c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 05:08:17 +0000 Subject: [PATCH 4/5] Add views property to update-project frontmatter configuration Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- actions/setup/js/update_project.cjs | 60 ++++++++++++++- pkg/parser/schemas/main_workflow_schema.json | 38 +++++++++- .../compiler_safe_outputs_specialized.go | 9 +++ pkg/workflow/update_project.go | 74 ++++++++++++++++++- pkg/workflow/update_project_job.go | 10 +++ 5 files changed, 186 insertions(+), 5 deletions(-) diff --git a/actions/setup/js/update_project.cjs b/actions/setup/js/update_project.cjs index 012e103e8f..6e9f59cb2e 100644 --- a/actions/setup/js/update_project.cjs +++ b/actions/setup/js/update_project.cjs @@ -955,8 +955,25 @@ async function main() { if (!result.success) return; const updateProjectItems = result.items.filter(item => item.type === "update_project"); - if (updateProjectItems.length === 0) return; + // Check if views are configured in frontmatter + const configuredViews = process.env.GH_AW_PROJECT_VIEWS; + let viewsToCreate = []; + if (configuredViews) { + try { + viewsToCreate = JSON.parse(configuredViews); + if (Array.isArray(viewsToCreate) && viewsToCreate.length > 0) { + core.info(`Found ${viewsToCreate.length} configured view(s) in frontmatter`); + } + } catch (parseError) { + core.warning(`Failed to parse GH_AW_PROJECT_VIEWS: ${getErrorMessage(parseError)}`); + } + } + + // If no update_project items and no configured views, nothing to do + if (updateProjectItems.length === 0 && viewsToCreate.length === 0) return; + + // Process update_project items from agent output for (let i = 0; i < updateProjectItems.length; i++) { const output = updateProjectItems[i]; try { @@ -968,6 +985,47 @@ async function main() { logGraphQLError(error, `Processing update_project item ${i + 1}`); } } + + // Create views from frontmatter configuration if any + // Views are created after items are processed to ensure the project exists + if (viewsToCreate.length > 0) { + // Get project URL from the first update_project item or fail if none + const projectUrl = updateProjectItems.length > 0 ? updateProjectItems[0].project : null; + + if (!projectUrl) { + core.warning("Cannot create configured views: no project URL found in update_project items. Views require at least one update_project operation to determine the target project."); + return; + } + + core.info(`Creating ${viewsToCreate.length} configured view(s) on project: ${projectUrl}`); + + for (let i = 0; i < viewsToCreate.length; i++) { + const viewConfig = viewsToCreate[i]; + try { + // Create a synthetic output item for view creation + const viewOutput = { + type: "update_project", + project: projectUrl, + operation: "create_view", + view: { + name: viewConfig.name, + layout: viewConfig.layout, + filter: viewConfig.filter, + visible_fields: viewConfig.visible_fields, + description: viewConfig.description, + }, + }; + + await updateProject(viewOutput); + core.info(`✓ Created view ${i + 1}/${viewsToCreate.length}: ${viewConfig.name} (${viewConfig.layout})`); + } catch (err) { + // prettier-ignore + const error = /** @type {Error & { errors?: Array<{ type?: string, message: string, path?: unknown, locations?: unknown }>, request?: unknown, data?: unknown }} */ (err); + core.error(`Failed to create configured view ${i + 1}: ${viewConfig.name}`); + logGraphQLError(error, `Creating configured view: ${viewConfig.name}`); + } + } + } } module.exports = { updateProject, parseProjectInput, generateCampaignId, main }; diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 0762613d40..3c002aa51d 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -3694,7 +3694,7 @@ "oneOf": [ { "type": "object", - "description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, campaign_id, and create_if_missing.", + "description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project Configuration also supports an optional views array for declaring project views to create. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, campaign_id, and create_if_missing.", "properties": { "max": { "type": "integer", @@ -3705,6 +3705,42 @@ "github-token": { "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + }, + "views": { + "type": "array", + "description": "Optional array of project views to create. Each view must have a name and layout. Views are created during project setup.", + "items": { + "type": "object", + "description": "View configuration for creating project views", + "required": ["name", "layout"], + "properties": { + "name": { + "type": "string", + "description": "The name of the view (e.g., 'Sprint Board', 'Campaign Roadmap')" + }, + "layout": { + "type": "string", + "enum": ["table", "board", "roadmap"], + "description": "The layout type of the view" + }, + "filter": { + "type": "string", + "description": "Optional filter query for the view (e.g., 'is:issue is:open', 'label:bug')" + }, + "visible-fields": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Optional array of field IDs that should be visible in the view (table/board only, not applicable to roadmap)" + }, + "description": { + "type": "string", + "description": "Optional human description for the view. Not supported by the GitHub Views API and may be ignored." + } + }, + "additionalProperties": false + } } }, "additionalProperties": false, diff --git a/pkg/workflow/compiler_safe_outputs_specialized.go b/pkg/workflow/compiler_safe_outputs_specialized.go index 8b81f4af5f..0d919aee2a 100644 --- a/pkg/workflow/compiler_safe_outputs_specialized.go +++ b/pkg/workflow/compiler_safe_outputs_specialized.go @@ -1,6 +1,7 @@ package workflow import ( + "encoding/json" "fmt" ) @@ -57,6 +58,14 @@ func (c *Compiler) buildUpdateProjectStepConfig(data *WorkflowData, mainJobName var customEnvVars []string customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) + // If views are configured in frontmatter, pass them to the JavaScript via environment variable + if cfg != nil && len(cfg.Views) > 0 { + viewsJSON, err := json.Marshal(cfg.Views) + if err == nil { + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_PROJECT_VIEWS: '%s'\n", string(viewsJSON))) + } + } + condition := BuildSafeOutputType("update_project") return SafeOutputStepConfig{ diff --git a/pkg/workflow/update_project.go b/pkg/workflow/update_project.go index 3531105240..f2127d3f6f 100644 --- a/pkg/workflow/update_project.go +++ b/pkg/workflow/update_project.go @@ -4,10 +4,20 @@ import "github.com/githubnext/gh-aw/pkg/logger" var updateProjectLog = logger.New("workflow:update_project") +// ProjectView defines a project view configuration +type ProjectView struct { + Name string `yaml:"name" json:"name"` + Layout string `yaml:"layout" json:"layout"` + Filter string `yaml:"filter,omitempty" json:"filter,omitempty"` + VisibleFields []int `yaml:"visible-fields,omitempty" json:"visible_fields,omitempty"` + Description string `yaml:"description,omitempty" json:"description,omitempty"` +} + // UpdateProjectConfig holds configuration for unified project board management type UpdateProjectConfig struct { BaseSafeOutputConfig `yaml:",inline"` - GitHubToken string `yaml:"github-token,omitempty"` + GitHubToken string `yaml:"github-token,omitempty"` + Views []ProjectView `yaml:"views,omitempty"` } // parseUpdateProjectConfig handles update-project configuration @@ -28,10 +38,68 @@ func (c *Compiler) parseUpdateProjectConfig(outputMap map[string]any) *UpdatePro updateProjectLog.Print("Using custom GitHub token for update-project") } } + + // Parse views if specified + if viewsData, exists := configMap["views"]; exists { + if viewsList, ok := viewsData.([]any); ok { + for i, viewItem := range viewsList { + if viewMap, ok := viewItem.(map[string]any); ok { + view := ProjectView{} + + // Parse name (required) + if name, exists := viewMap["name"]; exists { + if nameStr, ok := name.(string); ok { + view.Name = nameStr + } + } + + // Parse layout (required) + if layout, exists := viewMap["layout"]; exists { + if layoutStr, ok := layout.(string); ok { + view.Layout = layoutStr + } + } + + // Parse filter (optional) + if filter, exists := viewMap["filter"]; exists { + if filterStr, ok := filter.(string); ok { + view.Filter = filterStr + } + } + + // Parse visible-fields (optional) + if visibleFields, exists := viewMap["visible-fields"]; exists { + if fieldsList, ok := visibleFields.([]any); ok { + for _, field := range fieldsList { + if fieldInt, ok := field.(int); ok { + view.VisibleFields = append(view.VisibleFields, fieldInt) + } + } + } + } + + // Parse description (optional) + if description, exists := viewMap["description"]; exists { + if descStr, ok := description.(string); ok { + view.Description = descStr + } + } + + // Only add view if it has required fields + if view.Name != "" && view.Layout != "" { + updateProjectConfig.Views = append(updateProjectConfig.Views, view) + updateProjectLog.Printf("Parsed view %d: %s (%s)", i+1, view.Name, view.Layout) + } else { + updateProjectLog.Printf("Skipping invalid view %d: missing required fields", i+1) + } + } + } + } + } } - updateProjectLog.Printf("Parsed update-project config: max=%d, hasCustomToken=%v", - updateProjectConfig.Max, updateProjectConfig.GitHubToken != "") + updateProjectLog.Printf("Parsed update-project config: max=%d, hasCustomToken=%v, viewCount=%d", + updateProjectConfig.Max, updateProjectConfig.GitHubToken != "", len(updateProjectConfig.Views)) return updateProjectConfig } updateProjectLog.Print("No update-project configuration found") diff --git a/pkg/workflow/update_project_job.go b/pkg/workflow/update_project_job.go index 1c57cdd4db..5dbf9e7f02 100644 --- a/pkg/workflow/update_project_job.go +++ b/pkg/workflow/update_project_job.go @@ -1,6 +1,7 @@ package workflow import ( + "encoding/json" "fmt" ) @@ -37,6 +38,15 @@ func (c *Compiler) buildUpdateProjectJob(data *WorkflowData, mainJobName string) // The JavaScript code checks process.env.GH_AW_PROJECT_GITHUB_TOKEN to provide helpful error messages customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_PROJECT_GITHUB_TOKEN: %s\n", effectiveToken)) + // If views are configured in frontmatter, pass them to the JavaScript via environment variable + if data.SafeOutputs.UpdateProjects != nil && len(data.SafeOutputs.UpdateProjects.Views) > 0 { + viewsJSON, err := json.Marshal(data.SafeOutputs.UpdateProjects.Views) + if err != nil { + return nil, fmt.Errorf("failed to marshal views configuration: %w", err) + } + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_PROJECT_VIEWS: '%s'\n", string(viewsJSON))) + } + jobCondition := BuildSafeOutputType("update_project") permissions := NewPermissionsContentsReadProjectsWrite() From 65343079cc661eed24fcf654b4c184f8464b6c1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 05:18:23 +0000 Subject: [PATCH 5/5] Document views property in update-project safe output configuration Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .../guides/campaigns/project-management.md | 21 ++++++++ .../docs/reference/frontmatter-full.md | 14 +++++ .../content/docs/reference/safe-outputs.md | 51 +++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/docs/src/content/docs/guides/campaigns/project-management.md b/docs/src/content/docs/guides/campaigns/project-management.md index 90257ec5b2..e9ff0410d5 100644 --- a/docs/src/content/docs/guides/campaigns/project-management.md +++ b/docs/src/content/docs/guides/campaigns/project-management.md @@ -168,6 +168,27 @@ The campaign generator creates three views automatically: 2. **Task Tracker** (Table view) - Detailed tracking with filtering 3. **Progress Board** (Board view) - Kanban-style progress tracking +### Declarative view configuration + +Views can be declared directly in workflow frontmatter using the `views` property: + +```yaml +safe-outputs: + update-project: + github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} + views: + - name: "Sprint Board" + layout: board + filter: "is:issue is:open" + - name: "Task Tracker" + layout: table + filter: "is:issue,is:pull_request" + - name: "Timeline" + layout: roadmap +``` + +Views are automatically created when the workflow runs. This declarative approach is simpler than programmatic view creation and ensures views are configured consistently across workflow runs. + **Customization tips:** **Multi-Workflow Campaign**: Use Roadmap grouped by Worker/Workflow for timeline distribution, Task Tracker sliced by Priority+Status for urgent items, Progress Board grouped by Status for progress tracking. diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index db4377baec..74b427183a 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -1671,6 +1671,20 @@ safe-outputs: # (optional) github-token: "${{ secrets.GITHUB_TOKEN }}" + # Optional array of project views to create automatically. Each view must have a + # name and layout. Views are created during workflow execution after processing + # agent output items. + # (optional) + views: + - name: "Sprint Board" + layout: board # table, board, or roadmap + filter: "is:issue is:open" # optional filter query + - name: "Task Tracker" + layout: table + filter: "is:issue,is:pull_request" + - name: "Campaign Timeline" + layout: roadmap + # Option 2: Enable project management with default configuration (max=10) update-project: null diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 62855748c0..efc089c9e9 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -321,6 +321,14 @@ safe-outputs: update-project: max: 20 # max operations (default: 10) github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} + views: # optional: auto-create views + - name: "Sprint Board" + layout: board + filter: "is:issue is:open" + - name: "Task Tracker" + layout: table + - name: "Campaign Roadmap" + layout: roadmap ``` Agent must provide full project URL (e.g., `https://github.com/orgs/myorg/projects/42`). Optional `campaign_id` applies `campaign:` labels for [Campaign Workflows](/gh-aw/guides/campaigns/). Exposes outputs: `project-id`, `project-number`, `project-url`, `campaign-id`, `item-id`. @@ -348,6 +356,49 @@ fields: > [!NOTE] > Field names are case-insensitive and automatically normalized (e.g., `story_points` matches `Story Points`). +#### Creating Project Views + +Project views can be created automatically by declaring them in the `views` array. Views are created when the workflow runs, after processing update_project items from the agent. + +**View configuration:** +```yaml +safe-outputs: + update-project: + github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} + views: + - name: "Sprint Board" # required: view name + layout: board # required: table, board, or roadmap + filter: "is:issue is:open" # optional: filter query + - name: "Task Tracker" + layout: table + filter: "is:issue,is:pull_request" + - name: "Campaign Timeline" + layout: roadmap +``` + +**View properties:** + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `name` | string | Yes | View name (e.g., "Sprint Board", "Task Tracker") | +| `layout` | string | Yes | View layout: `table`, `board`, or `roadmap` | +| `filter` | string | No | Filter query (e.g., `is:issue is:open`, `label:bug`) | +| `visible-fields` | array | No | Field IDs to display (table/board only, not roadmap) | + +**Layout types:** +- **`table`** — List view with customizable columns for detailed tracking +- **`board`** — Kanban-style cards grouped by status or custom field +- **`roadmap`** — Timeline visualization with date-based swimlanes + +**Filter syntax examples:** +- `is:issue is:open` — Open issues only +- `is:pull_request` — Pull requests only +- `is:issue,is:pull_request` — Both issues and PRs +- `label:bug` — Items with bug label +- `assignee:@me` — Items assigned to viewer + +Views are created automatically during workflow execution. The workflow must include at least one `update_project` operation to provide the target project URL. See [Project Management Guide](/gh-aw/guides/campaigns/project-management/) for view customization strategies. + ### Project Board Copy (`copy-project:`)