From ac0db11170e6e4b6f61424180788f62a2a386bd9 Mon Sep 17 00:00:00 2001 From: Jay Gordon Date: Tue, 29 Sep 2026 11:18:09 -0400 Subject: [PATCH] fix: publish gallery proposals as editable issues Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/publish-gallery-proposal.yml | 10 ++++++---- Contributing.md | 2 +- README.md | 2 +- scripts/gallery-audit/promotion.mjs | 9 ++++++--- scripts/gallery-audit/test/audit.test.mjs | 3 +++ scripts/gallery-audit/test/configuration.test.mjs | 4 +++- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish-gallery-proposal.yml b/.github/workflows/publish-gallery-proposal.yml index 79abf85..fd177a7 100644 --- a/.github/workflows/publish-gallery-proposal.yml +++ b/.github/workflows/publish-gallery-proposal.yml @@ -51,15 +51,17 @@ jobs: cat > "$issue_body" <> "$GITHUB_STEP_SUMMARY" \ No newline at end of file diff --git a/Contributing.md b/Contributing.md index eb95645..c437208 100644 --- a/Contributing.md +++ b/Contributing.md @@ -25,7 +25,7 @@ Catalog changes may be submitted manually or proposed by the automated maintenan - avoid duplicate resources after URL normalization; and - include strong evidence for removals or retirements. -Generated maintenance pull requests are always drafts. Their bodies label additions as `A`, URL updates as `U`, retirements as `R`, and skipped items as `S`. An authorized maintainer can comment `Reject: A1, U1, R1` to remove those changes from the current proposal. A maintainer must review the complete catalog diff and merge it through the normal protected-branch process before publication. +Automated maintenance creates an unassigned proposal issue, not a pull request. The issue labels additions as `A`, URL updates as `U`, retirements as `R`, and skipped items as `S`. Each addition includes the complete proposed card fields and destination, and each retirement identifies the card and gives the removal reason and evidence. An authorized maintainer edits or deletes proposals in the issue, then assigns the issue to an implementation agent. The agent creates a draft pull request, which a maintainer must review and merge through the normal protected-branch process before publication. See [Automated gallery maintenance](./docs/automated-gallery-maintenance.md) and [Content source policy](./docs/content-source-policy.md) for the operating and source-approval requirements. diff --git a/README.md b/README.md index 7fbdbb2..fb470a5 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,6 @@ The repository deploys through `.github/workflows/deploy.yml` after reviewed cha ## Automated Maintenance -The weekly maintenance workflow audits existing entries, discovers catalog-aligned content from approved sources, and opens one draft pull request for human review. Its Copilot curator uses the Azure Cosmos DB Agent Kit and a repository humanizer skill. A new proposal requests review from `jagord_microsoft`; GitHub notification routing sends that request to `jagord@microsoft.com`. The workflow never merges or publishes catalog changes automatically. +The weekly maintenance workflow audits existing entries, discovers catalog-aligned content from approved sources, and opens an unassigned proposal issue. An operator edits the proposed additions, URL updates, and retirements, then assigns the issue to an implementation agent. Only that assigned agent creates a draft pull request for human review. The curator uses the Azure Cosmos DB Agent Kit and a repository humanizer skill. The workflow never creates a pull request, merges changes, or publishes catalog changes automatically. See [Automated gallery maintenance](./docs/automated-gallery-maintenance.md) for setup and operations, and [Content source policy](./docs/content-source-policy.md) for source eligibility. diff --git a/scripts/gallery-audit/promotion.mjs b/scripts/gallery-audit/promotion.mjs index f4536d2..28c8597 100644 --- a/scripts/gallery-audit/promotion.mjs +++ b/scripts/gallery-audit/promotion.mjs @@ -60,19 +60,21 @@ export function proposalItem(id, action, entry, previousUrl = null, suffix = '') export function proposalCardDetails(entry) { return [ + ' - Catalog change: Add this card to `static/templates.json`.', ` - Description: ${markdownText(entry.description, '**MISSING**')}`, + ` - Preview: ${markdownText(entry.preview, '**MISSING**')}`, + ` - Website: ${markdownText(entry.website, '**MISSING**')}`, ` - Author: ${markdownText(Array.isArray(entry.author) ? entry.author.join(', ') : entry.author, '**MISSING**')}`, + ` - Source: ${markdownText(entry.source, '**MISSING**')}`, ` - Date: ${markdownText(entry.date, '**MISSING**')}`, ` - Tags: ${markdownText(entry.tags?.length ? entry.tags.join(', ') : null, '**MISSING**')}`, - ` - Website: ${markdownText(entry.website, '**MISSING**')}`, - ` - Preview: ${markdownText(entry.preview, '**MISSING**')}`, - ` - Source: ${markdownText(entry.source, '**MISSING**')}`, ]; } export function retirementProof(entry) { const evidence = entry.retirementEvidence ?? {}; return [ + ' - Catalog change: Remove this card from `static/templates.json` and append its retirement record to `static/retired-templates.json`.', ` - Reason: ${markdownText(entry.retirementReason)}`, ` - Audit outcome: ${markdownText(evidence.auditOutcome)}`, ` - HTTP status: ${markdownText(evidence.httpStatus)}`, @@ -191,6 +193,7 @@ export function promotionMarkdown(result, generatedAt) { '', `URL updates: ${updates.length}`, '', ...updates.flatMap((entry, index) => [ ...proposalItem(`U${index + 1}`, 'Update', { title: entry.title, source: entry.url }, entry.previousUrl), + ' - Catalog change: Replace this card\'s source URL in `static/templates.json`.', ` - Reason: ${markdownText(entry.recommendationReason)}`, ` - Criteria: ${markdownText(entry.recommendationCriteria?.join(', '))}`, ]), diff --git a/scripts/gallery-audit/test/audit.test.mjs b/scripts/gallery-audit/test/audit.test.mjs index f97cf21..935b6f3 100644 --- a/scripts/gallery-audit/test/audit.test.mjs +++ b/scripts/gallery-audit/test/audit.test.mjs @@ -812,11 +812,14 @@ test('numbers every proposal issue item for unambiguous issue editing', () => { skippedAdditions: [{ url: 'https://example.com/skip', reason: 'already-cataloged' }], }, '2026-09-18T00:00:00.000Z'); assert.match(markdown, /\*\*A1\*\* Add \[First addition\]/); + assert.match(markdown, /Catalog change: Add this card to `static\/templates\.json`/); assert.match(markdown, /Reason: Relevant example\./); assert.match(markdown, /Criteria: specific/); assert.match(markdown, /\*\*U1\*\* Update \[Moved\]/); + assert.match(markdown, /Catalog change: Replace this card's source URL in `static\/templates\.json`/); assert.match(markdown, /Reason: Canonical redirect\./); assert.match(markdown, /\*\*R1\*\* Retire \[First retirement\]/); + assert.match(markdown, /Catalog change: Remove this card from `static\/templates\.json` and append its retirement record to `static\/retired-templates\.json`/); assert.match(markdown, /Reason: Superseded\./); assert.match(markdown, /\*\*S1\*\* https:\/\/example\.com\/skip/); assert.match(markdown, /Edit this issue before assigning it to Copilot/); diff --git a/scripts/gallery-audit/test/configuration.test.mjs b/scripts/gallery-audit/test/configuration.test.mjs index cdf3cca..3be0eb0 100644 --- a/scripts/gallery-audit/test/configuration.test.mjs +++ b/scripts/gallery-audit/test/configuration.test.mjs @@ -63,7 +63,9 @@ test('keeps audit read-only and publishes only an issue through trusted workflow assert.match(publisher, /workflow_run\.event != 'pull_request'/); assert.match(publisher, /workflow_run\.head_branch == github\.event\.repository\.default_branch/); assert.match(publisher, /permissions:\s*\n\s*actions: read\s*\n\s*issues: write/); - assert.match(publisher, /gh issue create --title "Gallery content proposal \$SOURCE_RUN_ID"/); + assert.match(publisher, /gh issue create --repo "\$GITHUB_REPOSITORY" --title "Gallery content proposal \$SOURCE_RUN_ID"/); + assert.match(publisher, /This workflow does not create a branch or pull request/); + assert.match(publisher, /Edit the proposals above before assigning this issue to an implementation agent/); assert.match(publisher, /Treat the edited issue body as the complete source of truth/); assert.doesNotMatch(publisher, /contents: write|actions\/checkout|git push|gh pr create|gh pr edit|automation\/gallery-content-updates|GALLERY_UPDATE_TOKEN/); });