Skip to content

Replace hardcoded /animatedarchitecture with dynamic primary command name#1165

Merged
PimvanderLoos merged 19 commits intomasterfrom
copilot/replace-hardcoded-commands
Apr 12, 2026
Merged

Replace hardcoded /animatedarchitecture with dynamic primary command name#1165
PimvanderLoos merged 19 commits intomasterfrom
copilot/replace-hardcoded-commands

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Nov 2, 2025

Plan: Replace hardcoded commands with primary command name

Based on the issue requirements:

  • Add primaryCommandName() method to IConfig interface
  • Add String primaryCommandName field to GeneralSectionSpigot.Result record
  • Implement logic to extract primary command name from command aliases in GeneralSectionSpigot
  • Update Info.java to use primaryCommandName instead of hardcoded /animatedarchitecture
  • Update Creator.java to use primaryCommandName instead of hardcoded /animatedarchitecture
  • Update DelayedStructureSpecificationInputRequest.java to use primaryCommandName instead of hardcoded /animatedarchitecture
  • Update CommandManager.java (Spigot) to use primaryCommandName instead of hardcoded /animatedarchitecture
  • Add command decorator logic to handle leading / for players vs console (implemented in ICommandSender.formatCommand())
  • Add IConfig to ToolUser.Context and Creator
  • Update tests in animatedarchitecture-core package for new functionality
  • Address code review feedback
  • Validate all changes work correctly
  • Rebase on master (again with copilot-setup-steps fix)
  • Fix compilation errors
  • Fix ErrorProne and Checkstyle violations
  • Refactor formatCommand with @FormatMethod annotation per review feedback
  • Update formatCommand signature: command name first, subcommand format second, args third

Summary

Successfully implemented all requested changes from the latest code review:

  1. Refactored formatCommand method signature:

    • First parameter: command name (e.g., "animatedarchitecture")
    • Second parameter: subcommand format string (e.g., "setopenstatus %s %d true")
    • Third parameter: varargs for format arguments
    • Implementations in IPlayer and IServer now concatenate command name with formatted subcommand
  2. Imported annotations instead of using fully qualified names:

    • Added imports for @FormatMethod, @CheckReturnValue, @FormatString from ErrorProne
    • Added import for @Nullable from JSpecify
    • Cleaner, more readable code
  3. Updated all usages throughout the codebase:

    • Info.java: 3 methods updated to pass command name separately
    • Creator.java: 7 locations updated with new signature
    • DelayedStructureSpecificationInputRequest.java: Updated to use new signature
  4. Updated tests to reflect new signature:

    • WrappedPlayerTest: Updated to test with command name and subcommand format
    • SpigotServerTest: Updated to test with command name and subcommand format

All static analysis checks pass with errorprone profile.

Original prompt

This section details on the original issue you should resolve

<issue_title>Replace hardcoded commands with primary command</issue_title>
<issue_description>There are currently several places where we assume /animatedarchitecture to be the 'main' command. See, for example, Info.java.

There are 2 issues with this approach:

  1. Clickable text components will break if /animatedarchitecture is not registered as a command.
  2. Users may be misled by showing them a command that may not exist or may not be the primary command.

To resolve this issue, we should add a field String primaryCommandName to the GeneralSectionSpigot#Result class, and a method String primaryCommandName to IConfig.
All hardcoded instances of /animatedarchitecture should then be replaced by primaryCommandName.

Additionally, we should look into the receiver of the command and decorate it appropriately (e.g., have a leading / for players, but not for the console).</issue_description>

<agent_instructions>Also update tests in the animatedarchitecture-core package. Use test name format methodToTest_whatToTest, for example: setPropertyValue_shouldSetRequiredFromPreviousValue, or setPropertyValue_shouldThrowExceptionWhenPreviousValueIsOfIncorrectType. The test itself should have // setup, // execute, and // verify. Execute and verify may be collapsed in one block // execute & verify. Use AssertJ and Mockito. Do not use lenient mode for mocks.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Replace hardcoded commands with primary command Replace hardcoded /animatedarchitecture with dynamic primary command name Nov 2, 2025
Copilot AI requested a review from PimvanderLoos November 2, 2025 14:30
@PimvanderLoos
Copy link
Copy Markdown
Owner

@copilot Please rebase on master and try to run it again. Firewall rules blocked me from connecting to one or more addresses (expand for details) should be fixed now.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 2, 2025

@PimvanderLoos Thanks for sending me some feedback. Unfortunately, I hit an error while trying to use the custom Copilot setup steps configured for this repository. The error I am seeing is:

No `copilot-setup-steps` job found in your `copilot-setup-steps.yml` workflow file. Please ensure you have a single job named `copilot-setup-steps`. For more details, see https://gh.io/copilot/actions-setup-steps.

Once you or someone with the necessary access fixes the problem, please let me know in a comment and I'll try again. Thanks!

@PimvanderLoos
Copy link
Copy Markdown
Owner

@copilot Please rebase the commit on master

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 2, 2025

@PimvanderLoos Thanks for sending me some feedback. Unfortunately, I hit an error while trying to use the custom Copilot setup steps configured for this repository. The error I am seeing is:

No `copilot-setup-steps` job found in your `copilot-setup-steps.yml` workflow file. Please ensure you have a single job named `copilot-setup-steps`. For more details, see https://gh.io/copilot/actions-setup-steps.

Once you or someone with the necessary access fixes the problem, please let me know in a comment and I'll try again. Thanks!

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 2, 2025

@copilot Please rebase the commit on master

Rebased on master (39e8248). The branch now includes the copilot-setup-steps.yml workflow and all changes have been preserved.

@PimvanderLoos
Copy link
Copy Markdown
Owner

@copilot Please rebase on master again, and then try to run it again.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 2, 2025

@copilot Please rebase on master again, and then try to run it again.

Rebased on latest master (a1d52ae). The branch now includes the copilot-setup-steps fix and is ready for CI/CD validation.

@PimvanderLoos
Copy link
Copy Markdown
Owner

@copilot Compilation failed. Can you fix the compilation errors and make sure that the CI/CD pipeline succeeds?

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 2, 2025

@copilot Compilation failed. Can you fix the compilation errors and make sure that the CI/CD pipeline succeeds?

Fixed compilation errors in commit f195019. The issue was that test files needed to be updated to include the new IConfig parameter in ToolUser.Context constructor. All tests now pass except for one pre-existing failure (StructureTypeLoaderIntegrationTest) unrelated to these changes.

@PimvanderLoos
Copy link
Copy Markdown
Owner

@copilot There is still an ErrorProne failure. Use mvn -P=errorprone checkstyle:checkstyle pmd:check verify to run all static analysis tools.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 3, 2025

@copilot There is still an ErrorProne failure. Use mvn -P=errorprone checkstyle:checkstyle pmd:check verify to run all static analysis tools.

Fixed all static analysis violations in commit 4bbbca4:

  • Split long line in Info.java (was 126 chars, now properly formatted)
  • Removed unused import in ICommandSenderTest.java
  • Removed unused mock field in ICommandSenderTest.java

All checks now pass with mvn -P=errorprone checkstyle:checkstyle pmd:check verify.

@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 3, 2025

@PimvanderLoos PimvanderLoos marked this pull request as ready for review November 4, 2025 17:49
Copilot AI review requested due to automatic review settings November 4, 2025 17:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.


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

@PimvanderLoos PimvanderLoos added this to the 1.0.0 milestone Dec 2, 2025
@PimvanderLoos PimvanderLoos force-pushed the copilot/replace-hardcoded-commands branch from fb8c099 to cf83584 Compare April 12, 2026 12:38
@PimvanderLoos PimvanderLoos requested a review from Copilot April 12, 2026 12:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.


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

Comment on lines 186 to 190
Mockito.mock(StructureActivityManager.class),
commandFactory,
assistedStepFactory
assistedStepFactory,
Mockito.mock(IConfig.class)
);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

ToolUser.Context is being constructed with a new Mockito.mock(IConfig.class) instance instead of the existing config mock field used throughout the test setup. This means stubbing on config (e.g., limits, primaryCommandName(), etc.) won't apply to the ToolUser/Creator code path and can lead to null command names in formatted commands. Pass the already-initialized config mock into the context (and stub primaryCommandName() if needed).

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +25
@Mock
private IPlayer player;

@Test
void formatCommand_shouldAddLeadingSlashForPlayers()
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

This test is named WrappedPlayerTest but the new formatCommand_* tests are exercising the IPlayer interface default method via a Mockito mock, not the WrappedPlayer implementation. Consider instantiating WrappedPlayer (with mocked Player/ILocalizer/ITextFactory) or renaming/moving the test to reflect that it's testing IPlayer#formatCommand rather than WrappedPlayer.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +32
// setup
when(player.formatCommand("animatedarchitecture", "help")).thenCallRealMethod();

// execute & verify
assertThat(player.formatCommand("animatedarchitecture", "help"))
.isEqualTo("/animatedarchitecture help");
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

Using when(player.formatCommand(...)).thenCallRealMethod() per invocation is brittle (any argument mismatch returns the mock default) and obscures what's being tested. If the goal is to test the default implementation, prefer creating the mock with CALLS_REAL_METHODS (or use a real WrappedPlayer instance) so the test doesn't depend on per-call stubbing.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +19
@Mock
private IServer server;

@Test
void formatCommand_shouldNotAddLeadingSlashForServer()
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

SpigotServerTest currently mocks IServer and calls the interface default method, so it doesn't actually validate SpigotServer behavior. Consider constructing a real SpigotServer (with mocked ILocalizer/ITextFactory) or renaming/moving the test to target IServer#formatCommand explicitly.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +54

import java.util.List;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

class GeneralSectionSpigotTest
{
@Test
void result_shouldSetPrimaryCommandNameFromFirstAlias()
{
// setup
final List<String> aliases = List.of("animatedarchitecture", "aa", "aarch");

// execute
final GeneralSectionSpigot.Result result = new GeneralSectionSpigot.Result(
Set.of(),
true,
aliases,
aliases.getFirst()
);

// verify
assertThat(result.primaryCommandName()).isEqualTo("animatedarchitecture");
}

@Test
void result_shouldSetPrimaryCommandNameFromSingleAlias()
{
// setup
final List<String> aliases = List.of("aa");

// execute
final GeneralSectionSpigot.Result result = new GeneralSectionSpigot.Result(
Set.of(),
true,
aliases,
aliases.getFirst()
);

// verify
assertThat(result.primaryCommandName()).isEqualTo("aa");
}

@Test
void defaultResult_shouldHaveCorrectPrimaryCommandName()
{
// execute & verify
assertThat(GeneralSectionSpigot.Result.DEFAULT.primaryCommandName())
.isEqualTo("animatedarchitecture");
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

These tests pass primaryCommandName into the Result constructor directly (using aliases.getFirst()), so they don't verify the new logic in GeneralSectionSpigot#getResult that derives the primary command from the aliases list / default. Consider building a ConfigurationNode with aliases set (including an empty list case) and asserting getResult(...).primaryCommandName() to cover the actual extraction behavior introduced in this PR.

Suggested change
import java.util.List;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
class GeneralSectionSpigotTest
{
@Test
void result_shouldSetPrimaryCommandNameFromFirstAlias()
{
// setup
final List<String> aliases = List.of("animatedarchitecture", "aa", "aarch");
// execute
final GeneralSectionSpigot.Result result = new GeneralSectionSpigot.Result(
Set.of(),
true,
aliases,
aliases.getFirst()
);
// verify
assertThat(result.primaryCommandName()).isEqualTo("animatedarchitecture");
}
@Test
void result_shouldSetPrimaryCommandNameFromSingleAlias()
{
// setup
final List<String> aliases = List.of("aa");
// execute
final GeneralSectionSpigot.Result result = new GeneralSectionSpigot.Result(
Set.of(),
true,
aliases,
aliases.getFirst()
);
// verify
assertThat(result.primaryCommandName()).isEqualTo("aa");
}
@Test
void defaultResult_shouldHaveCorrectPrimaryCommandName()
{
// execute & verify
assertThat(GeneralSectionSpigot.Result.DEFAULT.primaryCommandName())
.isEqualTo("animatedarchitecture");
}
import org.spongepowered.configurate.BasicConfigurationNode;
import org.spongepowered.configurate.ConfigurationNode;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class GeneralSectionSpigotTest
{
@Test
void getResult_shouldSetPrimaryCommandNameFromFirstAlias()
{
// setup
final List<String> aliases = List.of("animatedarchitecture", "aa", "aarch");
// execute
final GeneralSectionSpigot.Result result = getResult(aliases);
// verify
assertThat(result.primaryCommandName()).isEqualTo("animatedarchitecture");
}
@Test
void getResult_shouldSetPrimaryCommandNameFromSingleAlias()
{
// setup
final List<String> aliases = List.of("aa");
// execute
final GeneralSectionSpigot.Result result = getResult(aliases);
// verify
assertThat(result.primaryCommandName()).isEqualTo("aa");
}
@Test
void getResult_shouldUseDefaultPrimaryCommandNameWhenAliasesAreEmpty()
{
// execute
final GeneralSectionSpigot.Result result = getResult(List.of());
// verify
assertThat(result.primaryCommandName()).isEqualTo("animatedarchitecture");
}
@Test
void defaultResult_shouldHaveCorrectPrimaryCommandName()
{
// execute & verify
assertThat(GeneralSectionSpigot.Result.DEFAULT.primaryCommandName())
.isEqualTo("animatedarchitecture");
}
private GeneralSectionSpigot.Result getResult(List<String> aliases)
{
final ConfigurationNode node = BasicConfigurationNode.root();
node.node("commandAliases").raw(aliases);
return new GeneralSectionSpigot().getResult(node);
}

Copilot uses AI. Check for mistakes.
Comment on lines 194 to 198
final MinecraftHelp<ICommandSender> minecraftHelp = new MinecraftHelp<>(
"/animatedarchitecture help",
"/" + config.primaryCommandName() + " help",
sender -> this.bukkitAudiences.sender(PlayerFactorySpigot.unwrapCommandSender(sender)),
manager
);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

MinecraftHelp is initialized with a hardcoded leading '/' (now dynamic command name, but still always player-style). Since this PR introduces ICommandSender#formatCommand specifically to decorate commands for players vs console, consider constructing the help root command using sender.formatCommand(config.primaryCommandName(), "help") (e.g., by instantiating MinecraftHelp inside the handler) so console output doesn't suggest a slash-prefixed command. Also, this class still contains hardcoded command strings like /AnimatedArchitecture confirm (confirmation prompt) that likely should be switched to primaryCommandName()/formatCommand(...) for consistency with the PR goal.

Copilot uses AI. Check for mistakes.
dependabot Bot and others added 19 commits April 12, 2026 16:28
Bumps [net.sourceforge.pmd:pmd-core](https://github.com/pmd/pmd) from 7.20.0 to 7.22.0.
- [Release notes](https://github.com/pmd/pmd/releases)
- [Commits](pmd/pmd@pmd_releases/7.20.0...pmd_releases/7.22.0)

---
updated-dependencies:
- dependency-name: net.sourceforge.pmd:pmd-core
  dependency-version: 7.22.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps org.apache.logging.log4j:log4j-core from 2.25.3 to 2.25.4.

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-core
  dependency-version: 2.25.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: PimvanderLoos <3114723+PimvanderLoos@users.noreply.github.com>
Co-authored-by: PimvanderLoos <3114723+PimvanderLoos@users.noreply.github.com>
Co-authored-by: PimvanderLoos <3114723+PimvanderLoos@users.noreply.github.com>
…ser.Context

Co-authored-by: PimvanderLoos <3114723+PimvanderLoos@users.noreply.github.com>
Co-authored-by: PimvanderLoos <3114723+PimvanderLoos@users.noreply.github.com>
…tion

Co-authored-by: PimvanderLoos <3114723+PimvanderLoos@users.noreply.github.com>
…ormat and args

Co-authored-by: PimvanderLoos <3114723+PimvanderLoos@users.noreply.github.com>
Replace hardcoded '/AnimatedArchitecture confirm' with
sender.formatCommand(config.primaryCommandName(), "confirm") so the
confirmation clickable respects the configured command alias and
correctly applies the slash/no-slash rule for players vs. console.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the detached Mockito.mock(IConfig.class) in the ToolUser.Context
constructor with the shared @mock config field. This ensures any stubbing
(e.g. primaryCommandName()) set up by subclasses is visible to the context
and prevents latent null-return bugs when config methods are invoked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…od tests

Replace per-invocation thenCallRealMethod() with CALLS_REAL_METHODS as the
mock's default answer for IPlayer and IServer. This is less brittle, matches
the pattern already used in CreatorTestsUtil, and removes the need for setup
stubs. Also adds a trailing-space test for the empty-subcommand edge case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Compute primaryCommandName from commandAliases in the compact constructor
rather than trusting the caller-supplied value. This makes the invariant
self-enforcing: an empty alias list falls back to the first default alias,
and a non-empty list always uses aliases.getFirst(), regardless of what
was passed as the fourth constructor argument.

Also adds a test covering the empty-aliases fallback path that was
previously untested.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When subCommandFormat is an empty string, String.format returns "" and
the previous implementation produced a trailing space (e.g.
"/animatedarchitecture "). Now the formatted subcommand is only appended
when non-empty, keeping the output clean for both IPlayer and IServer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@PimvanderLoos PimvanderLoos force-pushed the copilot/replace-hardcoded-commands branch from cf83584 to a612f8e Compare April 12, 2026 14:37
@PimvanderLoos PimvanderLoos merged commit 99aa7da into master Apr 12, 2026
11 checks passed
@PimvanderLoos PimvanderLoos deleted the copilot/replace-hardcoded-commands branch April 12, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace hardcoded commands with primary command

3 participants