Skip to content

Update gh aw update --org to support workflow-targeted updates and repo prefiltering#41617

Merged
pelikhan merged 5 commits into
mainfrom
copilot/update-command-org-mode-workflows
Jun 26, 2026
Merged

Update gh aw update --org to support workflow-targeted updates and repo prefiltering#41617
pelikhan merged 5 commits into
mainfrom
copilot/update-command-org-mode-workflows

Conversation

Copilot AI commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

update --org previously treated org mode as all-or-nothing across source-managed workflows. This change makes positional workflow arguments effective in org mode: only requested workflows are considered for update, and repo discovery is narrowed to repositories that contain those workflow files.

  • Scope behavior in --org mode

    • Threads UpdateWorkflowsOptions.WorkflowNames through org-mode discovery and per-repo preview.
    • Ensures non-requested workflows are skipped when scanning a target repository.
  • Repo discovery optimization

    • Extends org search to build workflow-aware GitHub code search queries.
    • Adds filename predicates for requested workflows, reducing candidate repositories before expensive per-repo work.
  • Search/query plumbing

    • Updates searchOrgWorkflowRepos to accept workflow filters.
    • Adds buildOrgWorkflowSearchQuery to normalize IDs, dedupe, and emit deterministic OR-based filename filters.
  • Coverage updates

    • Adds focused tests for query generation and filter propagation in org-mode update flow.
query := buildOrgWorkflowSearchQuery("octo", []string{"triage.md", "repo-assist"})
// org:octo path:.github/workflows extension:md "source:" (filename:repo-assist.md OR filename:triage.md)

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title feat(update): support workflow filters in --org mode Update gh aw update --org to support workflow-targeted updates and repo prefiltering Jun 26, 2026
Copilot AI requested a review from pelikhan June 26, 2026 06:31
@pelikhan pelikhan marked this pull request as ready for review June 26, 2026 06:35
Copilot AI review requested due to automatic review settings June 26, 2026 06:35
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel failed during test quality analysis.

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request updates gh aw update --org so that positional workflow arguments are honored in org mode, narrowing both repository discovery and per-repo scanning to only the requested workflow(s). This reduces unnecessary work by prefiltering candidate repositories via workflow-aware GitHub code search queries.

Changes:

  • Thread UpdateWorkflowsOptions.WorkflowNames through org-mode repo discovery and per-repo workflow scanning.
  • Optimize org repo discovery by generating deterministic code-search queries with filename: filters for requested workflows.
  • Add unit tests covering query generation and workflow-filter propagation through the org update flow.
Show a summary per file
File Description
pkg/cli/update_org.go Adapts org-mode discovery and per-repo scanning to respect WorkflowNames.
pkg/cli/update_org_test.go Updates stubs for the new search signature and adds a test verifying workflow filters are passed through.
pkg/cli/update_org_search.go Extends org repo discovery to accept workflow filters and builds a deterministic OR-based filename: query.
pkg/cli/update_org_search_test.go Adds focused tests for buildOrgWorkflowSearchQuery output.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread pkg/cli/update_org.go
Comment on lines 95 to 99
return runCommandForOrg(ctx, org, repoGlobs, orgRunCallbacks{
SearchFn: searchOrgWorkflowReposFn,
SearchFn: searchFn,
ScanFn: scanFn,
ReportFn: renderOrgPreviewReport,
ApplyFn: func(ctx context.Context, preview orgRepoPreview, v bool) error {
Generated by design-decision-gate agent from PR #41617 diff.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (≈106 new lines in pkg/cli/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/41617-org-mode-workflow-targeted-updates.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-41617: Workflow-Name Filtering in --org Mode

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 48.3 AIC · ⌖ 11.3 AIC · ⊞ 8.4K ·

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 75/100 — Acceptable

Analyzed 2 new test function(s): 2 design, 0 implementation, 0 hard guideline violations — but both new tests are missing required assertion messages.

📊 Metrics & Test Classification (2 new tests analyzed)
Metric Value
New/modified tests analyzed 2 new + 8 signature adaptations
✅ Design tests (behavioral contracts) 2 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (50%)
Duplicate test clusters 0
Test inflation detected YES — update_org_test.go +42 vs update_org.go +5 (8.4:1)
🚨 Coding-guideline violations 0 hard (mock libs / build tags) — ⚠️ missing assertion messages (soft)
Test File Classification Issues Detected
TestBuildOrgWorkflowSearchQuery update_org_search_test.go:11 ✅ Design ⚠️ 3 bare assert.Equal without message
TestRunUpdateForOrgPassesWorkflowNameFilters update_org_test.go:95 ✅ Design ⚠️ 2 bare assert.Equal without message; happy-path only
TestRunUpdateForOrgDryRun (modified) update_org_test.go:47 — (signature update)
7 other modified tests update_org_test.go — (signature updates)

Go: 2 new (*_test.go); JavaScript: 0. Other languages: none detected.

⚠️ Flagged Tests — Requires Review (2 issues)

TestBuildOrgWorkflowSearchQuery (update_org_search_test.go:11) — ⚠️ Missing assertion messages on all 3 assert.Equal calls. Per project guidelines, every assertion must include a descriptive message to aid failure diagnosis.

// Current (bare — hard to diagnose on failure):
assert.Equal(t, `org:octo path:...`, buildOrgWorkflowSearchQuery("octo", nil))

// Suggested:
assert.Equal(t, `org:octo path:...`, buildOrgWorkflowSearchQuery("octo", nil),
    "nil workflow names: should produce base query without filename filter")

TestRunUpdateForOrgPassesWorkflowNameFilters (update_org_test.go:95) — ⚠️ Two bare assert.Equal calls without message arguments; happy-path only. Consider adding a case where the search function returns an error to verify filters do not silently break the error path.

Test Inflation Note

update_org_test.go grew 42 lines vs update_org.go 5 lines (8.4:1 ratio, above the 2:1 threshold). In context this is expected: the PR changes a function signature and 8 existing test stubs each needed a 1-line update (workflowNames []string added). The actual new behavioral test (TestRunUpdateForOrgPassesWorkflowNameFilters) is ~33 lines — proportional to the feature.

Verdict

Check passed. 0% implementation tests (threshold: 30%). Both new tests verify observable behavioral contracts — query string construction and end-to-end filter propagation. Assertion messages are missing and should be added, but this does not trigger a hard failure.

🧪 Test quality analysis by Test Quality Sentinel · 60.1 AIC · ⌖ 15.2 AIC · ⊞ 8.4K ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 75/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%). Both new tests verify behavioral contracts. Please address the missing assertion messages flagged in the analysis comment.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd, /grill-with-docs, and /zoom-out — overall a clean, well-structured feature. Six observations posted as inline comments; none are blocking.

📋 Key Themes & Highlights

Key Themes

  • Documentation gaps: buildOrgWorkflowSearchQuery has no doc comment, and searchOrgWorkflowRepos's docstring doesn't mention the narrowing behaviour of workflowNames. The searchFn closure adapter also lacks an explanatory comment.
  • Test coverage: Edge cases for buildOrgWorkflowSearchQuery (single name, all-empty names) are untested. TestRunUpdateForOrgPassesWorkflowNameFilters verifies propagation but not the filter effect at the previewOrgRepoUpdates level.
  • Semantic shift: TotalWorkflows now reflects the post-filter count when WorkflowNames is specified, quietly changing the meaning of the progress log line ("%d workflow(s)"). Worth a rename or doc note.

Positive Highlights

  • ✅ Clean closure adapter pattern to keep orgRunCallbacks.SearchFn backward-compatible with the upgrade command
  • ✅ Deterministic, sorted OR-query construction with deduplication is a nice touch
  • ✅ Both new tests (TestBuildOrgWorkflowSearchQuery and TestRunUpdateForOrgPassesWorkflowNameFilters) are well-scoped and easy to follow
  • ✅ Fully backward-compatible: empty WorkflowNames produces the original search query unchanged

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 92.6 AIC · ⌖ 9.99 AIC · ⊞ 6.5K

Comments that could not be inline-anchored

pkg/cli/update_org_search_test.go:11

[/tdd] Three edge cases are missing from this test: a single-workflow input (no OR combinator, but parentheses still wrap it), all-invalid names where every entry normalizes to empty (exercises the len(filenameFilters)==0 fallback on line 62), and a workflow name that is a full path (e.g. .github/workflows/triage.md).

<details>
<summary>💡 Suggested additions</summary>

// Single name — one filename filter, parentheses still present
assert.Equal(t,
    `org:octo path:.github/work…

</details>

<details><summary>pkg/cli/update_org_search.go:42</summary>

**[/grill-with-docs]** `buildOrgWorkflowSearchQuery` is undocumented. Given its non-trivial contract — normalization, deduplication, deterministic sort, and base-query fallback — a doc comment would help future maintainers understand when the fallback fires and why the OR filters are sorted.

&lt;details&gt;
&lt;summary&gt;💡 Suggested doc comment&lt;/summary&gt;

```go
// buildOrgWorkflowSearchQuery constructs a GitHub code-search query that finds
// .md files under .github/workflows/ with a &quot;source:&quot; field in …

</details>

<details><summary>pkg/cli/update_org_search.go:37</summary>

**[/grill-with-docs]** The existing doc comment for `searchOrgWorkflowRepos` doesn&#39;t mention the new `workflowNames` parameter or its narrowing effect. A reader looking at the function signature won&#39;t know whether an empty slice means &quot;match all&quot; or &quot;match none&quot;.

&lt;details&gt;
&lt;summary&gt;💡 Suggested addition to the doc comment&lt;/summary&gt;

Add a sentence after the existing description:

// When workflowNames is non-empty, the search is narrowed to repos that contain
// at least one of the named w…

pkg/cli/update_org_test.go:95

[/tdd] This test verifies that WorkflowNames is propagated to both the search and preview functions, which is valuable. However, the core claim in the PR description — "non-requested workflows are skipped when scanning a target repository" — isn't exercised here because previewOrgRepoUpdatesFn is mocked out entirely.

A complementary integration-style unit test that calls the real previewOrgRepoUpdates (with a fake checkout) and checks that a workflow absent from WorkflowNames is …

pkg/cli/update_org.go:73

[/zoom-out] The closure adapts searchOrgWorkflowReposFn's updated three-parameter signature to orgRunCallbacks.SearchFn's two-parameter interface. Without a comment this looks like unnecessary indirection — future readers may wonder why the function isn't assigned directly.

<details>
<summary>💡 Suggested inline comment</summary>

// Wrap searchOrgWorkflowReposFn to adapt its (org, workflowNames, verbose)
// signature to the (org, verbose) SearchFn interface expected by
// runCom…

</details>

<details><summary>pkg/cli/update_org.go:158</summary>

**[/grill-with-docs]** `TotalWorkflows` is now assigned `len(workflows)` after `findWorkflowsWithSource` has already applied the `WorkflowNames` filter. When the user specifies target workflows, `TotalWorkflows` will reflect the *filtered* count, not the total number of source-managed workflows in the repo.

This silently changes the meaning of the field and the log message at line 84 (`&quot;%s: %d workflow(s), %d with updates&quot;`), which now reads as the number of *requested* workflows rather than t</details>

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking observations

The change is well-scoped. Workflow name filters are correctly threaded through both the search query (as filename: predicates) and the per-repo scan (findWorkflowsWithSource), so filtering is enforced at two independent layers. The new tests cover query generation, dedup, and filter propagation.

Issues found (2, both non-blocking)
  1. Silent fallback in buildOrgWorkflowSearchQuery (update_org_search.go:64): when all workflow names normalize to empty, the function returns the base (unfiltered) query with no warning. Correctness is still preserved by the per-repo scan, but it silently wastes API search quota. Inline comment added.

  2. Missing boundary tests in TestBuildOrgWorkflowSearchQuery (update_org_search_test.go:29): []string{} and []string{""} (all-empty-normalization path) are not covered. Inline comment added.

🔎 Code quality review by PR Code Quality Reviewer · 106.2 AIC · ⌖ 7.3 AIC · ⊞ 5.2K

}
if len(filenameFilters) == 0 {
return base
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent fallback to full-org scan when all workflow names normalize to empty: when workflowNames is non-empty but every entry reduces to "" via normalizeWorkflowID, the function silently returns the base query — no filename predicates, no warning — causing searchOrgReposByQuery to scan the entire org.

💡 Details and mitigation

Correctness is preserved by the backstop in previewOrgRepoUpdates: even if the search over-fetches repos, findWorkflowsWithSource re-filters them using opts.WorkflowNames. So no wrong updates are applied. The risk is pure API quota waste (an org-wide search instead of a targeted one) with no signal to the caller.

This is hard to reach in practice since CLI validation blocks empty names upstream, but it is worth hardening:

if len(filenameFilters) == 0 {
    // All provided names normalized to empty; caller should not hit this path.
    // Fall back to broad search and let per-repo scan enforce the filter.
    return base
}

A fmt.Fprintln(os.Stderr, console.FormatWarningMessage("...")) or debug log here would make unexpected fallback observable without blocking the operation.

`org:octo path:.github/workflows extension:md "source:" (filename:repo-assist.md)`,
buildOrgWorkflowSearchQuery("octo", []string{"repo-assist", ".github/workflows/repo-assist.md"}),
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing boundary test for []string{} (empty slice) and all-empty-normalization path: the test covers nil, two names, and dedup, but not []string{} or a slice where every entry normalizes to "" (e.g. []string{""}).

💡 Suggested additions
// empty slice → base query (same as nil)
assert.Equal(t,
    `org:octo path:.github/workflows extension:md "source:"`,
    buildOrgWorkflowSearchQuery("octo", []string{}),
)

// all entries normalize to empty → base query (silent fallback path)
assert.Equal(t,
    `org:octo path:.github/workflows extension:md "source:"`,
    buildOrgWorkflowSearchQuery("octo", []string{""}),
)

These cases exercise the if len(filenameFilters) == 0 { return base } guard at line 62, which is the silent-fallback path discussed in the adjacent comment.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 2 commits June 26, 2026 06:56
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Addressed the actionable review feedback in 4a6cf41 and re-ran make fmt, make lint, make test-unit, and make test locally. CI on this head is still unverified until a maintainer re-triggers it.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Completed a pr-finisher pass in 10400da: added the fallback clarification in pkg/cli/update_org_search.go, re-ran make fmt, make lint, make test-unit, make recompile, and make test (the first full-suite run hit an unrelated flaky TestGitPatchFromHEADCommits, and the re-run passed). CI on this head is still unverified until a maintainer re-triggers it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants