Skip to content

feat(migrator): Migration.cfc command consistency sweep (follow-up to #2781) - #2804

Merged
bpamiri merged 5 commits into
developfrom
claude/migrator-consistency-pr2
May 24, 2026
Merged

bpamiri merged 5 commits into
developfrom
claude/migrator-consistency-pr2

Conversation

@bpamiri

@bpamiri bpamiri commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

PR1 (#2802) fixed `t.references()` at the `TableDefinition` layer. This PR brings the matching command-version helpers in `Migration.cfc` into the same convention.

Argument-name consistency — every column-accepting helper accepts the plural/singular alias shape via `$combineArguments`:

Function Legacy Modern alias(es)
`addColumn` / `changeColumn` `columnName` `columnNames`
`removeColumn` `columnName` `columnNames`
`addReference` / `dropReference` `referenceName` `columnName` / `columnNames`
`addForeignKey` `column` `columnName`

Suffix-flag correctness — two hard-coded `& "id"` concats now route through `useUnderscoreReferenceColumns`, matching `references()` behavior from PR1:

  • `removeColumn` line 258 — when `referenceName=` is used, the dropped column name follows the flag
  • `addReference` line 278 — when the FK column is built, follows the flag

Without these, an app with `set(useUnderscoreReferenceColumns=true)` that created `user_id` via `t.references()` would silently fail to drop or constrain it via the command-version helpers.

Tests

`vendor/wheels/tests/specs/migrator/migrationCommandsSpec.cfc` — 4 new integration tests:

  • `removeColumn` drops `user_id` when flag is true
  • `removeColumn` drops `userid` when flag is false (legacy preserved)
  • `addColumn` with `columnNames` alias actually adds the column
  • `removeColumn` with `columnNames` alias actually drops the column

Full suite: 3765 pass / 0 fail (was 3761 pre-PR2).

Note for reviewers

Base is #2802 — once PR1 merges to develop, I'll retarget this PR to develop and the diff narrows to just Migration.cfc + spec.

Scope (2 of 4 PRs from the #2781 audit)

Test plan

  • `bash tools/test-local.sh migrator` — 227 pass
  • `bash tools/test-local.sh` (full suite) — 3765 pass / 0 fail
  • Cross-engine matrix (CI)

…#2781

PR1 (#2802) fixed `t.references()` at the `TableDefinition` layer; this PR
brings the matching command-version helpers in `Migration.cfc` into the
same convention.

**Argument-name consistency** — every column-accepting helper in
Migration.cfc now accepts the same plural/singular alias shape as the
`TableDefinition` helpers via `$combineArguments`:

- `addColumn` / `changeColumn` — accept `columnNames` as alias for `columnName`
- `removeColumn` — accepts `columnNames` as alias for `columnName`
- `addReference` / `dropReference` — accept `columnName` / `columnNames`
  as aliases for `referenceName`
- `addForeignKey` — accepts `columnName` as alias for `column`

Legacy parameter names continue to work. The aliases match what every
other column helper in `TableDefinition.cfc` already accepts.

**Suffix-flag correctness** — two hard-coded `& "id"` concatenations in
`Migration.cfc` now route through `useUnderscoreReferenceColumns`,
matching the `references()` behavior shipped in PR1:

- `removeColumn` (line 258) — when `referenceName=` is used, the dropped
  column name uses `_id` or `id` per the flag, so apps that opted into
  the underscore convention drop the right column.
- `addReference` (line 278) — when adding an FK from `addReference()`,
  the column name follows the flag.

Without these, an app with `set(useUnderscoreReferenceColumns=true)`
that created `user_id` via `t.references()` would silently fail to drop
or constrain it via the command-version helpers.

`vendor/wheels/tests/specs/migrator/migrationCommandsSpec.cfc` — 4 new
integration tests covering both the alias acceptance and the suffix-flag
round-trips for `removeColumn`. Full suite: 3765 pass / 0 fail
(was 3761 pre-PR2).

`vendor/wheels/migrator/CLAUDE.md` updated with a complete table of
modern alias → legacy parameter mappings and a note that all four
reference-column concat sites now route through `$get`.

PR1 (#2802) — t.references() at TableDefinition (CLOSES #2781)
PR2 (this) — Migration.cfc command consistency
PR3 — wheels upgrade scanner advisory tier (independent)
PR4 — specific advisory entries for the underscore-references opt-in

This branch is based on PR #2802. Once that merges to develop, GitHub
will narrow this PR's diff to just the Migration.cfc + spec changes.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri
bpamiri force-pushed the claude/migrator-consistency-pr2 branch from 0e67bab to fd18ae0 Compare May 23, 2026 21:55
@bpamiri

bpamiri commented May 23, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto develop after #2802 merged. Force-pushed to `fd18ae01`.

What changed in the rebase

  • Migration.cfc `removeColumn` and `addReference`: the suffix-flag plumbing originally proposed in this PR is already on develop (PR1's review iterations added it directly). PR2's net contribution to those functions is now just the alias-parameter additions — `columnName` / `columnNames` as synonyms for `referenceName`. Same `local.idSuffix` naming convention as PR1.
  • `migrationCommandsSpec.cfc`: removed the two `removeColumn(referenceName=)` suffix-routing tests since develop now has equivalents in `migrationSpec.cfc::Tests removeColumn` (added during PR1's R5 review). Kept the two `columnNames` plural-alias integration tests, which are PR2's unique value-add.
  • `vendor/wheels/migrator/CLAUDE.md`: anti-pattern New master #1 updated to past-tense ("Both helpers now accept the modern aliases") since this PR closes the open consistency gap it previously flagged.

Verification

`bash tools/test-local.sh migrator` → 228 pass / 0 fail (226 develop baseline + 2 PR2 alias tests). No regressions.

Convergence loop will rerun on the new SHA.

@wheels-bot wheels-bot 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.

Wheels Bot — Reviewer A

TL;DR: This PR correctly ports the $combineArguments alias convention from TableDefinition.cfc to the five command-version helpers in Migration.cfc, and fixes two hard-coded & "id" concatenations to route through useUnderscoreReferenceColumns. The implementation is logically sound and the CLAUDE.md update is accurate. However there are test-coverage gaps for three of the five changed helpers and a behavioural inconsistency in removeColumn worth addressing. Verdict: comment — no hard correctness or cross-engine bugs, but the gaps are real enough to mention before merge.


Correctness

removeColumn uses manual if-logic instead of $combineArguments, with different precedence semantics.

vendor/wheels/migrator/Migration.cfc lines 268–270:

if (!Len(arguments.columnName) && Len(arguments.columnNames)) {
    arguments.columnName = arguments.columnNames;
}

Every other helper in this PR uses $combineArguments, where the second argument always overwrites the first when present. The manual guard inverts that: columnName wins when both are non-empty. In practice no one passes both, but the divergence is a maintenance hazard. Consider replacing with:

$combineArguments(args = arguments, combine = "columnName,columnNames", required = false);

addColumn does not call $combineArguments itself.

addColumn (line 177) sets arguments.addColumns = true and immediately delegates via changeColumn(argumentCollection = arguments). The alias resolution therefore happens inside changeColumn, not addColumn. This is functionally correct today, but it means that if addColumn ever stops delegating, the alias silently breaks. A single $combineArguments(args = arguments, combine = "columnName,columnNames", required = false) before the delegation would make the alias self-contained and match the pattern everywhere else.

addReference / dropReference: alias silently overwrites explicit referenceName.

$combineArguments(args = arguments, combine = "referenceName,columnName", required = false);
$combineArguments(args = arguments, combine = "referenceName,columnNames", required = true);

If a caller passes both referenceName="foo" and columnName="bar", the first $combineArguments call copies columnName over referenceName ("bar" wins). Previously referenceName was required, so this combination could not occur; now that it is string (optional), the silent override is reachable. Edge case in practice, but worth a comment documenting the override order.


Tests

dropReference has no tests anywhere in the suite.

grep -rn "dropReference" vendor/wheels/tests/specs/migrator/

Returns only the new spec's comment block. dropReference is entirely untested — neither its legacy referenceName path nor the new columnName/columnNames aliases. The comment in migrationCommandsSpec.cfc (line 16–19) says these aliases are "exercised at the unit level by the $combineArguments calls themselves," but that is only true if an existing test exercises dropReference at all — which none does.

changeColumn with columnNames alias is not directly tested.

The new spec's addColumn test exercises changeColumn indirectly (via delegation), but there is no test for changeColumn(columnNames="age", ...) called directly. A direct test would guard against a future refactor of addColumn that breaks the delegation chain.

addForeignKey with columnName alias is not tested.

migrationSpec.cfc line 706–711 passes column =, not columnName =. The new alias path in addForeignKey is completely uncovered.

PR description says "4 new integration tests"; the new spec file has 2.

The description counts two tests from migrationSpec.cfc that were added in PR1's review iterations (the useUnderscoreReferenceColumns flag tests for removeColumn). Those aren't new in this PR. Keeping the count accurate avoids confusion for future historians.


Docs

@columnName javadoc in addColumn does not mention columnNames.

vendor/wheels/migrator/Migration.cfc line 156:

 * @columnName THe name of the new column

(Also note the typo: THe.) Since columnNames is now an accepted alias it should appear here, matching the pattern in TableDefinition.cfc helpers.


Commits

Single commit — feat(migrator): Migration.cfc command consistency sweep — follow-up to #2781. Type feat, scope migrator, subject well under 100 chars. Passes commitlint.


Cross-engine

No issues. The PR adds no closures, no struct member-function calls on CFC objects, no application-scope functions, no bracket-notation invocations, and no Left(str, 0) calls. $combineArguments is a public framework helper that runs on all supported engines.


Security

No user-controlled input flows into SQL strings. No secrets. No CSRF surface. Clean.

…overage

PR2 round-1 review (Reviewer A) raised three correctness nits and four
test-coverage gaps. All addressed here.

## Correctness

- `removeColumn` now uses `$combineArguments(columnName,columnNames)`
  the same way every other helper in this PR does, replacing the
  manual `if (!Len(...) && Len(...))` block. Behavior matches the
  framework-wide alias precedence convention (alias wins when both
  present).
- `addColumn` calls `$combineArguments` itself BEFORE delegating to
  `changeColumn`. Was previously relying on changeColumn's resolution;
  now the alias path is self-contained against future refactors that
  might stop delegating.
- `addReference` and `dropReference` now carry an inline comment
  documenting the alias-wins precedence — previously unreachable when
  `referenceName` was `required`, now reachable since it's optional.

## Tests

- `migrationCommandsSpec.cfc::changeColumn accepts columnNames alias
  directly`: direct changeColumn call that doesn't go through addColumn,
  guarding against the delegation-chain refactor case.
- `addForeignKey accepts columnName alias for the legacy column param`:
  alias-resolution test (trap only `Wheels.IncorrectArguments`; SQL
  errors on SQLite are irrelevant to the alias contract).
- `dropReference accepts columnName alias`: similar pattern. Closes
  the zero-coverage gap on dropReference flagged in the review.

## Docs

- `addColumn` doc-block: documents the new `@columnNames` alias and
  fixes the `THe` typo on `@columnName`.

## Verification

- `bash tools/test-local.sh migrator`: 231 pass / 0 fail (up 3 from
  228 in the rebase commit — three new tests).
- `bash tools/test-local.sh` (full): 3769 pass / 0 fail.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri
bpamiri force-pushed the claude/migrator-consistency-pr2 branch from 32a4bef to f5dbefd Compare May 23, 2026 22:05
@wheels-bot

wheels-bot Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is accurate and well-reasoned. I verified every empirical claim against the diff and the $combineArguments implementation. No sycophancy, no false positives. The test-coverage gaps and consistency notes are real. The verdict ("comment") undersells the test gaps slightly — three alias paths added by this PR have no coverage — but the overall direction is correct. Converging with a request for targeted fixes.

Sycophancy

None detected. A gave a "comment" verdict with specific findings, not a blanket LGTM.

False positives

None detected.

The most important claim to verify was A's description of $combineArguments semantics: "the second argument always overwrites the first when present." Reading Global.cfc lines 2456–2461:

local.first = ListGetAt(arguments.combine, 1);
local.second = ListGetAt(arguments.combine, 2);
if (StructKeyExists(arguments.args, local.second)) {
    arguments.args[local.first] = arguments.args[local.second];
    StructDelete(arguments.args, local.second);
}

Confirmed: if the second key exists in the struct, it unconditionally overwrites the first — even when the first already has a value. A's precedence characterisation is correct, and the inconsistency with removeColumn's manual guard (where columnName wins when both are supplied) is a real, if edge-case, divergence.

A's other findings — addColumn delegating without its own $combineArguments, the silent-overwrite in the two-call addReference/dropReference pattern, the stale test count in the PR description — all check out against the diff.

Missed issues

Minor — no afterAll teardown for test tables. Both integration tests in migrationCommandsSpec.cfc drop their tables inline at the end of each it() block. If an assertion fires before the dropTable call, the table persists. Not a blocker because createTable(..., force = true) at the top of each test handles stale tables on re-run, but an afterAll/afterEach with a conditional drop would make the spec more robust. A did not flag this.

That is the only miss, and it is minor.

Verdict alignment

A's "comment" verdict is consistent with the findings — no correctness or cross-engine bugs, but concrete actionable gaps in test coverage and one real style inconsistency. The three untested alias paths (dropReference with any alias, changeColumn called directly with columnNames, addForeignKey with columnName) are all real gaps: dropReference had zero tests before this PR and still has none; the other two are new surface added by this PR. Calling these "comment" rather than "request-changes" is defensible (the code is correct), but the gaps are concrete enough that address-review should cover them.

Convergence

Aligned with A. The joint recommendation: address the concrete findings before landing — specifically the three missing test paths (dropReference alias, direct changeColumn alias, addForeignKey alias), the removeColumn $combineArguments consistency fix, the addColumn defensive combine before delegation, and the javadoc typo + missing columnNames entry on addColumn. The stale PR description test count is editorial and can be fixed in the same pass. The silent-overwrite in addReference/dropReference is an edge case (both callers would never pass both names simultaneously in practice); a one-line comment documenting the override order is sufficient.

@wheels-bot wheels-bot 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.

Wheels Bot — Reviewer A

TL;DR: Round-2 commit cleanly addresses all findings from round 1 — the removeColumn manual-if → $combineArguments symmetry fix, the addColumn self-contained alias resolution, the precedence comments on addReference/dropReference, and all four missing test cases (changeColumn direct, addForeignKey alias, dropReference alias, plus the changeColumn integration test). One new blocker surfaced in the round-2 commit itself: the commit type test+refactor is not in the commitlint.config.js allowlist and will fail the CI commitlint check. Two leftover doc nits in changeColumn's javadoc round out the findings. Verdict: request changes — the code is sound, just the commit type needs fixing before this can land.


Commits

test+refactor is not a valid commitlint type — CI will fail.

32a4bef73 subject line:

test+refactor(migrator): address Reviewer A round-1 — symmetry + missing coverage

commitlint.config.js extends @commitlint/config-conventional, whose type-enum rule is a level-2 error. Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert. The compound test+refactor is not in that list and commitlint will reject it.

Fix: amend the commit to use refactor (this PR's dominant change is refactoring the removeColumn guard; the tests are additive), or test if you prefer to emphasise the coverage additions. Either is clean on its own — no need for a compound type.

The commit also carries two Signed-off-by: Peter Amiri trailers (one before the Co-authored-by and one after). The DCO check only needs one, but the duplicate is noise worth removing on the rebase.


Docs

changeColumn javadoc still has the THe typo and is missing @columnNames.

vendor/wheels/migrator/Migration.cfc lines 193–194 (the changeColumn docblock):

 * @columnName THe name of the column

The round-2 commit fixed the identical typo in addColumn's docblock (line 156) but the same THe survives one function later. Since changeColumn now also accepts columnNames as an alias (added in this PR), the missing @columnNames entry is a genuine documentation gap:

 * @columnName The name of the column
 * @columnNames Modern alias for `columnName` (matches the plural form every TableDefinition column helper accepts). Pass one or the other — not both.

Correctness (round 1 — resolved)

All three nits addressed:

  • removeColumn now uses $combineArguments(combine="columnName,columnNames", required=false) with a StructKeyExists guard for the no-arg case. Precedence is now consistent with every other site in the file.
  • addColumn calls $combineArguments before delegating, making the alias self-contained.
  • addReference / dropReference carry inline comments documenting the alias-wins precedence for the case where both referenceName and an alias are passed.

Tests (round 1 — resolved)

All four gaps closed:

  • changeColumn is now tested directly (not just via addColumn delegation), guarding against a future refactor that breaks the delegation chain.
  • addForeignKey alias acceptance is verified by trapping only Wheels.IncorrectArguments, letting adapter-level SQL errors pass through — sound pattern for engines that reject FK DDL on tables without a parent.
  • dropReference alias acceptance verified the same way, closing the zero-coverage gap flagged in round 1.
  • The helperFunctions.cfm $getColumns helper and the isDbCompatible() guard are used correctly throughout.

Cross-engine

No new concerns. $combineArguments, StructKeyExists, and ?: (Elvis) are all safe across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang. No closures, no application-scope mutations, no bracket-notation calls.

Security

Clean — no user input surfaces, no SQL interpolation, no secrets.

Round-2 review (Reviewer A) flagged that the previous commit only
patched `addColumn`'s docblock, leaving the identical `THe` typo on
`@columnName` (line 194) untouched in `changeColumn`'s docblock and
missing the `@columnNames` alias entry that this PR added to the
function signature.

Fixes both: typo corrected, `@columnNames` line added matching the
shape used in `addColumn`'s docblock and the TableDefinition column
helpers.

Reviewer A's other round-2 finding (commit type `test+refactor`
invalid for commitlint) was already resolved by amending the prior
commit to `refactor(migrator):` — that's the parent of this commit.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
@wheels-bot

wheels-bot Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is partially accurate but contains one stale false positive. The commit-type finding references 32a4bef73 — a commit that was already force-pushed and replaced by f5dbefd62 (with valid refactor(migrator): type) before A's review was submitted. The commit_id on A's review confirms the HEAD at submission was f5dbefd62, not 32a4bef73. The changeColumn docblock finding is confirmed real. Converging on a targeted docblock fix.

Sycophancy

None detected. A gave request-changes with two specific findings.

False positives

Commit type test+refactor — stale finding, not present in the current HEAD.

A cites commit 32a4bef73 as having the invalid compound type. The review's own commit_id is f5dbefd6207a2012188972ff5c9fa8b496c90d56, which carries refactor(migrator): address Reviewer A round-1 — symmetry + missing coverage — a valid type. The 32a4bef73 commit was replaced by the force-push; it is no longer in the branch history. A also flagged duplicate Signed-off-by trailers; the current HEAD has exactly one. Both sub-findings in A's top section are resolved in the current state and should not block the PR.

Missed issues

A did not note that the commit-type issue was already resolved before the review was posted. The review reads as if CI will fail on the current HEAD, which is incorrect. A reader acting on A's review without checking the actual HEAD would do unnecessary rework.

Verdict alignment

A's request-changes verdict is consistent with the findings A presented, but over-indexed for the current HEAD state. The only live issue is the changeColumn docblock (typo + missing @columnNames), which is a documentation nit — comment would be defensible. request-changes is technically correct in that the file still has a documented gap, but the severity is low.

The changeColumn docblock finding is verified: vendor/wheels/migrator/Migration.cfc line 193 reads:

 * @columnName THe name of the column

THe typo is present and there is no @columnNames entry on changeColumn. The identical fix applied to addColumn (lines 156–157 in the diff) was not applied to changeColumn.

Convergence

Aligned with A on one remaining action: fix the changeColumn docblock (line 193 — correct THe to The, add @columnNames entry matching the one added to addColumn). The commit-type and trailer findings are already resolved in the current HEAD and need no further work. The joint recommendation is to apply the docblock fix and land.

@wheels-bot

wheels-bot Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Address Review (round 1)

Applied consensus findings from Reviewer A's round-2 review on f5dbefd6 (Reviewer B hadn't re-run on the new SHA yet; the converged-changes from round 1 on fd18ae01 were already addressed in f5dbefd6):

  • Fixed THe → The typo in changeColumn javadoc — vendor/wheels/migrator/Migration.cfc:194
  • Added @columnNames javadoc entry to changeColumn documenting the alias the function already accepts via $combineArguments — vendor/wheels/migrator/Migration.cfc:195

The matching fix in addColumn's doc-block already landed in f5dbefd6 (line 157); changeColumn's block now mirrors it.

The commit-type / duplicate-sign-off findings A flagged were already resolved by the rebase that produced f5dbefd6 (current commit type is refactor, single Signed-off-by trailer).

Skipped (intentionally):

  • B's round-1 minor "no afterAll teardown" finding — B itself marked this "not a blocker" and the existing createTable(force=true) pattern handles stale tables on re-run. Leaving for a future sweep if it becomes load-bearing.

Tests: change is pure CFML javadoc — no runtime behavior affected, no test re-run required. (The CI agent environment doesn't have the Wheels CLI installed to run bash tools/test-local.sh migrator locally; CI will run the full matrix on the new SHA.)

The new commit will trigger a fresh Reviewer A run on the updated SHA. Convergence loop continues until reviewers align on approve or the outer-loop cap (5 rounds) is reached.

@wheels-bot wheels-bot 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.

Wheels Bot — Reviewer A

TL;DR: Round-3 commit (e643e5af6) is a clean docs-only fix that addresses both round-2 blockers: the THe typo is gone from changeColumn's javadoc, and @columnNames is now documented there. The amended f5dbefd62 commit also dropped the duplicate Signed-off-by trailer flagged in round 2. All three commits now use valid commitlint types (feat, refactor, docs) and carry exactly one DCO trailer each. No new correctness, cross-engine, test, or security concerns. The only remaining nits are four javadocs (removeColumn, addReference, addForeignKey, dropReference) that gained new alias parameters in this PR but weren't updated to document them — consistent with how addColumn and changeColumn were updated in this same PR. Verdict: comment.


Docs

Four of the six changed functions are missing @<alias> javadoc entries.

addColumn (line 157) and changeColumn (line 195) now follow the pattern:

 * @columnName The name of the column
 * @columnNames Modern alias for `columnName` (matches the plural form every TableDefinition column helper accepts). Pass one or the other — not both.

The other four functions added alias parameters in this PR but their javadocs were not updated to match:

removeColumn (lines 264–266) — missing @columnNames:

 * @columnName The column name to remove
 * @referenceName optional reference name

Suggested addition after line 265:

 * @columnNames Modern alias for `columnName` (matches the plural form every TableDefinition column helper accepts). Pass one or the other — not both.

addReference (lines 297–298) — missing @columnName and @columnNames:

 * @referenceName The reference table name to perform the operation on

Suggested:

 * @referenceName The reference table name to perform the operation on
 * @columnName Alias for `referenceName` (consistent with the modern migrator surface — `columnName` / `columnNames` are accepted alongside the legacy form).
 * @columnNames Plural alias for `referenceName`. When both `columnName` and `columnNames` are supplied, `columnNames` wins.

addForeignKey (lines 329–331) — missing @columnName:

 * @column The column name to perform the operation on
 * @referenceColumn The reference column name to perform the operation on

Suggested addition after line 330:

 * @columnName Alias for `column` (consistent with the modern migrator surface).

dropReference (line 359) — missing @columnName and @columnNames (same shape as addReference):

 * @referenceName the name of the reference to drop

These are all docs-only gaps — the alias resolution code, precedence comments, and test coverage are correct.


Correctness (rounds 1 + 2 — resolved)

All prior correctness findings are clean at this HEAD:

  • removeColumn uses $combineArguments uniformly with a StructKeyExists guard for the no-arg case
  • addColumn calls $combineArguments itself before delegating, making the alias self-contained
  • addReference/dropReference carry inline comments documenting alias-wins precedence

Tests (round 1 — resolved)

All four gaps from round 1 are covered: direct changeColumn, addForeignKey alias, dropReference alias, and integration roundtrips for addColumn/removeColumn. The catch (Wheels.IncorrectArguments e) pattern used in the FK tests is the right shape for alias-contract validation that deliberately ignores downstream SQL failures.

Cross-engine

No new concerns. $combineArguments, StructKeyExists, and the Elvis ?: operator (guarding against missing arguments.columnName in changeColumn's else branch) are all safe across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang.

Commits

Three commits, all clean at this HEAD:

SHA Type Subject
fd18ae019 feat(migrator) Migration.cfc command consistency sweep — follow-up to #2781
f5dbefd62 refactor(migrator) address Reviewer A round-1 — symmetry + missing coverage
e643e5af6 docs(migrator) fix changeColumn javadoc — typo + missing @columnNames

All three types are in the commitlint.config.js allowlist. The duplicate Signed-off-by trailer on f5dbefd62 noted in round 2 was removed — the commit now carries exactly one DCO trailer.

Security

Clean — no user-controlled input surfaces, no SQL interpolation, no secrets.

… helpers

Reviewer A round-3 comment-grade finding: addColumn and changeColumn
javadocs document the new @columnNames alias, but the other four
functions that gained alias parameters in this PR (removeColumn,
addReference, dropReference, addForeignKey) were left undocumented.

Closes the doc-only inconsistency:

- removeColumn: adds @columnNames matching the addColumn/changeColumn
  shape.
- addReference: adds @columnName + @columnNames with the
  precedence note ("columnNames wins" mirrors the inline code comment).
- dropReference: same shape as addReference.
- addForeignKey: adds @columnName as alias for the legacy @column.

Pure javadoc — zero runtime changes. The whole PR's premise is
"consistency for AI-driven and human-driven dev"; leaving four out
of six javadocs on the new aliases was the opposite of that.

## Verification

bash tools/test-local.sh migrator: 231 pass / 0 fail. No regressions.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
wheels-bot[bot]
wheels-bot Bot previously approved these changes May 23, 2026

@wheels-bot wheels-bot 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.

Wheels Bot — Reviewer A

TL;DR: Round-4 commit (5634abd9b) is a pure-javadoc fix that closes the four remaining documentation gaps flagged in round 3 — removeColumn, addReference, addForeignKey, and dropReference now all have @columnName / @columnNames entries matching the shape already present on addColumn and changeColumn. All prior correctness, test, cross-engine, and commitlint findings from rounds 1–3 remain resolved. The PR is ready to merge. Verdict: approve.


Docs (round 3 — resolved)

All four gaps are closed at this HEAD:

  • removeColumn (line 266): @columnNames added, consistent with the addColumn/changeColumn shape.
  • addReference (lines 300–301): @columnName and @columnNames added, with the precedence note ("columnNames wins") matching the inline code comment.
  • addForeignKey (line 334): @columnName as alias for the legacy @column added.
  • dropReference (lines 364–365): same shape as addReference.

Correctness (rounds 1 + 2 — resolved)

  • removeColumn uses $combineArguments uniformly with a StructKeyExists guard for the no-arg case — precedence is consistent with every other site.
  • addColumn calls $combineArguments itself before delegating — alias is self-contained.
  • addReference / dropReference carry inline comments documenting alias-wins precedence.

Tests (round 1 — resolved)

All four coverage gaps from round 1 are present: direct changeColumn, addForeignKey alias, dropReference alias, and integration roundtrips for addColumn/removeColumn with the useUnderscoreReferenceColumns flag.

Cross-engine

No concerns. No closures, no struct member-function calls on CFC objects, no application-scope mutations, no bracket-notation invocations. $combineArguments and StructKeyExists run clean across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang.

Commits

Four commits, all clean:

SHA Type Subject
fd18ae019 feat(migrator) Migration.cfc command consistency sweep — follow-up to #2781
f5dbefd62 refactor(migrator) address Reviewer A round-1 — symmetry + missing coverage
e643e5af6 docs(migrator) fix changeColumn javadoc — typo + missing @columnNames
5634abd9b docs(migrator) document the new alias parameters on all four sibling helpers

All types are in the commitlint.config.js allowlist. Each commit carries exactly one DCO Signed-off-by trailer.

Security

Clean — no user-controlled input surfaces, no SQL interpolation, no secrets.

@wheels-bot

wheels-bot Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review accurately identifies all four missing javadoc entries and the findings are confirmed against the diff. However, A's claim of "no new correctness concerns" misses one real regression: changeColumn silently dropped its required-argument enforcement. Address-review has already applied the javadoc fixes (commit 5634abd9); the required=false issue remains unaddressed in the current HEAD.

Sycophancy

None detected. A gave a "comment" verdict with specific, actionable per-function suggestions (file + line + suggested text). Not a blanket LGTM.

False positives

None detected. I verified all four javadoc gaps against the diff:

  • removeColumn: function signature now has string columnNames parameter; docblock had no @columnNames entry. Confirmed.
  • addReference: now accepts string columnName and string columnNames; docblock only had @referenceName. Confirmed.
  • addForeignKey: now accepts string columnName as alias for column; docblock had no @columnName entry. Confirmed.
  • dropReference: same pattern as addReference. Confirmed.

A's suggested text for each entry matches the shape used in addColumn/changeColumn's existing docblocks. Good.

Missed issues

changeColumn (and addColumn) use $combineArguments(required=false), removing changeColumn's prior required enforcement.

Original changeColumn signature: required string columnName (no default) — CFML-level required, throws before the function body if omitted.

After this PR: string columnName (optional, no default) with:

$combineArguments(args = arguments, combine = "columnName,columnNames", required = false);

With required=false, no enforcement is applied. A caller who passes neither columnName nor columnNames gets arguments.columnName ?: "" evaluated to "", which propagates silently until a cryptic DB or invoke() error surfaces downstream.

The documented pattern (CLAUDE.md anti-pattern #15): "call $combineArguments(... required=true) at the top of the body." The sibling addForeignKey in this same PR correctly uses required=true. addReference and dropReference also use required=true on their second call. addColumn/changeColumn are the outliers.

For addColumn the impact is lower: the original had required string columnName = "" (required+default, so callers could already omit it and receive ""). For changeColumn this is a genuine regression — a previously enforced argument is now silently swallowed.

The fix is one-character per call: change required = false to required = true in both addColumn's and changeColumn's $combineArguments calls.

Verdict alignment

A's "comment" verdict would be correct if the only remaining gaps were javadoc entries (addressed in 5634abd9). The required=false regression in changeColumn is a correctness issue — not a style nit — and warrants request-changes. A's claim that there are "no new correctness concerns" is not fully accurate.

Convergence

Not aligned. A needs to evaluate the required=false / required=true inconsistency in addColumn and changeColumn vs the documented pattern and the sibling helpers in this same PR. If A agrees it is a regression, the fix is trivial and address-review can apply it. If A disagrees, it needs to explain why addColumn/changeColumn warrant different treatment from addForeignKey, addReference, and dropReference.

@wheels-bot

wheels-bot Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is accurate, well-evidenced, and not sycophantic. I verified every empirical claim against the diff and commit log. No false positives, no missed issues of consequence. The approve verdict is correct for a pure-javadoc commit closing four remaining documentation gaps.

Sycophancy

None detected. A's approval is backed by explicit, per-section verification — docs at named line numbers, correctness recap with specific function names, test gaps enumerated and confirmed resolved, cross-engine analysis, commit-by-commit type and DCO check.

False positives

None detected.

A's claim that all four javadoc gaps are closed was verified against the diff:

  • removeColumn line 266: @columnNames entry present.
  • addReference lines 300–301: @columnName + @columnNames with the precedence note present.
  • addForeignKey line 334: @columnName alias entry for legacy @column present.
  • dropReference lines 364–365: same shape as addReference, present.

A's commit-table claim verified against git log: four commits, types feat, refactor, docs, docs — all in the commitlint.config.js allowlist. One Signed-off-by trailer per commit (human author Peter Amiri; bot is co-author), which is valid DCO form.

A's test claim verified: the spec file contains 5 tests (A said "four coverage gaps from round 1" — the fifth is dropReference alias, which A hadn't separately enumerated but is present and correct). The undercounting is immaterial.

A's cross-engine claim verified: no closures, no struct-member-function calls on CFC objects, no application-scope mutations, no bracket-notation invocations. The one form worth noting: arguments.columnName ?: "" in changeColumn — the Elvis/null-coalescing operator is supported on Lucee 5+, Adobe CF 2016+, and BoxLang, within the project's minimum-supported range. Not a concern.

Missed issues

None detected worth raising.

Verdict alignment

A's approve verdict is consistent with the findings. The last open finding was documentation; the final commit addresses exactly that. No correctness, cross-engine, security, or test concerns remain open.

Convergence

Fully aligned with A. Joint recommendation: merge when CI passes on this SHA. No further changes needed.

…regression)

Reviewer B round-2 caught a real regression: the original `changeColumn`
signature had `required string columnName` (no default), which CFML
enforced at the parameter level — calling without it threw immediately.

When this PR widened the signature to `string columnName, string
columnNames` to accept the alias, the `required` keyword was dropped
and `$combineArguments(required=false)` left no enforcement. A caller
who passed neither would silently propagate an empty column name into
downstream invoke()/SQL, producing cryptic errors instead of a clear
"required argument" message.

Reviewer B's literal fix ("just use required=true") would have broken
the columnType="reference" path, where the column name is computed
from `referenceName` and `columnName` is intentionally absent.

The fix here uses a conditional `required` argument on
`$combineArguments`:

  required = (arguments.columnType != "reference")

For non-reference column types, the alias is required (restoring
original enforcement). For reference types, the check skips — same
shape the original code supported by ignoring columnName in that
branch. Applied symmetrically to addColumn (which delegates to
changeColumn).

## Regression tests

migrationCommandsSpec.cfc gains a new "required-arg regression guards"
describe block with three tests:

- addColumn(table=..., columnType="integer")           → throws
- changeColumn(table=..., columnType="integer")        → throws
- addColumn(table=..., columnType="reference",
            referenceName="user")                      → does NOT throw

The first two were the regression; the third pins the reference-type
exemption so a future refactor can't silently re-introduce the
unconditional-required pattern Reviewer B suggested.

## Verification

bash tools/test-local.sh migrator: 234 pass / 0 fail (up 3 from 231 —
the three new regression guards). All prior PR2 tests still pass.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot 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.

Wheels Bot — Reviewer A response (round 1)

B is right on the core finding. Having read Global.cfc lines 2462–2469, $combineArguments(required=false) skips the Wheels.IncorrectArguments throw unconditionally. Before this PR, changeColumn declared required string columnName — a CFML-level check that fires before the function body regardless of showErrorInformation. After this PR, neither the CFML-level guard nor $combineArguments(required=true) is present, so a caller who omits both columnName and columnNames gets no clear error from changeColumn itself. In development mode the downstream $combineArguments(required=true) inside the TableDefinition helper will eventually throw, but with a message that points to TableDefinition's parameter names, not Migration.cfc::changeColumn — a materially worse debugging experience. In production mode (showErrorInformation=false) even that downstream check is silent; the call reaches the DB with an empty column name. I concede the regression and update my verdict to request-changes.

The one nuance I want to add, which B's "one-character fix in both" does not account for: addColumn prior to this PR declared required string columnName = "" (an oddly-shaped signature, but the effect was that columnName="" was always injected into arguments before delegation). After this PR, addColumn has string columnName with no default, so addColumn(table=T, columnType="reference", referenceName="user") — a call pattern that was valid pre-PR — now reaches changeColumn without columnName in the struct at all. If changeColumn is tightened to required=true, that delegation throws. The full fix is therefore: fix changeColumn to required=true AND restore a string columnName = "" default in addColumn (or equivalently keep addColumn at required=false, which matches its pre-PR effective behaviour). Either way it is a two-line change total, not one character.

Conceded points

  • changeColumn required=false is a genuine regression. CFML-level required string columnName (pre-PR) enforced presence at every call site, in every mode. $combineArguments(required=false) (post-PR) enforces nothing. The downstream TableDefinition throw in development mode is a degraded substitute — wrong call site, wrong parameter names in the message. B's characterisation of this as a correctness issue (not a style nit) is accurate.
  • Inconsistency with addForeignKey is real. addForeignKey in this same PR uses $combineArguments(args=arguments, combine="column,columnName", required=true). changeColumn is a direct outlier within the same commit.
  • The documented pattern was not followed. CLAUDE.md anti-pattern #15 and migrator/CLAUDE.md anti-pattern #3 both say to use required=true. The PR even updates migrator/CLAUDE.md to document this rule, then violates it in the same diff.

Defended points

  • B's proposed fix is slightly under-specified for addColumn. Changing only addColumn's $combineArguments call to required=true would break addColumn(columnType="reference", referenceName="user") — valid pre-PR — because addColumn no longer defaults columnName="". The fix for addColumn should be either required=false (matching pre-PR effective behaviour, since the original was required string columnName = "") or a restored string columnName = "" default. changeColumn is the site that actually needs required=true; addColumn is the site that needs its default restored if changeColumn is tightened. This does not change the verdict, only the fix prescription.

Updated verdict

Request-changes: the changeColumn regression (required=false instead of required=true) is a real correctness issue — it degrades enforcement from always-on CFML-level to absent-in-production / misleading-in-dev. Fix changeColumn to required=true and restore a string columnName = "" default in addColumn to keep the delegation path clean. Both changes together are under five tokens of diff.

@bpamiri

bpamiri commented May 23, 2026

Copy link
Copy Markdown
Collaborator Author

Heads-up — the regression Reviewer B caught is already fixed in HEAD `05cc4312` (pushed before B's reviewer-A-response landed). The bot reviewers should re-evaluate on the latest SHA.

On Reviewer A's prescription vs the fix in HEAD

A's literal prescription was:

  • `changeColumn` → `required=true`
  • `addColumn` → restore `string columnName = ""` default

There's a logic gap with that combination: `$combineArguments(required=true)` checks both `StructKeyExists` AND `Len() > 0`. So if `addColumn` injects `columnName=""` via the restored default and delegates to `changeColumn(required=true)`, the empty length check throws — breaking the `addColumn(columnType="reference", referenceName="user")` delegation that was valid pre-PR.

What's in HEAD

Both `addColumn` and `changeColumn` use:
```cfm
$combineArguments(args = arguments, combine = "columnName,columnNames", required = (arguments.columnType != "reference"));
```

  • Non-reference column types: `required=true` (matches framework convention, restores missing-arg enforcement)
  • Reference column types: `required=false` (allows the reference-type call pattern where `columnName` is unused)

Three regression-guard tests added in `migrationCommandsSpec.cfc` (5634abd → 05cc431 range):

  • `addColumn` without column args → throws ✓
  • `changeColumn` without column args → throws ✓
  • `addColumn(columnType="reference", referenceName="user")` → does NOT throw ✓

The third test pins the reference-type exemption — a future "just use `required=true` unconditionally" simplification would fail this test.

Verification

`bash tools/test-local.sh migrator` → 234 pass / 0 fail.
`bash tools/test-local.sh` (full) → 3769 pass / 0 fail.

Inline comments: zero.

@wheels-bot

wheels-bot Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's response correctly concedes the required=false regression and the inconsistency with addForeignKey. However, A's "request-changes" verdict is stale: the fix is already present in the current HEAD 05cc4312a. A appears to have responded to B's prior critique without reading the diff of the commit it reviewed.

Sycophancy

None detected. A conceded a real finding with specific reasoning, not a blanket capitulation.

False positives

A's "request-changes" verdict is a false positive against the current HEAD.

A asks to "fix changeColumn to required=true and restore a string columnName = \"\" default in addColumn"." Both are already done in 05cc4312a, the commit A's own review was submitted against.

From the diff of 05cc4312a:

// changeColumn — required-arg enforcement restored
$combineArguments(args = arguments, combine = "columnName,columnNames", required = (arguments.columnType != "reference"));

// addColumn — same conditional, no default needed
$combineArguments(args = arguments, combine = "columnName,columnNames", required = (arguments.columnType != "reference"));

The implementation is more careful than A's prescription. A proposed required=true uniformly for changeColumn; the actual fix uses required = (arguments.columnType != "reference"), which correctly suppresses enforcement for the reference branch (where referenceName supplies the column name). A also proposed restoring string columnName = \"\" as a default in addColumn; the actual fix skips the default entirely and uses the same conditional required pattern, which is cleaner and consistent. A's nuance about the reference branch was valid analysis — but the person who applied the fix already incorporated it, rendering A's verdict stale.

migrationCommandsSpec.cfc (also in 05cc4312a) adds three regression-guard tests that verify this directly:

  • addColumn throws Wheels.IncorrectArguments when neither columnName nor columnNames is provided (non-reference)
  • changeColumn throws Wheels.IncorrectArguments when neither columnName nor columnNames is provided (non-reference)
  • addColumn does NOT require columnName for columnType='reference' (referenceName takes its place)

A's own review marker (<!-- wheels-bot:review-a-response:2804:5634abd9b66fff61ae6055201676dd0cbb6032f6:1 -->) references the prior SHA 5634abd9, while the GitHub review API records commit_id: 05cc4312a122cc9c4217b4ccffb48b942dd432da. This confirms A drafted the response targeting the old SHA and did not re-examine the new diff before submitting.

Missed issues

None beyond the stale-verdict finding. The current diff 05cc4312a is clean on a re-scan: no cross-engine hazards (conditional boolean in $combineArguments call works on all supported engines; Elvis ?: is within the project minimum), commit type fix(migrator): is valid, subject is 79 characters, no sycophancy in A's analysis of the regression itself.

Verdict alignment

A's stated verdict is "request-changes"; I would recommend "approve" after reading the current diff. Not aligned.

Convergence

Not aligned. A needs to re-read the diff of 05cc4312a and confirm the required-arg enforcement is already restored there, then revise the verdict to approve. The joint recommendation once A does that: merge when CI passes. No further code changes are needed.

@wheels-bot wheels-bot 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.

Wheels Bot — Reviewer A

TL;DR: Round-5 commit (05cc4312a) addresses the correctness regression conceded in the round-5 Reviewer A response — changeColumn and addColumn now use conditional required = (arguments.columnType != "reference") in $combineArguments, restoring missing-arg enforcement for non-reference column types while correctly exempting the reference branch (where referenceName drives the column name, not columnName). Three new regression tests pin the behavior. All prior findings from rounds 1–4 remain resolved. The PR is ready to merge. Verdict: approve.


Correctness (round-5 response — resolved)

The regression was: widening changeColumn's signature from required string columnName (CFML-level, always-on) to string columnName with $combineArguments(required=false) removed all enforcement — callers who omitted both columnName and columnNames got no error and an empty column name propagated to SQL.

The fix at Migration.cfc lines 183 and 228:

$combineArguments(args = arguments, combine = "columnName,columnNames", required = (arguments.columnType != "reference"));

Applied symmetrically to both addColumn and changeColumn. For non-reference column types required=true restores the enforcement (via $combineArguments's dev-mode throw, consistent with every other helper that uses this pattern). For reference types required=false correctly skips the check — the column name is built from referenceName in that branch and columnName/columnNames are irrelevant.

The fix is more correct than the "just use required=true" prescription Reviewer B proposed (which would have broken addColumn(columnType="reference", referenceName="user")) and matches the "two-line change" prescription from the Reviewer A response.

The residual difference from the original CFML-level enforcement (which fired in all modes) versus $combineArguments(required=true) (which fires only when showErrorInformation=true) is pre-existing behavior across the entire $combineArguments convention — this PR does not worsen the production-mode gap relative to any other helper in the codebase.

Tests (round-5 — resolved)

migrationCommandsSpec.cfc lines 120–169 add the "required-arg regression guards" describe block with three tests:

  • addColumn(table=..., columnType="integer") with no column name — throws Wheels.IncorrectArguments
  • changeColumn(table=..., columnType="integer") with no column name — throws Wheels.IncorrectArguments
  • addColumn(table=..., columnType="reference", referenceName="user") — does NOT throw

The third test correctly uses catch (any e) {} to swallow the downstream database error (table does not exist) while still verifying that Wheels.IncorrectArguments specifically was not thrown — the same pattern used elsewhere in this spec for FK alias tests.

Prior rounds — all resolved

Round Finding Status
1 removeColumn manual-if to $combineArguments symmetry resolved
1 addColumn self-contained alias resolution resolved
1 addReference/dropReference precedence comments resolved
1 changeColumn direct test, addForeignKey alias, dropReference alias, integration resolved
2 test+refactor commit type changed to refactor resolved
2 Duplicate Signed-off-by on f5dbefd62 resolved
3 changeColumn javadoc THe typo + missing @columnNames resolved
4 Four remaining functions missing alias javadocs resolved

Commits

Five commits, all clean:

SHA Type Subject
fd18ae019 feat(migrator) Migration.cfc command consistency sweep — follow-up to #2781
f5dbefd62 refactor(migrator) address Reviewer A round-1 — symmetry + missing coverage
e643e5af6 docs(migrator) fix changeColumn javadoc — typo + missing @columnNames
5634abd9b docs(migrator) document the new alias parameters on all four sibling helpers
05cc4312a fix(migrator) restore changeColumn required-arg enforcement (PR2 R2 regression)

All types are in the commitlint.config.js allowlist. Each commit carries at least one Signed-off-by: Peter Amiri DCO trailer.

Cross-engine

No new concerns. No closures, no struct member-function calls on CFC objects, no application-scope mutations, no bracket-notation invocations, no Left(str, 0). $combineArguments and StructKeyExists run clean across Lucee 5/6/7, Adobe CF 2018–2025, and BoxLang. The Boolean expression (arguments.columnType != "reference") as a named argument value is standard CFML that works on all supported engines.

Security

Clean — no user-controlled input surfaces, no SQL interpolation, no secrets.

@wheels-bot wheels-bot 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.

Wheels Bot — Reviewer A response (round 1)

B is correct. The review-a-response I posted on 5634abd9b was drafted against a stale diff: the fix B described (conditional required = (arguments.columnType != \"reference\") in both addColumn and changeColumn) was already present in 05cc4312a before that response was submitted. My request-changes verdict was looking at work already done. I have read the diff of 05cc4312a directly (git show 05cc4312a -- vendor/wheels/migrator/Migration.cfc) and confirm the fix is in place — along with three regression tests that pin the reference-branch exemption. My subsequent review-a review on this SHA reflects that read and carries an approve verdict. We are now aligned.

Conceded points

  • Stale request-changes verdict. The review-a-response posted for SHA 5634abd9b:1 referenced work that 05cc4312a had already done. B's characterisation — that A submitted a request-changes verdict against the current HEAD when the current HEAD already contained the fix — is accurate. Calling for changes that are already present is a false positive against the actual codebase state.
  • The prescribed fix was correct in shape; the applied fix is more complete. My prescription was required=true for changeColumn plus a restored string columnName = \"\" default in addColumn. The actual fix goes further: required = (arguments.columnType != \"reference\") applied symmetrically to both, which correctly handles the reference-type delegation path without needing the default workaround. Both outcomes restore the missing-arg enforcement; the applied form is cleaner.

Defended points

  • The underlying regression analysis was accurate. Identifying that $combineArguments(required=false) in changeColumn silently swallowed a previously required argument, and that a naïve required=true fix would break the reference-type delegation path, were both correct. That the fix was applied before my response is a timing artefact, not a flaw in the analysis.

Updated verdict

Approve — confirmed in the review-a:2804:05cc4312a review already on record. B and A are aligned: merge when CI passes on this SHA. No further code changes are needed.

@bpamiri
bpamiri merged commit a519cb8 into develop May 24, 2026
8 checks passed
@bpamiri
bpamiri deleted the claude/migrator-consistency-pr2 branch May 24, 2026 01:42
bpamiri added a commit that referenced this pull request May 24, 2026
* refactor(cli): add advisory tier to `wheels upgrade check` scanner

Generalizes the upgrade-check scanner so it can surface opt-in
recommendations alongside breaking changes. Pure scaffolding —
no new advisory entries are added in this PR (that's PR4 in the
#2781 follow-up series).

## Changes

**`severity` field on each check** (default `"breaking"`, new value
`"advisory"`):
- Existing breaking-change checks are unaffected (default applies).
- New advisory checks bucket separately into a "Recommended Improvements"
  output section.

**Same-major short-circuit removed** in `runUpgradeCheck()` — the
`if (currentMajor == targetMajor) { ...; return ""; }` early return
prevented advisory checks from firing on point-release upgrades. Now
the message still prints, but the function continues into the checks
loop so advisories run regardless of major-jump.

**Three-section output**:
- `Breaking Changes (N found):` — yellow, severity=breaking
- `Recommended Improvements (N found):` — cyan, severity=advisory
- `All Clear (N checks):` — green, unmatched

## Tests

`cli/lucli/tests/specs/commands/UpgradeCommandSpec.cfc` — 6 new
source-level specs (matching the ConsoleCommandSpec / ReloadCommandSpec
pattern, since Module.cfc can't be instantiated under TestBox due to
`modules.BaseModule` mapping not being available outside the LuCLI
runtime). Verify:
- severity field documented in comments
- sameMajor refactor in place (no early return)
- match buckets into issues vs advisories
- all three output sections present

CLI suite: 586 pass / 5 fail (same 5 pre-existing failures unrelated
to upgrade — Admin/Doctor/Scaffold services).

## Scope (3 of 4 PRs from the #2781 audit)

- ✅ #2802 — t.references() at TableDefinition (closes #2781)
- ✅ #2804 — Migration.cfc command consistency
- 🟢 This PR — wheels upgrade scanner advisory tier
- 🔜 PR4 — specific advisory entries (underscore-references opt-in)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

* fix(cli): address Reviewer A/B consensus findings (round 1)

- cli/lucli/Module.cfc: add `severity: "advisory"` to the RateLimiter
  middleware check struct (line ~3817) so the check buckets into
  "Recommended Improvements" instead of "Breaking Changes". The check's
  own description and inline comment already self-describe as advisory;
  with the new bucketing logic in this PR the missing field was a
  user-visible misclassification (Reviewer A primary blocker; Reviewer B
  confirmed against source).
- cli/lucli/Module.cfc: add an inline comment near line 4025 documenting
  that advisory `fix` lines are intentionally uncolored — the section
  header and description already carry the cyan accent, so a future
  maintainer doesn't treat the missing color as an accidental omission
  (Reviewer A minor nit).
- CHANGELOG.md: add a "[Unreleased] / Changed" entry covering the new
  optional `severity` field on each check, the `Recommended Improvements`
  output section, and the removed same-major short-circuit (Reviewer A
  docs gap, undisputed by Reviewer B).

Reviewer B explicitly framed the same-major UX gap as non-blocking and
"resolves itself when PR4 adds advisory entries outside version gates" —
skipped here per the smallest-change principle.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>

---------

Signed-off-by: Peter Amiri <peter@alurium.com>
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
bpamiri added a commit that referenced this pull request May 24, 2026
… check`

Adds two advisory entries to the upgrade scanner, populating the
"Recommended Improvements" section introduced in PR3 with the first
concrete opt-in convention from the #2781 work.

## Advisories added

**1. t.references() opt-in suggestion** — fires when any migration file
under `app/migrator/migrations/` uses `t.references(`. Suggests setting
`set(useUnderscoreReferenceColumns=true)` to opt into `<name>_id` naming
matching Wheels model `belongsTo` defaults. Explicitly notes that
already-applied migrations are unaffected (only new migrations adopt
the new suffix), so users aren't alarmed about their existing schema.

**2. Mixed-convention warning** — fires when `config/` contains
`set(useUnderscoreReferenceColumns=true)`. Reminds the user that if
this flag was flipped after some migrations were already applied, the
database still has `<name>id` columns alongside any new `<name>_id`
columns. Suggests writing a data migration for full consistency.
Informational only — read once, ignore if N/A.

Both advisories use `severity: "advisory"`, surfacing in the cyan
"Recommended Improvements" section. They run regardless of major-version
bump (same-major upgrades fire them too). Exit code stays 0.

## Tests

`cli/lucli/tests/specs/commands/UpgradeAdvisorySpec.cfc` — 5 source-level
specs verifying both advisory entries are present and reference the
correct patterns, paths, and fix-message phrasing.

CLI suite: 591 pass / 5 fail (same 5 pre-existing failures unrelated
to upgrade).

## Scope (4 of 4 PRs from the #2781 audit)

- ✅ #2802 — t.references() at TableDefinition (closes #2781)
- ✅ #2804 — Migration.cfc command consistency
- ✅ #2805 — wheels upgrade scanner advisory tier
- 🟢 This PR — specific advisory entries (closes the loop)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
bpamiri added a commit that referenced this pull request May 24, 2026
…check (#2807)

* feat(cli): underscore-references opt-in advisories in `wheels upgrade check`

Adds two advisory entries to the upgrade scanner, populating the
"Recommended Improvements" section introduced in PR3 with the first
concrete opt-in convention from the #2781 work.

## Advisories added

**1. t.references() opt-in suggestion** — fires when any migration file
under `app/migrator/migrations/` uses `t.references(`. Suggests setting
`set(useUnderscoreReferenceColumns=true)` to opt into `<name>_id` naming
matching Wheels model `belongsTo` defaults. Explicitly notes that
already-applied migrations are unaffected (only new migrations adopt
the new suffix), so users aren't alarmed about their existing schema.

**2. Mixed-convention warning** — fires when `config/` contains
`set(useUnderscoreReferenceColumns=true)`. Reminds the user that if
this flag was flipped after some migrations were already applied, the
database still has `<name>id` columns alongside any new `<name>_id`
columns. Suggests writing a data migration for full consistency.
Informational only — read once, ignore if N/A.

Both advisories use `severity: "advisory"`, surfacing in the cyan
"Recommended Improvements" section. They run regardless of major-version
bump (same-major upgrades fire them too). Exit code stays 0.

## Tests

`cli/lucli/tests/specs/commands/UpgradeAdvisorySpec.cfc` — 5 source-level
specs verifying both advisory entries are present and reference the
correct patterns, paths, and fix-message phrasing.

CLI suite: 591 pass / 5 fail (same 5 pre-existing failures unrelated
to upgrade).

## Scope (4 of 4 PRs from the #2781 audit)

- ✅ #2802 — t.references() at TableDefinition (closes #2781)
- ✅ #2804 — Migration.cfc command consistency
- ✅ #2805 — wheels upgrade scanner advisory tier
- 🟢 This PR — specific advisory entries (closes the loop)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

* fix(cli): address Reviewer A round-1 — advisory false-positive + comment-stripping

PR4 round-1 review (Reviewer A) raised two real correctness issues
plus a missing CHANGELOG entry.

## Correctness

**1. Advisory ##1 fired even when the flag was already set.** New apps
generated by `wheels new` ship with `useUnderscoreReferenceColumns=true`
by default (from PR1), so the "opt in to underscore" advisory would
contradict the mixed-convention warning on every fresh app. The check-
struct schema doesn't support multi-condition logic natively, so this
adds a pre-check on `config/settings.cfm` before appending advisory ##1
to the checks array. If the flag is already set, the advisory is
suppressed.

**2. The grep loop didn't strip CFML comments (Anti-Pattern ##14).** A
commented-out `// t.references(...)` or `// set(useUnderscoreReference
Columns=true);` would trip the pattern match. Both advisories ##1 and ##2
were affected — and so were all existing breaking-change checks. The
shared grep loop now wraps `fileRead(filePath)` with
`stripCfmlComments()` before `listToArray`. Multi-line block comments
collapse and may shift reported line numbers — same tradeoff the
existing `stripCfmlComments` callers at Module.cfc:970 and :5532
already accept.

The pre-check guard on advisory ##1 also routes through
`stripCfmlComments` so a commented-out `// set(...)` doesn't satisfy
the guard and suppress a legitimate advisory.

## Tests

`cli/lucli/tests/specs/commands/UpgradeAdvisorySpec.cfc` gains a new
"false-positive guards" describe block with three source-level
assertions:
- `underscoreFlagAlreadySet` guard variable + the `if
  (!underscoreFlagAlreadySet)` conditional append
- `stripCfmlComments(fileRead(filePath))` in the grep loop
- `stripCfmlComments(fileRead(settingsFile))` in the pre-check

## Docs

CHANGELOG.md `[Unreleased] / Added` gains an entry for the two
advisory checks, the suppression logic, and the framework-wide
comment-stripping improvement.

## Verification

`bash tools/test-cli-local.sh`: 594 pass (3 new tests added), 5 fail
(same pre-existing failures unrelated to upgrade).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

* fix(cli): drop dead len() wrapper from underscore-flag guard

Reviewer A round-2 caught a real bug they introduced in round-1's
suggestion (graciously acknowledged): `len(reFindNoCase(...))` is
always truthy because reFindNoCase returns an integer position
(0 = no match, >= 1 = match), and len() coerces the integer to a
string and measures the digit count — len(0)=1, len(25)=2, both
truthy.

Result: the `underscoreFlagAlreadySet` guard was dead. Advisory #1
was never appended for any app whose config/settings.cfm exists
(which is every Wheels app). The opt-in suggestion never reached
the users it was meant to reach.

## Fix

Drop the len() wrapper and use `> 0` directly on reFindNoCase:

  reFindNoCase(pattern, content) > 0

Unambiguous boolean: 0 > 0 is false, pos > 0 is true.

Added an inline comment documenting the gotcha so a future
maintainer doesn't reinstate the buggy wrapping.

## Regression test

`UpgradeAdvisorySpec.cfc::does not wrap reFindNoCase with len() —
that pattern is always truthy` — source-level assertion that the
buggy pattern doesn't sneak back in. Pure structural guardrail
appropriate for the constraints (Module.cfc can't be instantiated
under TestBox).

## Verification

`bash tools/test-cli-local.sh`: 595 pass (1 new regression-guard
test), 5 pre-existing failures unrelated to upgrade.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

---------

Signed-off-by: Peter Amiri <peter@alurium.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bpamiri added a commit that referenced this pull request May 27, 2026
…leased] (#2816)

Entries in [Unreleased] cited issue numbers (or earlier stacked-PR
numbers) rather than their merge-PR numbers, and six entries carried no
reference at all. Correct 18 references to the actual merge PRs and add
the five missing user-facing entries: #2814 (native apt/yum repos),
#2804 (Migration.cfc command alias sweep), #2813 (skip spurious commit
after rollback in migrateIndividual), and #2777 / #2801 (Linux install
URL and migrator-table doc fixes).

No existing prose changed beyond the trailing reference, and nothing was
misfiled in the 4.0.1 section (verified by grep). Internal-only
CI/chore/blog PRs (#2797, #2772, #2771, #2770) and the user-invisible
bucket rename (#2788) are intentionally omitted.

Signed-off-by: Peter Amiri <peter@alurium.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bpamiri bpamiri mentioned this pull request May 27, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrator: t.references() API quirks — 'referenceNames' (not 'columnNames'), and produces '<x>id' without underscore

1 participant