Skip to content

feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty - #2967

Merged
bpamiri merged 5 commits into
developfrom
fix/bot-2963-roadmap-high-impact-cli-mcp-tooling-honesty-gaps-f
Jun 10, 2026
Merged

feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty#2967
bpamiri merged 5 commits into
developfrom
fix/bot-2963-roadmap-high-impact-cli-mcp-tooling-honesty-gaps-f

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Address the two high-impact tooling-honesty gaps from the wave-2 framework review (§5.2): CLI failure exit codes and MCP tool input schemas. Three focused changes, all under cli/lucli/:

  • wheels upgrade check --strict — escalates advisory (Recommended Improvements) findings to the same Wheels.UpgradeCheckFailed hard-fail path as breaking findings, so CI can gate on opt-in convention changes. Without --strict, advisories continue to report-and-pass. Mirrors Django --fail-level WARNING / Mix --warnings-as-errors.
  • ArgSpec.toInputSchema() — derives a JSON-Schema-compatible {type:"object", properties, required, additionalProperties:false} envelope from a command's declared positionals / flags / options. positional() / flag() / option() accept an optional description arg that flows into each property. One source of truth for CLI parsing and MCP tool discovery (FastMCP / Symfony JsonDescriptor pattern from the research comment).
  • mcpHiddenTools() structural sweep — appends every $-prefixed PUBLIC function discovered via getMetaData(this) to the hidden list. Defense-in-depth: a future $publicHelper cannot leak as a callable MCP tool even if the literal denylist is not updated.

Fixes #2963

Recommended path from research: #2963 (comment)

Related Issue

Closes #2963

Type of Change

  • New feature
  • Enhancement to existing feature

Feature Completeness Checklist

  • DCO sign-offSigned-off-by: trailer present on the commit
  • TestsArgSpecSpec (8 new it's for toInputSchema()), UpgradeCommandSpec (5 new it's for --strict), new McpHiddenToolsSpec (4 it's for the structural $-prefix sweep). All red→green in TDD order.
  • Framework Docs — handled separately by bot-update-docs.yml
  • AI Reference Docs — handled separately by bot-update-docs.yml
  • CLAUDE.md — handled separately by bot-update-docs.yml
  • CHANGELOG.md — entries added under [Unreleased] (Added + Fixed)
  • Test runner passescurl /wheels/cli/tests?format=json returns totalPass: 811, totalFail: 0, totalError: 0 after the implementation. Failing baseline (before implementing) showed the new specs red with the expected error messages (has no function with name [toInputSchema], getMetaData(this) was not found, strict was not found in parseUpgradeArgs).

Test Plan

  • curl /wheels/cli/tests?format=json — full suite green (811 / 0 / 0)
  • wheels upgrade check --strict on an app with only advisory findings — confirm non-zero exit and Wheels.UpgradeCheckFailed
  • wheels upgrade check (no --strict) on the same app — confirm exit 0 (current behavior preserved)
  • Inspect any subcommand's ArgSpec → toInputSchema() output to verify the JSON-Schema envelope matches MCP tools/list[].inputSchema expectations

Notes for reviewers

This PR is the minimum surface area that demonstrates both gaps end-to-end:

  • The wider audit of test/migrate/validate exit codes from the issue body is intentionally out of scope here (#2890 already covered the CLI generally and upgrade check already throws on breaking findings; this PR adds the missing --strict gate for advisories).
  • The mcpToolSpecs() registry — wiring toInputSchema() into every per-tool MCP advertisement — is a natural follow-up once the foundational schema-emit primitive lands here.

…ling honesty

Address the two high-impact tooling-honesty gaps from the wave-2 framework
review (§5.2): CLI failure exit codes and MCP tool input schemas.

* `wheels upgrade check --strict` escalates advisory ("Recommended
  Improvements") findings to the same `Wheels.UpgradeCheckFailed` hard-fail
  path as breaking findings, so CI pipelines can gate on opt-in convention
  changes. Without `--strict`, advisories continue to report-and-pass.
  Mirrors Django `--fail-level WARNING` / Mix `--warnings-as-errors`.
* `ArgSpec.toInputSchema()` derives a JSON-Schema-compatible
  `{type:"object", properties, required, additionalProperties:false}`
  envelope from a command's declared positionals / flags / options.
  Type mapping follows ArgSpec coercion (string / number / boolean).
  `positional()` / `flag()` / `option()` accept an optional `description`
  argument that flows into each emitted property. One source of truth for
  CLI parsing and MCP tool discovery (FastMCP / Symfony JsonDescriptor
  pattern from the research comment).
* `mcpHiddenTools()` structurally appends every `$`-prefixed PUBLIC
  function discovered via `getMetaData(this)`, defense-in-depth so a
  future `$publicHelper` cannot leak as a callable MCP tool even if the
  literal denylist is not updated.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
… guides (#2963)

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

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Docs updated

Added a doc commit to this PR:

  • web/sites/guides/src/content/docs/v4-0-0/command-line-tools/wheels-commands/upgrade.mdx — updated synopsis, exit-status description, and flags table to document the new --strict flag
  • web/sites/guides/src/content/docs/v4-0-0/command-line-tools/mcp-integration.mdx — added bullet to "Tools deliberately hidden" noting that $-prefixed internal helpers are now structurally excluded via getMetaData(this)

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

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: The three changes are well-structured and the test coverage is solid. One correctness gap deserves a fix before merge: combining --format=json with --strict produces inconsistent output — the JSON document reports "success": true while the process exits non-zero, which silently misleads any CI pipeline that parses stdout. Verdict: request changes on that finding; the rest are nits.


Correctness

--format=json --strictsuccess field contradicts exit code

cli/lucli/Module.cfc around line 4570–4647:

// JSON mode emits the document before the throw guards
if (jsonMode) {
    out(serializeJSON({
        ...
        "success": arrayLen(issues) == 0,   // ← only checks breaking issues
        "advisories": advisories,
        ...
    }));
}

// strict throw fires AFTER the JSON was already emitted
if (arguments.strict && arrayLen(advisories)) {
    throw(
        type = "Wheels.UpgradeCheckFailed",
        message = "Upgrade check found #arrayLen(advisories)# advisory finding(s) and --strict is set..."
    );
}

When a user runs wheels upgrade check --strict --format=json on an app that has only advisory findings:

  1. JSON stdout: {"success": true, "breaking": [], "advisories": [...]} — process hasn't thrown yet.
  2. Process exit: non-zero (the throw fires).

A CI script that checks $? sees a failure; a script that parses stdout | jq .success sees true. The two signals are inconsistent and the bug is silent — the strictest consumer (exit code) wins at the shell level, but any automation that trusts the JSON document is misled.

The help banner now lists both flags as combinable ([--to=<version>] [--strict] [--format=json]), so this combination is an advertised path.

Suggested fix — gate "success" on both issues and the strict-advisory case:

var strictAdvisoryFail = arguments.strict && arrayLen(advisories) > 0;
if (jsonMode) {
    out(serializeJSON({
        "currentVersion": currentVersion,
        "targetVersion": target,
        "success": arrayLen(issues) == 0 && !strictAdvisoryFail,
        "strict": arguments.strict,
        "breaking": issues,
        "advisories": advisories,
        "passed": passed,
        "guide": guideUrl
    }));
}

Adding the "strict" key to the JSON document also lets downstream consumers understand why the command exited non-zero without inspecting stderr or documentation.


Tests

No test for --format=json --strict interaction

cli/lucli/tests/specs/commands/UpgradeCommandSpec.cfc

The five new UpgradeCommandSpec tests exercise the source-level structure of strict mode but none covers the JSON output path. A test along these lines would catch the success inconsistency described above:

it("emits success=false in JSON mode when strict finds advisories (no breaking)", () => {
    // The jsonMode branch's success expression must account for strict + advisories,
    // not just issues alone.
    var jsonBlockIdx = reFindNoCase("jsonMode", variables.moduleSource);
    var jsonBlock = mid(variables.moduleSource, jsonBlockIdx, 600);
    expect(jsonBlock).toInclude("strict");
});

Docs

CHANGELOG "Added" entry for toInputSchema() overstates availability

CHANGELOG.md (the new "Added" bullet):

services/ArgSpec.toInputSchema() derives a JSON-Schema-compatible {type:"object", …} envelope … so MCP tool input schemas can be generated from the same typed declaration the CLI parser already consumes.

The method exists and the primitive works, but it has zero callers in this PR — the mcpToolSpecs() wiring is explicitly deferred to a follow-up (noted in the PR description). The changelog entry reads as if MCP tool schemas are now being emitted, which is not the case yet. Suggest adding a parenthetical: "Foundation for per-tool MCP input schemas; wiring into tools/list is a follow-up (#2963)."

Help banner trailing-period inconsistency (minor nit)

cli/lucli/Module.cfc around line 2819:

& "  --to=<version>    Target Wheels version (default: latest stable)" & nl   // no period
& "  --format=json     Emit a machine-readable JSON report" & nl               // no period
& "  --strict          Treat advisory findings (recommended improvements) as failures." & nl  // period

The --strict description ends with a period; the existing --to and --format lines do not. Pick one style for the section.


Commits

Single commit, title feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty — 82 chars, valid type/scope, not ALL-CAPS. Passes commitlint. DCO trailer present.


Summary

The --format=json --strict inconsistency is the only item requiring a code change. The test gap is closely related (the fix would expose the untested path). The CHANGELOG and banner nits can land with the fix or as a follow-up. Everything else — the structural $-prefix sweep in mcpHiddenTools(), the ArgSpec description threading, and the toInputSchema() type mapping — looks correct and well-tested.

@wheels-bot

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review is substantively correct. The primary finding — --format=json --strict produces a contradictory document — is verified: line 4524-4531 emits "success": arrayLen(issues) == 0 before the strict throw fires (line ~4633 in the diff), so with only advisory findings the JSON reports success: true while the process exits non-zero. A's suggested fix (pre-compute strictAdvisoryFail and gate success on both conditions) addresses the root cause correctly. I'd add one partial miss and one weakness in A's proposed test.

Sycophancy

None detected. A requested changes, not an approval, and anchored every finding to specific lines.

False positives

None detected. I read the JSON emit block at line 4524 and the new strict throw in the diff. The ordering is exactly as A described: JSON flushes first, throw fires second. A's claim is accurate.

Missed issues

  • Docs commit not reviewed. A's review marker is wheels-bot:review-a:2967:76cf6cf9298b1da263f47b78a96a911688af09b4 — the first (implementation) commit. The second commit (83520df, the current head) was pushed after A's review and is outside A's scope. The docs changes themselves look correct and consistent with the implementation (upgrade.mdx accurately documents the new flag; mcp-integration.mdx accurately describes the structural sweep). However, once the success-field bug is fixed, the upgrade.mdx paragraph that lists the JSON fields (currentVersion, targetVersion, success, …) should clarify that success is false when strict mode escalates advisories — right now it only says the non-zero exit still applies, not that success reflects it. Minor, but the docs should not lag the code fix.

  • A's suggested test is too loose. A proposes windowing 600 chars from the first jsonMode reference and checking toInclude("strict"). That window would pass even if strict appears only in a comment or in the surrounding if (arguments.strict) check unrelated to the success expression. A tighter source-level assertion — e.g. looking for !strictAdvisoryFail or && !arguments.strict inside the serializeJSON({ block specifically — would be more defensive. This doesn't invalidate the finding, but A should sharpen the test suggestion before bot-address-review.yml authors it.

Verdict alignment

A's request-changes verdict is consistent with identifying a real correctness bug that is advertised on the combined-flag help path.

Convergence

Aligned. A correctly identified the one blocking issue (json/strict success inconsistency) and the closely related test gap. No false positives to retract, and the code fix A proposed is the right one. My two additions (docs follow-up after the fix, tighter test) are incremental refinements, not blockers on the consensus. Joint recommendation: fix success to account for strictAdvisoryFail, add a source-level test that checks the serializeJSON block specifically, and update the upgrade.mdx success-field description in the same commit.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

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: The new commit (83520df) adds guide documentation for --strict and the $-prefix MCP exclusion — the docs are accurate and clear. However, the main correctness finding from the previous review round (the success field in JSON mode contradicts the exit code when --strict is active with advisory-only findings) remains unfixed in the implementation, and the new docs inadvertently make it more visible without flagging it. The test gap for the JSON + strict path also remains. Verdict: request changes on the unfixed correctness issue; everything else is a nit. (Submitting as --comment because GitHub does not allow --request-changes on self-authored PRs.)


Correctness

--format=json --strictsuccess field still contradicts exit code (unresolved from round 1)

cli/lucli/Module.cfc, around line 4573 (unchanged in this PR):

if (jsonMode) {
    out(serializeJSON({
        "currentVersion": currentVersion,
        "targetVersion": target,
        "success": arrayLen(issues) == 0,   // only checks breaking findings
        "breaking": issues,
        "advisories": advisories,
        ...
    }));
}

// strict throw fires AFTER the JSON document has already been emitted
if (arguments.strict && arrayLen(advisories)) {
    throw(type = "Wheels.UpgradeCheckFailed", message = "...");
}

When a project has only advisory findings and the user runs wheels upgrade check --strict --format=json:

  1. JSON stdout: {"success": true, ...}arrayLen(issues) == 0 is true, no breaking findings.
  2. Process exit: non-zero — the strict throw fires after the JSON is emitted.

A CI script that parses stdout | jq .success sees true; $? sees failure. The two signals are inconsistent. The previous review proposed the minimal fix:

var strictAdvisoryFail = arguments.strict && arrayLen(advisories) > 0;
if (jsonMode) {
    out(serializeJSON({
        "currentVersion": currentVersion,
        "targetVersion": target,
        "success": arrayLen(issues) == 0 && !strictAdvisoryFail,
        "strict": arguments.strict,
        "breaking": issues,
        "advisories": advisories,
        "passed": passed,
        "guide": guideUrl
    }));
}

Adding "strict": arguments.strict also lets consumers understand why success is false when breaking is empty.

Docs compound the issue

web/sites/guides/src/content/docs/v4-0-0/command-line-tools/wheels-commands/upgrade.mdx, line 590 (added in 83520df):

With --format=json the human report is replaced by a single JSON document (currentVersion, targetVersion, success, breaking, advisories, passed, guide) — the non-zero exit on breaking or strict-mode advisory findings still applies.

The updated text correctly describes the exit code, but listing success in the field inventory without noting that it only reflects breaking findings (not strict-mode advisories) will mislead a reader who reaches for jq .success in a --strict pipeline. Once the code is fixed, the docs text becomes fully accurate; until then, this sentence makes the inconsistency more findable for users.


Tests

Still no test for the --format=json --strict interaction

cli/lucli/tests/specs/commands/UpgradeCommandSpec.cfc

The five new --strict tests are source-level structural checks (does the function accept strict? does it forward it? does the throw reference Wheels.UpgradeCheckFailed?). None verifies the JSON output path. A test along these lines would catch the success inconsistency described above:

it("emits success=false in JSON mode when --strict escalates advisories", () => {
    var jsonBlockIdx = reFindNoCase("jsonMode", variables.moduleSource);
    var jsonBlock = mid(variables.moduleSource, jsonBlockIdx, 800);
    expect(reFindNoCase("success.*strict|strictAdvisoryFail", jsonBlock)).toBeGT(0);
});

Docs

The new documentation added in 83520df is otherwise accurate:

  • mcp-integration.mdx — the new bullet correctly describes the structural $-prefix exclusion behavior.
  • upgrade.mdx — the synopsis, exit status paragraph, and flags table all correctly describe --strict semantics for the exit code. The gap is limited to the success field as noted above.

Minor nit — help banner trailing-period inconsistency (carried from round 1, still present)

cli/lucli/Module.cfc, around line 2819:

& "  --to=<version>    Target Wheels version (default: latest stable)" & nl   // no period
& "  --format=json     Emit a machine-readable JSON report" & nl               // no period
& "  --strict          Treat advisory findings (recommended improvements) as failures." & nl  // period

The --strict line ends with a period; --to and --format do not. Pick one style for the section.

Minor nit — CHANGELOG overstates toInputSchema() availability (carried from round 1, still present)

CHANGELOG.md, "Added" section:

services/ArgSpec.toInputSchema() ... so MCP tool input schemas can be generated from the same typed declaration the CLI parser already consumes.

The method is implemented and tested, but the wiring into mcpToolSpecs() / tools/list is explicitly deferred (PR description: "a natural follow-up"). The changelog reads as if MCP clients can already discover per-tool parameter schemas. Suggest appending: "Foundation for per-tool MCP input schemas; wiring into tools/list is a follow-up (#2963)."


Commits

SHA Message Status
76cf6cf feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty Valid — 82 chars, DCO present
83520df docs(web/guides): add --strict flag and $-prefix MCP exclusion to CLI guides (#2963) Valid — 83 chars, DCO present

Both pass commitlint. No concerns.


Summary

The docs commit is a good-faith response to the round-1 review and is directionally correct. The blocking issue is that the success field in JSON mode remains inconsistent with the exit code when --strict is combined with advisory-only findings — the fix is a one-line change to the success expression in the jsonMode block plus a new "strict" key. Once that lands, the docs text becomes fully accurate and the CHANGELOG nit becomes a polish item.

- Module.cfc: precompute strictAdvisoryFail and gate the JSON
  document's `success` field on (issues == 0) AND not strict-advisory
  fail, so `jq .success` and `$?` stop disagreeing when
  --strict --format=json runs on an app with advisory-only findings.
  Echo `strict` back in the JSON body so consumers can explain a
  success=false document with empty breaking[]. (A correctness,
  rounds 1+2; B confirmed.)
- Module.cfc: drop the trailing period from the --strict help-banner
  line so the section matches --to / --format style. (A nit,
  rounds 1+2.)
- UpgradeCommandSpec.cfc: two new source-level it's pinning the
  `serializeJSON({` block — one asserts `success` is wired through
  `strictAdvisoryFail`, the other asserts the `strict` field is
  echoed back. Tighter than the round-1 600-char window suggestion
  (B's refinement). (A test gap; B refinement.)
- upgrade.mdx: extend the JSON-mode description to list the new
  `strict` field and clarify that `success` tracks the exit code
  precisely (including the strict + advisory-only case), so the
  docs don't lag the fix. (B missed-issue.)
- CHANGELOG.md: re-word the toInputSchema entry to flag it as the
  foundation only — wiring into tools/list is the follow-up — and
  extend the --strict entry with the JSON success/strict gating
  detail. (A nit, rounds 1+2.)

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

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Address Review (round 1)

Applied the consensus findings from Reviewer A (rounds 1 + 2) and Reviewer B (round 1 — wheels-bot:converged-changes:2967:83520df6f95d7d714cb86104c52ddd8d8b75d479):

  • cli/lucli/Module.cfc (the blocking correctness bug) — precompute strictAdvisoryFail = arguments.strict && arrayLen(advisories) > 0 above the jsonMode branch and gate the JSON document's success field on arrayLen(issues) == 0 && !strictAdvisoryFail. jq .success and $? now agree under --strict --format=json with advisory-only findings. Also echo "strict": arguments.strict in the JSON body so a success: false document with empty breaking[] no longer looks like a data inconsistency. (A's primary finding, both rounds; B confirmed and tagged the same fix.)
  • cli/lucli/Module.cfc — drop the trailing period from the --strict help-banner line so the Options section matches --to / --format style. (A nit, both rounds.)
  • cli/lucli/tests/specs/commands/UpgradeCommandSpec.cfc — two new source-level its:
    • gates the JSON 'success' field on strict + advisories, not just breaking issues — asserts strictAdvisoryFail is present AND its consumption is windowed inside the out(serializeJSON({ ... })) block (tighter than the round-1 600-char window from the jsonMode first reference; addresses B's "test is too loose" refinement).
    • includes the 'strict' flag in the JSON document so consumers can explain a non-zero exit — pins "strict": arguments.strict inside the same serializeJSON({ block.
  • web/sites/guides/src/content/docs/v4-0-0/command-line-tools/wheels-commands/upgrade.mdx — extend the JSON-mode paragraph to list the new strict field in the document inventory and to clarify that success tracks the exit code precisely (including the --strict + advisory-only case). Addresses B's missed-issue: "once the code is fixed, the upgrade.mdx paragraph that lists the JSON fields should clarify that success is false when strict mode escalates advisories."
  • CHANGELOG.md — re-word the toInputSchema() "Added" entry to make it explicit that this is the foundation only and that wiring into tools/list is a follow-up; extend the --strict entry with the JSON success/strict-gating detail so the changelog matches the new behavior. (A's CHANGELOG-overstates-availability nit, both rounds.)

Test re-run: I could not execute bash tools/test-cli-local.sh from this sandbox session — the command is denied by the address-review permission gate (bash tools/test-cli-local.sh and direct wheels --version both return "command requires approval"). The two new specs are pure source-level structural checks against variables.moduleSource, matching the rest of UpgradeCommandSpec.cfc's shape, and the regexes were spot-verified against the post-edit source (the strictAdvisoryFail symbol, the "strict": arguments.strict literal, and the success.{0,80}strictAdvisoryFail window all land inside the 600-char serializeJSON window). CI on the new SHA will be the authoritative confirmation.

No findings were intentionally skipped — every item from A's two rounds and B's convergence is addressed in the commit above.

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 Author

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: The new commit (3d73f245) resolves every blocking and advisory finding from review rounds 1 and 2. The main correctness issue — success in JSON mode disagreeing with the process exit code when --strict is active with advisory-only findings — is fixed correctly. Tests are in place, docs are accurate, and the CHANGELOG is properly scoped. Verdict: approve (submitted as --comment; GitHub disallows self-approval).


Correctness

Round-1/2 blocking issue — resolved.

cli/lucli/Module.cfc, line 4570–4580:

var strictAdvisoryFail = arguments.strict && arrayLen(advisories) > 0;

// JSON mode block:
"success": arrayLen(issues) == 0 && !strictAdvisoryFail,
"strict": arguments.strict,

strictAdvisoryFail is precomputed BEFORE the jsonMode block, so the emitted document is always consistent with the exit code. The strict key is echoed back so a consumer seeing success: false with empty breaking[] can distinguish "strict escalation" from a data inconsistency. All three scenarios are now coherent:

Inputs success exit
breaking findings false non-zero
strict + advisories, no breaking false non-zero
no breaking, strict off or no advisories true zero

Tests

Two new source-level it blocks in cli/lucli/tests/specs/commands/UpgradeCommandSpec.cfc:

  • "gates the JSON success field on strict + advisories" — windows the serializeJSON block and asserts success.{0,80}strictAdvisoryFail appears, so a future rewrite that forgets the gate fails the spec immediately.
  • "includes the strict flag in the JSON document" — asserts "strict": arguments.strict appears in the same window.

Pattern matches the existing source-level spec approach (Module.cfc's BaseModule dependency makes live instantiation infeasible in TestBox) and is tighter than the 600-char window suggested in round 1.


Docs

web/sites/guides/src/content/docs/v4-0-0/command-line-tools/wheels-commands/upgrade.mdx:

The exit-status paragraph now lists strict in the JSON field inventory and explicitly states that success tracks the exit code precisely — including the --strict + advisory-only case. Previously this gap made the docs complicit in the bug.

The flags table row for --format=json is updated to note that advisory findings also gate when --strict is set.


Commits

SHA Message Status
76cf6cf feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty Valid — 82 chars, DCO present
83520df docs(web/guides): add --strict flag and $-prefix MCP exclusion to CLI guides (#2963) Valid — 83 chars, DCO present
3d73f24 fix(cli): address Reviewer A/B consensus findings (round 1) Valid — 57 chars, DCO present

All three pass commitlint. No concerns.


Minor nit (non-blocking, carry-forward resolved)

Help-banner continuation line (cli/lucli/Module.cfc, line 2823):

  --strict          Treat advisory findings (recommended improvements) as failures
                    Useful for CI — opt-in convention changes will gate the build.

The trailing period on the continuation line is fine — it closes the description sentence. The original nit (the first line had a period while --to/--format had none) is resolved.


Summary

All items from rounds 1 and 2 are fully addressed. The code change is minimal and targeted (4 lines in the production path, 2 new test assertions, doc text), the fix is correct, and the tests pin it against regression. No outstanding findings remain.

@wheels-bot

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review of the address-review commit is accurate and clean. The primary correctness fix is verified: strictAdvisoryFail is computed before the jsonMode JSON emit, so jq .success and $? agree under all three input combinations A's table describes. No missed issues, no false positives.

Sycophancy

None detected. A submitted as --comment (GitHub disallows self-approval), stated "approve" with an evidence trail, and did not wave hands at any section.

False positives

None detected. I read the diff at each cited location. The strictAdvisoryFail precomputation, the "strict": arguments.strict echo, the test windows over the serializeJSON block, and the docs paragraph update are all exactly as A described.

Missed issues

None detected. Cross-engine risks are minimal: getMetaData(this) in mcpHiddenTools() is guarded with a structKeyExists check and a catch fallback, covering engines where functions may be absent or structured differently. The $toSchemaProperty private function in ArgSpec.cfc is a plain service component (not a mixin), so the "private mixin functions are not integrated" invariant does not apply. All three commit headers are valid conventional-commit types within 100 chars.

Verdict alignment

A's approve verdict is consistent with the findings — every blocking and advisory item from rounds 1 and 2 has been addressed with minimal, targeted changes.

Convergence

Aligned. A correctly describes the fix, the tests, and the docs state. Nothing remains outstanding. Joint recommendation: approve and merge.

@wheels-bot

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Address Review (round 2)

Re-invoked on head 3d73f245967d55882e06e2476d45f2154ae1adb6, but the convergence verdict at this SHA is approve, not changes. After round-1 address-review committed 3d73f245, both reviewers re-ran on the new SHA and aligned on approve:

  • Reviewer A on 3d73f245 (review marker review-a:2967:3d73f245967d55882e06e2476d45f2154ae1adb6): "All items from rounds 1 and 2 are fully addressed. The code change is minimal and targeted (4 lines in the production path, 2 new test assertions, doc text), the fix is correct, and the tests pin it against regression. No outstanding findings remain."
  • Reviewer B on 3d73f245 (review marker review-b:2967:3d73f245967d55882e06e2476d45f2154ae1adb6:1 + approve-convergence marker converged-approve:2967:3d73f245967d55882e06e2476d45f2154ae1adb6): "Aligned. A correctly describes the fix, the tests, and the docs state. Nothing remains outstanding. Joint recommendation: approve and merge."

Re-checking each round-1 consensus finding against the current HEAD confirms the fix is in place and unchanged since round-1 address-review:

  • cli/lucli/Module.cfc:4570strictAdvisoryFail precomputed above the jsonMode branch.
  • cli/lucli/Module.cfc:4579 — JSON document success gated on arrayLen(issues) == 0 && !strictAdvisoryFail.
  • cli/lucli/Module.cfc:4580"strict": arguments.strict echoed in the JSON body.
  • cli/lucli/Module.cfc:2822 — trailing period dropped from the --strict help-banner line.
  • cli/lucli/tests/specs/commands/UpgradeCommandSpec.cfc:121-147 — the two new source-level it blocks pin strictAdvisoryFail AND windowed success.{0,80}strictAdvisoryFail inside the out(serializeJSON({ block, plus the "strict": arguments.strict echo.
  • web/sites/guides/src/content/docs/v4-0-0/command-line-tools/wheels-commands/upgrade.mdx:36 — JSON-mode paragraph lists the strict field in the document inventory and clarifies that success tracks the exit code precisely (including the --strict + advisory-only case).
  • CHANGELOG.md:25-26--strict entry documents the JSON success / strict gating; toInputSchema() entry flagged as the foundation only, with tools/list wiring as the follow-up.

No code changes were applied this round. git status against HEAD is clean (the only modified path is lucee.json, the CI environment fixture written by the workflow itself, not part of this PR's scope). No new commit will be pushed; the head stays at 3d73f245967d55882e06e2476d45f2154ae1adb6.

The convergence loop has reached an approve verdict at this SHA. Handing back for a human merge — outer-loop count is 2, well below the 5-round cap.

(Diagnostic for the next bot-tooling sweep: this round-2 invocation appears to be re-entry caused by the round-1 address-review comment body literally quoting the prior changes-convergence marker text. The bot-address-review.yml if: predicate matches that substring anywhere in a wheels-bot comment body, so the round-1 comment re-fires the workflow even though the actionable trigger has been resolved. A minor predicate tightening — e.g. checking the marker is in an HTML comment, or scoping the substring match — would prevent this no-op round. Not worth a code change in this PR.)

@bpamiri
bpamiri marked this pull request as ready for review June 10, 2026 12:20
…oling-honesty-gaps-f

Resolve CHANGELOG.md conflict by keeping both Unreleased Fixed bullets
(this PR's ##2963 mcpHiddenTools entry and develop's ##2954 middleware
caching entry from PR ##2964).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

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 covers a single new commit since the round-3 approval: a merge of origin/develop into the feature branch (7e37fed23). All substantive findings from rounds 1–3 remain resolved. The only file changed in the merge itself is CHANGELOG.md, where the conflict was resolved correctly by keeping both this PR's bullets and PR #2964's middleware-caching bullet. Verdict: approve (submitted as --comment; GitHub disallows self-approval on author-opened PRs).


Correctness

Merge conflict resolution — CHANGELOG.md — correct.

CHANGELOG.md, merge commit 7e37fed23:

Both this branch and origin/develop added entries to [Unreleased] ### Fixed. The resolution keeps both:

  • This PR's three bullets (--strict, ArgSpec.toInputSchema(), mcpHiddenTools() structural sweep).
  • PR #2964's middleware-caching bullet.

No entry was dropped and the sections are in correct order. No concern.

Incoming develop changes — orthogonal.

git show confirms the merge commit changed only CHANGELOG.md. The PR #2964 changes (vendor/wheels/Dispatch.cfc, deploy service CFCs, test specs, web docs) arrived cleanly via the merge but are already on develop, so GitHub's PR diff correctly excludes them. There is no overlap between PR #2964's files and this PR's own diff (cli/lucli/Module.cfc, ArgSpec.cfc, three test specs, two guide pages).


Commits

SHA Message Status
76cf6cf92 feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty Valid — 82 chars, DCO present
83520df6f docs(web/guides): add --strict flag and $-prefix MCP exclusion to CLI guides (#2963) Valid — 83 chars, DCO present
3d73f2459 fix(cli): address Reviewer A/B consensus findings (round 1) Valid — 57 chars, DCO present
7e37fed23 Merge origin/develop into fix/bot-2963-... Merge commit — exempt from conventional-commit format. Signed-off-by: Peter Amiri (human merge author) present.

The squash-merge commit that lands on develop will use the PR title (feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty, 82 chars, type feat, scope cli) — valid.


Summary

No outstanding findings. All items from rounds 1–3 remain resolved. The merge commit is a clean integration of develop's unrelated improvements; no new correctness, compatibility, security, or test gaps were introduced by the merge.

…oling-honesty-gaps-f

Resolve CHANGELOG.md conflict: keep this PR's Added entries and develop's
##2955 Performance entry (from PR ##2965) in the same Unreleased section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

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 covers the single new commit since the round-4 approval: a merge of origin/develop (396488a8f) that brings in PR #2965 (URLFor controller/action lookup caching). All substantive findings from rounds 1–4 remain resolved. The CHANGELOG conflict was resolved correctly. No new correctness, cross-engine, security, or test gaps were introduced. Verdict: approve (submitted as --comment; GitHub disallows self-approval on author-opened PRs).


Correctness

Merge conflict resolution — CHANGELOG.md — correct.

CHANGELOG.md, merge commit 396488a8f:

Both this branch and origin/develop added entries to the [Unreleased] section. The resolution keeps all three logical groups:

  • This PR's ### Added bullets (--strict, ArgSpec.toInputSchema()).
  • PR #2965's ### Performance entry (URLFor caching).
  • The ### Fixed section, which now carries entries from both sides: this PR's mcpHiddenTools() structural sweep bullet plus the pre-existing dispatch, job, and onlyProvides() entries from develop.

No entry was dropped; section order (AddedPerformanceFixed) is logical and consistent with the existing changelog structure. No concern.

Incoming develop changes — orthogonal.

git show 396488a8f --stat confirms the merge commit touched four files: CHANGELOG.md (conflict resolution), vendor/wheels/Global.cfc, vendor/wheels/Mapper.cfc, and vendor/wheels/tests/specs/global/urlforSpec.cfc. The latter three are PR #2965's URLFor-caching changes, which arrived on develop as c51b28973 before this merge. They have zero overlap with this PR's own diff (cli/lucli/Module.cfc, ArgSpec.cfc, three test specs, two guide pages). GitHub's PR diff correctly excludes them.


Commits

SHA Message Status
76cf6cf92 feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty Valid — 82 chars, DCO present
83520df6f docs(web/guides): add --strict flag and $-prefix MCP exclusion to CLI guides (#2963) Valid — 83 chars, DCO present
3d73f2459 fix(cli): address Reviewer A/B consensus findings (round 1) Valid — 57 chars, DCO present
7e37fed23 Merge origin/develop into fix/bot-2963-... Merge commit — exempt from conventional-commit format. DCO: Signed-off-by: Peter Amiri (human merge author)
c51b28973 perf(router): cache URLFor controller/action lookups in app scope with negative caching (#2965) Landed on develop — arrives via merge. DCO: Signed-off-by: claude[bot] matches the commit's author identity.
396488a8f Merge origin/develop into fix/bot-2963-... Merge commit — exempt from conventional-commit format. DCO: Signed-off-by: Peter Amiri (human merge author)

The squash-merge commit that lands on develop will use the PR title (feat(cli): upgrade check --strict + ArgSpec toInputSchema for MCP tooling honesty, 82 chars, type feat, scope cli) — valid.


Summary

No outstanding findings. All items from rounds 1–4 remain resolved. The merge commit is a clean integration of develop's unrelated URLFor-caching improvement; no new correctness, compatibility, security, or test gaps were introduced by the merge.

@wheels-bot

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's round-4 review is substantively sound: the conflict resolution in CHANGELOG.md is correct, no entries were dropped, the four commits pass commitlint, and the claim that this PR's file surface (Module.cfc, ArgSpec.cfc, test specs, two guide pages) has no overlap with PR #2964's files is accurate. One factual inaccuracy in how A characterised the merge commit is worth flagging, though it does not change the conclusion.

Sycophancy

None detected. A's round-4 review does real work — it checks the conflict resolution, the commit table, and the cross-branch overlap claim.

False positives

A's claim: "git show confirms the merge commit changed only CHANGELOG.md."

This does not match what the command actually returns. Running git show --stat 7e37fed23c2e33186295c0f23f8d755a91f86d84 shows 15 files changed (517 insertions, 53 deletions): CHANGELOG.md, CLAUDE.md, README.md, cli/README.md, cli/lucli/services/deploy/cli/DeploySecretsCli.cfc, cli/lucli/services/deploy/lib/SecretResolver.cfc, two deploy test specs, vendor/wheels/Dispatch.cfc, RouteMiddlewareLifecycleSpec.cfc, and five web-guide pages. git diff 3d73f2459 7e37fed23c2e33186295c0f23f8d755a91f86d84 --name-only returns the same 15 files.

A was almost certainly reasoning from the combined-diff patch view (git show without --stat), which in combined-diff mode suppresses files whose merge result equals either parent. Under that view, only CHANGELOG.md — the single conflicted file — appears in the patch. The other 14 files arrived verbatim from P2 (develop, e77ec94) and are correctly excluded from the PR diff by GitHub because they were already on develop. A's conclusion is therefore correct: from the PR diff's perspective, the only new thing to review in round 4 is the CHANGELOG.md conflict resolution. But the supporting sentence ("git show confirms...") is factually wrong as stated and would mislead anyone who ran git show --stat themselves and saw 15 files.

Missed issues

None. The 14 develop-side files in the merge stat are from PRs already landed on develop (Dispatch.cfc from #2964, SecretResolver.cfc from #2951) and are outside this PR's scope. A's orthogonality claim holds up. No cross-engine concerns apply here — cli/lucli/ runs under LuCLI (Lucee-based) only, so the getMetaData(this) structural sweep and arrayContainsNoCase calls in Module.cfc are not cross-engine risks. The try/catch around the reflection path is a sound fallback.

Verdict alignment

A's verdict (approve, submitted as --comment because GitHub disallows self-approval) is consistent with a clean four-round review where all substantive findings were resolved by 3d73f2459.

Convergence

Aligned, no further changes needed. A's factual error about the merge stat does not affect any downstream action — the CHANGELOG.md conflict resolution was correctly verified, and the PR diff is sound. Rounds 1–3 covered the substantive changes. The joint recommendation is approve.

@wheels-bot

wheels-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's round-5 review is clean. I verified each factual claim against the actual repo state and found no sycophancy, no false positives, and no missed issues on the merge commit.

Sycophancy

None detected. A's approve is backed by specific evidence: the merge commit stat (4 files, all named), CHANGELOG conflict verification (all three logical groups present, none dropped), and a commit-table DCO pass for every SHA. No unsupported LGTM.

False positives

None detected. I ran the same checks A cited:

  • git show 396488a8f --stat confirms exactly the 4 files A listed: CHANGELOG.md, vendor/wheels/Global.cfc, vendor/wheels/Mapper.cfc, vendor/wheels/tests/specs/global/urlforSpec.cfc.
  • The combined diff (diff --cc) confirms the ### Added section came from the branch side, ### Performance from develop (c51b28973), and the mcpHiddenTools fixed bullet from the branch — no entry dropped. A's characterisation of the conflict resolution is accurate.
  • DCO trailers verified on all three bot commits (claude[bot] <41898282+claude[bot]@users.noreply.github.com>), both human merge commits (Peter Amiri), and the develop through-commit (c51b28973 — previously validated when perf(router): cache URLFor controller/action lookups in app scope with negative caching #2965 merged).

Missed issues

None detected. The three non-CHANGELOG files in the merge are exclusively URLFor-caching code (#2965) with zero overlap with this PR's diff (cli/lucli/ and guide pages). A correctly scoped the round-5 review to the merge commit and did not need to re-audit the substantive changes already passed in rounds 1-4.

Verdict alignment

A's --comment (not --approve) is the correct GitHub mechanics for a bot that opened the PR. The underlying verdict of approve is consistent with the findings: no outstanding issues, clean merge commit.

Convergence

Aligned. The merge commit is a correct integration of an orthogonal develop improvement. All prior round findings remain resolved. Joint recommendation: approve.

@bpamiri
bpamiri merged commit ee2b2f2 into develop Jun 10, 2026
15 checks passed
@bpamiri
bpamiri deleted the fix/bot-2963-roadmap-high-impact-cli-mcp-tooling-honesty-gaps-f branch June 10, 2026 12:38
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.

roadmap: high-impact CLI/MCP tooling-honesty gaps — failure exit codes (incl. upgrade check) + MCP tool input schemas

1 participant