Skip to content

fix: keep the low-tier promises — bulk-update label, event-name validation, workflow personalisation - #1434

Merged
glennmichael123 merged 2 commits into
mainfrom
fix/low-tier-wiring
Aug 31, 2026
Merged

fix: keep the low-tier promises — bulk-update label, event-name validation, workflow personalisation#1434
glennmichael123 merged 2 commits into
mainfrom
fix/low-tier-wiring

Conversation

@glennmichael123

Copy link
Copy Markdown
Member

Four declared-but-never-wired findings from the sweep's low tier, all the same defect class this run has been retiring: things typed, validated and documented that no code ever read.

bulk-update label

docs/features/pull-requests.md promises bulk-update: For PRs with 5+ package updates under Dynamic Labeling. Nothing applied it — a filter watching for the label saw every grouped PR slip past. generateLabels now adds it at five or more updates.

Notification event names are validated

EVENT_NAMES was exported with the comment "for config validation and subscription lists" — and no validator imported it. A mistyped events: ['pr.opened'] built the sink, read the credential, and filtered every real event out: notifications configured, nothing ever delivered. validateConfig gains validateNotifications, checking slack/discord/webhooks[] — event names against EVENT_NAMES, env-var names non-empty, webhook url present and http(s).

workflows.enabled

Declared and documented; generate-workflows generated the full set regardless. It now declines on explicit false only — the same reading the dashboard uses, so a repository that never wrote a workflows block keeps the command.

workflows.custom[].assignees — and the personalisation that was already theater

The field was declared and validated, and generateCustomWorkflow didn't even accept it. Worse: reviewers and labels were accepted — and then dropped by generateWorkflow, which never rendered them into the YAML. The generated workflow runs ./buddy update against whatever buddy.config.ts the checkout carries, so a per-workflow override can only travel through the command line:

  • buddy update gains --reviewers / --assignees / --labels (comma-separated; fold into pullRequest only when given, so an unset pullRequest block stays unset).
  • The generated workflow passes the per-workflow overrides as those flags, on both the dry-run and live invocations, single-quoted with quotes stripped so a hostile name cannot escape the argument.
  • Global pullRequest values are deliberately not baked into the YAML any more (scheduled workflows used to pass them into the void): the runtime reads them from config on every run, and freezing today's values into a workflow file would shadow every later config edit.

Tests

bun test: 2165 pass / 0 fail. New: test/custom-workflows.test.ts (flags rendered on both invocations, no flags when no overrides, shell-quote hardening, globals not frozen); extensions to test/pr-labels.test.ts (5 ⇒ labelled, 4 ⇒ not), test/config-validation.test.ts (valid block passes; mistyped event, missing url, bad webhook events all reported), test/cli-surface.test.ts (decline honoured, no .github written).

Remaining low items (review --light --fix, reviewSuggestions, skipIfReviewed) wait on #1431, which owns the review files.

🤖 Generated with Claude Code

glennmichael123 and others added 2 commits August 31, 2026 19:38
…ation, workflow personalisation

Four declared-but-never-wired findings from the sweep's low tier:

- generateLabels now applies the documented bulk-update label to PRs
  carrying five or more updates; the docs promised it under Dynamic
  Labeling before any code read it.
- validateConfig gains validateNotifications: EVENT_NAMES was exported
  'for config validation' and no validator imported it, so a mistyped
  notification event silently subscribed a sink to nothing.
- generate-workflows honours workflows.enabled: false (explicit false
  only, the dashboard's reading).
- workflows.custom[].assignees was validated and dropped — the template
  did not accept the field, and reviewers/labels were accepted and then
  discarded. The generated workflow now passes per-workflow overrides to
  ./buddy update as --reviewers/--assignees/--labels flags (new on the
  update command), and stops baking global pullRequest values into YAML,
  where they would shadow every later config edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… existsSync

Under CI's file order, test/dependency-file-parser.test.ts's node:fs module
mock leaks into cli-surface, and existsSync returned undefined. Bun's native
glob cannot be reached by a node:fs mock, and asserting the directory holds
exactly buddy.config.ts is the stronger claim anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@glennmichael123
glennmichael123 merged commit f0bb121 into main Aug 31, 2026
6 checks passed
glennmichael123 added a commit that referenced this pull request Aug 31, 2026
…sted modules (#1436)

* fix(review): read what publishing reported, honour reviewSuggestions, and un-strand two flags

Four review-side findings, the last of the sweep's low tier:

- reviewPullRequest now reads the ReviewSubmissionResult every provider
  already returned: a review that failed to post says so instead of
  claiming success (and records no state, so the next run does not skip
  a review nobody can see), and inline comments the platform dropped
  are counted in the status and warned about.
- prepareReview/renderFinding take the provider's reviewSuggestions
  capability: Bitbucket declares it false and was receiving literal
  suggestion fences promising a one-click apply it does not have; it
  now gets a plain code fence labelled as a suggested replacement.
- buddy review --auto passes skipIfReviewed, so the workflow that fires
  on every edited event stops re-reviewing the same head commit; a
  person at a terminal still always gets their review.
- buddy review --light --fix applies suggestions: the light branch
  returned before the fix block, so the flag worked everywhere except
  the one mode fast enough for a pre-commit hook.

The review-parity stub answered createReview with { id: 1 } — encoding
the defect that the result was ignored; it now returns what the
interface declares.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): defuse the suite's three landmines

- The node:fs module mock in dependency-file-parser leaked into every
  file that ran after it: mock.module is process-global and permanent,
  so once afterEach reset the mocks, existsSync returned undefined
  suite-wide — which is exactly how #1434's decline test failed on CI
  while passing alone. The mock now delegates to the real fs except
  while this suite has armed it.
- preload.ts gains a repair-only cwd net: a suite that chdirs into a
  temp directory and deletes it leaves process.cwd() dead, and every
  later filesystem-touching test fails with an unrelated ENOENT. A live
  cwd is never moved.
- test/composer-constraint-updates.test.ts was a 0-byte file that ran
  and asserted nothing — coverage in every listing, a test of nothing.
  It now holds the suite its name promises: caret, tilde, exact and
  compound constraints keep their shape, require-dev updates, and an
  unknown package leaves the file untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: cover the dossier's two untested modules, and say bun install is not optional

- src/ai/providers/anthropic.ts was the one AI provider without a
  direct test (google and openai have them) — and it is the default
  when several keys are present. The SDK is faked at the module seam so
  complete() runs its real mapping: block collection, the stop-reason
  table (refusal preserved — it arrives as a successful response with
  empty content), usage with cache reads, schema-constrained JSON
  parsing, request passthrough, and the lazily built, reused client.
- src/utils/lock-file.ts: 188 lines, publicly re-exported, no direct
  test. The pure parts are asserted directly; regenerateLockFile is
  exercised through its non-fatal contract — a machine without the
  package manager gets a result object naming the failure, not an
  unhandled rejection (which channel reports it is platform-dependent:
  node fires 'error', Bun closes with a negative code).
- README: bun install is a hard prerequisite on a fresh clone (bun test
  otherwise fails with phantom module errors), and the declared git
  hooks do not install themselves — bunx bun-git-hooks activates them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ai): assert the anthropic mapping at the fetch layer, not through a second module mock

The new anthropic-provider file owned its own mock.module of the SDK —
process-global, so under CI's file order it clobbered ai-provider's
fetch-stubbed anthropic tests, which started receiving the fake's
canned response. The same landmine class this branch exists to defuse.

The genuinely new cases move into ai-provider.test.ts through the real
SDK: the remaining stop reasons (stop_sequence, max_tokens, unknown),
schema-constrained JSON parsing and its malformed-JSON failure, and the
wire shape (system, tools mapped to input_schema, output_config,
max_tokens default) captured from the request body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@glennmichael123
glennmichael123 deleted the fix/low-tier-wiring branch August 31, 2026 12:31
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.

1 participant