Skip to content

feat(j19): replace arcane_help tool with help action/subaction routing#5

Merged
jmagar merged 12 commits intomainfrom
bd-arcane-mcp-j19/help-system
Apr 6, 2026
Merged

feat(j19): replace arcane_help tool with help action/subaction routing#5
jmagar merged 12 commits intomainfrom
bd-arcane-mcp-j19/help-system

Conversation

@jmagar
Copy link
Copy Markdown
Owner

@jmagar jmagar commented Apr 5, 2026

Summary

  • Removes the standalone arcane_help MCP tool
  • Adds action=help to the arcane tool for top-level help
  • Adds subaction=help to all 11 domains for per-domain docs with full params/examples
  • Adds CI drift test ensuring help text stays in sync with the action enum

Beads

  • arcane-mcp-j19.1: Create dispatchHelp content module
  • arcane-mcp-j19.2: Wire help into arcane tool schema + routing

Breaking Change

arcane_help tool is removed. Use arcane action=help instead.

Testing

  • 272 tests pass (267 existing + 5 new help routing tests)
  • CI drift test: asserts all 11 domain names appear in top-level help output
  • npm run typecheck clean

Usage after this change

  • Top-level: arcane action=help
  • Domain-specific: arcane action=container subaction=help
  • Domain hint: arcane action=help subaction=container

Summary by cubic

Replaced the arcane_help tool with built‑in help routing on arcane, added CI hardening, and patched high‑severity vite CVEs. Breaking change: use arcane action=help or action=<domain> subaction=help. Satisfies J19.

  • New Features

    • Added action=help and subaction=help across all 11 domains with full params/examples; introduced dispatchHelp plus drift and routing tests.
    • CI hardening: added gitleaks/gitleaks-action@v2 secret scan and a block-env-commits pre-commit hook to block .env* files (except .env.example).
  • Refactors

    • Moved scripts to bin/, updated docs and CI/Justfile references; SessionStart hook now installs deps in CLAUDE_PLUGIN_DATA when package.json changes.
    • Help schema: subaction is optional for action=help and accepts domain names; exported HELP_DOMAINS to dedupe domain lists; moved smoke test to bin/smoke-test.ts.
    • Dependencies: bumped vite to 8.0.5 to resolve CVEs and fixed the lockfile to preserve @emnapi/core and @emnapi/runtime; synced versions to 1.2.0 in package.json and .claude-plugin/plugin.json.

Written for commit 0ed553e. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added top-level help (action=help) and domain-scoped help (action= subaction=help).
  • Breaking Changes

    • Removed the standalone arcane_help tool; use arcane action=help instead.
  • Tests

    • Added routing and dispatcher tests for help behavior.
  • Documentation

    • Updated hook and script references to the new bin/ layout.
  • Chores

    • Bumped release to 1.2.0, added commit-time .env blocker, adjusted pre-commit hook, removed obsolete ensure-ignore utility, and added a secret-scan CI job.

Copilot AI review requested due to automatic review settings April 5, 2026 23:28
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

Integrates help into the arcane MCP tool (top-level and domain-scoped), removes the standalone arcane_help tool, adds a dispatchHelp module with full and per-domain Markdown, bumps version to 1.2.0, and moves several hook/script paths into bin/ (plus related docs/tests updates).

Changes

Cohort / File(s) Summary
Release & Package Metadata
CHANGELOG.md, package.json, .claude-plugin/plugin.json
Bumped package and plugin versions to 1.2.0; added changelog entry describing integrated help and removal of arcane_help.
Arcane tool & dispatch
src/mcp/tools/arcane.ts, src/mcp/tools/dispatch/help.ts, src/mcp/tools/dispatch/index.ts
Removed standalone arcane_help; extended arcane to accept action=help and domain-scoped subaction; added dispatchHelp(domain?) and exported HELP_DOMAINS.
Tests
tests/mcp/tools/arcane.test.ts, tests/mcp/tools/dispatch/dispatchers.test.ts
Added tests for top-level and domain-scoped help routing, dispatchHelp outputs, unknown-domain validation, and CI coverage drift for domains.
Hooks & hook scripts
hooks/hooks.json, hooks/scripts/ensure-ignore-files.sh (removed), hooks/*, bin/*
Removed old hooks/scripts/ensure-ignore-files.sh; replaced some hook-script invocations with a new package.json sync/install command in hooks/hooks.json; moved documented hook/script paths to bin/.
Bin scripts & tooling
bin/block-env-commits.sh, bin/check-*.sh, bin/smoke-test.ts, bin/CLAUDE.md
Added bin/block-env-commits.sh; updated script header/docs to reference bin/; added bin/CLAUDE.md guidance.
Pre-commit & CI
.pre-commit-config.yaml, .github/workflows/ci.yml, Justfile
Added local pre-commit hook to block .env commits; CI/Justfile steps updated to run scripts from bin/; CI gains a gitleaks secret-scan job.
Docs & inventory
docs/*, hooks/CLAUDE.md, bin/CLAUDE.md, CLAUDE.md
Updated documentation to reference bin/ paths and added docs describing bin/ and hooks/ subtree usage.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ArcaneHandler as Arcane Handler
    participant DispatchHelp as dispatchHelp()
    participant Response

    Client->>ArcaneHandler: Request (action=help OR action=<domain>, subaction=help)
    ArcaneHandler->>ArcaneHandler: Validate request, compute optional domain hint
    ArcaneHandler->>DispatchHelp: dispatchHelp(domain?)
    DispatchHelp->>DispatchHelp: Lookup domain or return full help markdown
    DispatchHelp-->>ArcaneHandler: Return help text
    ArcaneHandler-->>Response: Wrap into MCP response
    Response-->>Client: Deliver help content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
A hop to the docs, a nibble of cheer,
Help tucked in arcs for domains far and near,
Pages aligned, scripts set to bin,
The rabbit reads, then gives a grin,
Hooray for tidy trails we steer.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: replacing the standalone arcane_help tool with help action/subaction routing in the arcane tool.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bd-arcane-mcp-j19/help-system

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

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

This PR replaces the standalone arcane_help MCP tool with a unified help action/subaction routing inside the existing arcane tool, centralizing and expanding help/docs output across all Arcane domains.

Changes:

  • Removed the arcane_help tool and introduced arcane action=help plus subaction=help per domain.
  • Added a new dispatchHelp() dispatcher providing top-level and per-domain documentation strings.
  • Added/updated tests to cover help routing and to detect domain/help drift in CI.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/mcp/tools/dispatch/dispatchers.test.ts Adds unit tests for dispatchHelp() output and basic drift checking.
tests/mcp/tools/arcane.test.ts Adds integration-style tests asserting help routing behavior through the arcane tool handler.
src/mcp/tools/dispatch/index.ts Re-exports the new dispatchHelp dispatcher.
src/mcp/tools/dispatch/help.ts Introduces the new help text module and dispatchHelp(domain?) routing.
src/mcp/tools/arcane.ts Removes arcane_help registration; wires help into schema and routing logic for arcane.
package.json Bumps package version to 1.2.0.
CHANGELOG.md Documents the help routing change and notes the removal of arcane_help as breaking.
Comments suppressed due to low confidence (1)

src/mcp/tools/arcane.ts:91

  • The published tool schema still requires subaction and restricts it to the operation enum. That conflicts with the documented/advertised usage arcane action=help (no subaction) and arcane action=help subaction=container (domain hint), which schema-driven MCP clients may reject or not suggest. Consider making subaction optional and/or expanding the schema to allow domain names when action === "help" (or adding a dedicated domain field for the hint), then keep the existing runtime guard for non-help actions.
      subaction: z
        .enum([
          "list",
          "get",
          "create",
          "update",

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

Comment on lines +576 to +581
export function dispatchHelp(domain?: string): string {
if (!domain || domain === "all") return HELP_FULL;
const help = DOMAIN_HELP[domain];
if (!help)
throw new ValidationError(
`Unknown domain '${domain}'. Valid domains: environment, project, container, image, network, volume, system, image-update, vulnerability, registry, gitops`,
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

dispatchHelp() duplicates the valid-domain list in the error message as a hardcoded string, separate from DOMAIN_HELP keys. This will drift the next time a domain is added/renamed. Consider generating the list from Object.keys(DOMAIN_HELP) (and possibly sorting it) so the thrown message always matches the actual supported domains.

Suggested change
export function dispatchHelp(domain?: string): string {
if (!domain || domain === "all") return HELP_FULL;
const help = DOMAIN_HELP[domain];
if (!help)
throw new ValidationError(
`Unknown domain '${domain}'. Valid domains: environment, project, container, image, network, volume, system, image-update, vulnerability, registry, gitops`,
const VALID_DOMAINS = Object.keys(DOMAIN_HELP).sort().join(", ");
export function dispatchHelp(domain?: string): string {
if (!domain || domain === "all") return HELP_FULL;
const help = DOMAIN_HELP[domain];
if (!help)
throw new ValidationError(
`Unknown domain '${domain}'. Valid domains: ${VALID_DOMAINS}`,

Copilot uses AI. Check for mistakes.
"name": "arcane-mcp",
"mcpName": "tv.tootie/arcane-mcp",
"version": "1.1.5",
"version": "1.2.0",
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

This release is explicitly marked as a breaking change (removal of arcane_help), but the package version is bumped from 1.1.5 to 1.2.0. Since the repo states it adheres to Semantic Versioning, this should be a major version bump (e.g., 2.0.0) and the changelog header should match.

Suggested change
"version": "1.2.0",
"version": "2.0.0",

Copilot uses AI. Check for mistakes.
Comment on lines +475 to +511
it("dispatchHelp(undefined) returns string containing all 11 domain names", () => {
const help = dispatchHelp(undefined);
expect(typeof help).toBe("string");
for (const domain of ALL_DOMAINS) {
expect(help).toContain(domain);
}
});

it('dispatchHelp("container") returns string containing stop, restart, create, params', () => {
const help = dispatchHelp("container");
expect(help).toContain("stop");
expect(help).toContain("restart");
expect(help).toContain("create");
expect(help).toContain("params");
});

it('dispatchHelp("registry") returns string containing "NOT environment-scoped"', () => {
const help = dispatchHelp("registry");
expect(help).toContain("NOT environment-scoped");
});

it('dispatchHelp("unknown-domain") throws ValidationError', () => {
expect(() => dispatchHelp("unknown-domain")).toThrow(ValidationError);
});

it('dispatchHelp("all") returns same result as dispatchHelp(undefined)', () => {
expect(dispatchHelp("all")).toBe(dispatchHelp(undefined));
});

it("CI drift test: each known domain appears in top-level help output", () => {
const fullHelp = dispatchHelp(undefined);
for (const domain of ALL_DOMAINS) {
expect(
fullHelp,
`Expected domain '${domain}' to appear in top-level help`,
).toContain(domain);
}
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

The last test ("CI drift test") re-checks that each domain appears in the top-level help output, which is already asserted by the earlier test "dispatchHelp(undefined) returns string containing all 11 domain names". Consider consolidating these to a single test to reduce duplication while keeping the better failure message.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ca244705a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"help",
])
.describe("Resource type to operate on"),
subaction: z
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow action=help without requiring a subaction

The new tool description and changelog instruct clients to call action=help, but subaction is still a required enum field in the declared input schema. In MCP clients that validate tool arguments against the schema (and in model tool planners constrained by that schema), {"action":"help"} is rejected before this handler runs, so the advertised top-level help path is unavailable after removing arcane_help.

Useful? React with 👍 / 👎.

Comment on lines 127 to 131
"delete-backup",
"status",
"sync",
"help",
])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Permit domain hints in action=help schema

The handler explicitly supports domain hints for help (action=help with subaction equal to a domain like container), but the subaction enum does not include any domain names. As a result, schema-validated MCP calls cannot provide those values, so this routing path is unreachable in real protocol usage despite being tested via direct handler invocation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 10-17: The markdown headings in CHANGELOG.md (e.g., "## [1.2.0] -
2026-04-05", "### Added", "### Removed") violate MD022 by not being surrounded
by blank lines; fix this by inserting a blank line before and after each heading
so each heading is separated from surrounding content (ensure there is an empty
line above "## [1.2.0] - 2026-04-05", an empty line between that H2 and the "###
Added" H3, and empty lines before and after "### Added" and "### Removed").

In `@package.json`:
- Line 4: The CI is failing due to a version mismatch: package.json has
"version": "1.2.0" while plugin.json still contains "version": "1.1.5"; open
plugin.json and update its top-level "version" field to "1.2.0" so both files
match (ensure you update the exact "version" property in plugin.json to mirror
package.json).

In `@src/mcp/tools/arcane.ts`:
- Around line 148-172: Extract the duplicated DOMAINS list into a single
exported constant in help.ts (e.g., export const VALID_DOMAINS) and update
arcane.ts to import and use that constant instead of defining DOMAINS locally;
specifically replace the local DOMAINS used in the help branch (the array
checked against subaction and passed to dispatchHelp) with the imported
VALID_DOMAINS so DOMAIN_HELP and the routing logic stay in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 18c34f61-32e4-4f9d-aba4-bfe65c71f277

📥 Commits

Reviewing files that changed from the base of the PR and between e8bffc5 and 3ca2447.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • package.json
  • src/mcp/tools/arcane.ts
  • src/mcp/tools/dispatch/help.ts
  • src/mcp/tools/dispatch/index.ts
  • tests/mcp/tools/arcane.test.ts
  • tests/mcp/tools/dispatch/dispatchers.test.ts

"name": "arcane-mcp",
"mcpName": "tv.tootie/arcane-mcp",
"version": "1.1.5",
"version": "1.2.0",
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.

⚠️ Potential issue | 🔴 Critical

Version mismatch with plugin.json causing CI failure.

The pipeline reports package.json=1.2.0 but plugin.json=1.1.5. Update plugin.json to 1.2.0 to resolve the version sync check failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 4, The CI is failing due to a version mismatch:
package.json has "version": "1.2.0" while plugin.json still contains "version":
"1.1.5"; open plugin.json and update its top-level "version" field to "1.2.0" so
both files match (ensure you update the exact "version" property in plugin.json
to mirror package.json).

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="package.json">

<violation number="1" location="package.json:4">
P2: This declares a breaking change but only applies a minor version bump. Use a major SemVer bump to avoid publishing a breaking API change as backward-compatible.</violation>
</file>

<file name="src/mcp/tools/dispatch/help.ts">

<violation number="1" location="src/mcp/tools/dispatch/help.ts:581">
P3: Generate the valid-domain portion of this error message from `DOMAIN_HELP` keys instead of hardcoding it. Keeping two independent domain lists here will drift and can return misleading validation guidance.</violation>
</file>

<file name="CHANGELOG.md">

<violation number="1" location="CHANGELOG.md:10">
P2: This release documents a breaking change but uses a minor version (`1.2.0`); bump to a major version to stay SemVer-compliant.</violation>
</file>

<file name="src/mcp/tools/arcane.ts">

<violation number="1" location="src/mcp/tools/arcane.ts:164">
P1: The new `action=help` routing is incompatible with the input schema: `subaction` is still required and does not allow domain names, so documented help calls are rejected before reaching this logic.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

"name": "arcane-mcp",
"mcpName": "tv.tootie/arcane-mcp",
"version": "1.1.5",
"version": "1.2.0",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

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

P2: This declares a breaking change but only applies a minor version bump. Use a major SemVer bump to avoid publishing a breaking API change as backward-compatible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 4:

<comment>This declares a breaking change but only applies a minor version bump. Use a major SemVer bump to avoid publishing a breaking API change as backward-compatible.</comment>

<file context>
@@ -1,7 +1,7 @@
   "name": "arcane-mcp",
   "mcpName": "tv.tootie/arcane-mcp",
-  "version": "1.1.5",
+  "version": "1.2.0",
   "description": "MCP server for the Arcane Docker management API",
   "type": "module",
</file context>
Suggested change
"version": "1.2.0",
"version": "2.0.0",
Fix with Cubic


## [Unreleased]

## [1.2.0] - 2026-04-05
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

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

P2: This release documents a breaking change but uses a minor version (1.2.0); bump to a major version to stay SemVer-compliant.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At CHANGELOG.md, line 10:

<comment>This release documents a breaking change but uses a minor version (`1.2.0`); bump to a major version to stay SemVer-compliant.</comment>

<file context>
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## [1.2.0] - 2026-04-05
+
+### Added
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/plugin/HOOKS.md (1)

8-14: ⚠️ Potential issue | 🟡 Minor

File location diagram is inconsistent with actual script locations.

The "File location" section still shows scripts under hooks/scripts/, but scripts have been moved to bin/. This contradicts the updated command paths in the JSON example below (lines 27, 32, 44).

📝 Proposed fix to update the file location diagram
 ## File location
 

hooks/
-├── hooks.json # Hook definitions
-└── scripts/

  • ├── sync-env.sh # Sync userConfig to .env
  • ├── fix-env-perms.sh # Fix .env permissions
  • └── ensure-ignore-files.sh # Verify ignore patterns
    +└── hooks.json # Hook definitions
    +bin/
    +├── sync-env.sh # Sync userConfig to .env
    +├── fix-env-perms.sh # Fix .env permissions
    +└── ensure-ignore-files.sh # Verify ignore patterns
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plugin/HOOKS.md` around lines 8 - 14, Update the "File location" diagram
in HOOKS.md so it matches the actual layout used by the examples: move the three
scripts (sync-env.sh, fix-env-perms.sh, ensure-ignore-files.sh) out of the old
scripts/ subtree into a top-level bin/ directory and leave hooks.json at the
hooks/ root; ensure the ASCII tree shows hooks/ containing hooks.json and a
separate bin/ directory containing the three script filenames to match the
command paths referenced in the JSON examples.
src/mcp/tools/arcane.ts (1)

87-132: ⚠️ Potential issue | 🔴 Critical

Help routing with domain hints is unreachable due to schema constraints.

The subaction enum (lines 87-132) lists only operation names (list, get, create, etc.) and help, but the help routing logic at lines 150-151 expects domain names (container, environment, project, etc.) to select domain-specific help. Documented calls like action=help subaction=container fail schema validation before reaching the handler.

The schema must include domain names in the subaction enum and mark it optional to support:

  1. action=help subaction=<domain> → domain-specific help (routed via line 150-151)
  2. action=<domain> subaction=help → domain-specific help (routed via line 158-161)
  3. action=help (no subaction) → full help (routed via line 150-151 with undefined domainHint)
Proposed fix

Add domain names (environment, project, container, image, network, volume, system, image-update, vulnerability, registry, gitops) to the subaction enum and make it optional with .optional() chained before .describe().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/tools/arcane.ts` around lines 87 - 132, The subaction z.enum
currently only contains operation names and prevents domain-hint help calls from
passing validation; update the subaction schema (the z.enum(...) assigned to
subaction) to include the domain names environment, project, container, image,
network, volume, system, image-update, vulnerability, registry, and gitops, and
chain .optional() before the existing .describe() so subaction can be omitted
(supporting action=help with no subaction) — keep the existing operation strings
as well so the same schema supports both action=<domain> subaction=help and
action=help subaction=<domain>.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/hooks.json`:
- Around line 9-11: The SessionStart hook in hooks.json now contains an inline
npm install command that syncs package.json and runs npm install (the command
string starting with diff -q "${CLAUDE_PLUGIN_ROOT}/package.json" ... npm
install), but this behavior isn't documented; update docs/plugin/HOOKS.md by
adding a SessionStart example entry that describes this npm install hook,
include the exact purpose and behavior (compare root vs data package.json, copy
when different, run npm install, and cleanup on failure), and show the command
string or an equivalent snippet and its timeout setting so the documentation
matches the hooks.json configuration.

In `@src/mcp/tools/arcane.ts`:
- Around line 69-70: Tests and docs still reference a removed tool named
arcane_help; restore compatibility by either reintroducing an alias named
"arcane_help" that delegates to the existing arcane tool implementation in
src/mcp/tools/arcane.ts (so existing behavior and help output remain identical),
or update tests and documentation to remove/replace references to arcane_help:
modify tests/test_live.sh to stop asserting / calling arcane_help (remove
protocol checks for arcane_help and the
test_arcane_help_http/test_arcane_help_stdio functions) and update
TEST_COVERAGE.md to reflect the current exposed tools list. Use the symbol
"arcane_help" to add the alias or update the test function names
test_arcane_help_http and test_arcane_help_stdio and the protocol assertions
accordingly.

In `@tests/mcp/tools/arcane.test.ts`:
- Around line 1048-1086: The current tests call the handler directly via
setupTool()/callTool(), bypassing MCP SDK Zod validation and letting invalid
subaction values (e.g., "container") slip through; add integration-style tests
that exercise the registered tool contract through the MCP SDK validation layer
instead of the raw handler so schema errors are surfaced. Specifically, create
tests that invoke the public tool entrypoint (the same function used by the SDK
registration) rather than the raw handler from setupTool, and include cases that
pass invalid subaction values (like "container") expecting validation errors;
reference the existing test utilities (setupTool and callTool) and HELP_DOMAINS
to ensure help routing behavior is still asserted but only after validating via
the SDK contract. Ensure the new tests assert that inputs failing the Zod schema
produce validation failures and that valid help queries still return the
expected domain/help text.

---

Outside diff comments:
In `@docs/plugin/HOOKS.md`:
- Around line 8-14: Update the "File location" diagram in HOOKS.md so it matches
the actual layout used by the examples: move the three scripts (sync-env.sh,
fix-env-perms.sh, ensure-ignore-files.sh) out of the old scripts/ subtree into a
top-level bin/ directory and leave hooks.json at the hooks/ root; ensure the
ASCII tree shows hooks/ containing hooks.json and a separate bin/ directory
containing the three script filenames to match the command paths referenced in
the JSON examples.

In `@src/mcp/tools/arcane.ts`:
- Around line 87-132: The subaction z.enum currently only contains operation
names and prevents domain-hint help calls from passing validation; update the
subaction schema (the z.enum(...) assigned to subaction) to include the domain
names environment, project, container, image, network, volume, system,
image-update, vulnerability, registry, and gitops, and chain .optional() before
the existing .describe() so subaction can be omitted (supporting action=help
with no subaction) — keep the existing operation strings as well so the same
schema supports both action=<domain> subaction=help and action=help
subaction=<domain>.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ee9d46d-3974-49c6-9d4c-b0abd38533c7

📥 Commits

Reviewing files that changed from the base of the PR and between 3ca2447 and 886941b.

📒 Files selected for processing (17)
  • bin/check-docker-security.sh
  • bin/check-no-baked-env.sh
  • bin/check-outdated-deps.sh
  • bin/ensure-ignore-files.sh
  • bin/fix-env-perms.sh
  • bin/smoke-test.ts
  • bin/sync-env.sh
  • docs/INVENTORY.md
  • docs/plugin/HOOKS.md
  • docs/repo/SCRIPTS.md
  • hooks/hooks.json
  • hooks/scripts/ensure-ignore-files.sh
  • src/mcp/tools/arcane.ts
  • src/mcp/tools/dispatch/help.ts
  • src/mcp/tools/dispatch/index.ts
  • tests/mcp/tools/arcane.test.ts
  • tests/mcp/tools/dispatch/dispatchers.test.ts
💤 Files with no reviewable changes (1)
  • hooks/scripts/ensure-ignore-files.sh

hooks/hooks.json Outdated
Comment on lines +9 to +11
"command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\"",
"timeout": 60
},
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if the inline npm install command is documented in HOOKS.md

echo "=== Checking if npm install hook is documented in HOOKS.md ==="
if grep -q "npm install" docs/plugin/HOOKS.md; then
  echo "✓ npm install is documented"
else
  echo "✗ npm install hook is NOT documented in HOOKS.md"
fi

echo ""
echo "=== Current SessionStart hooks in hooks.json ==="
cat hooks/hooks.json | grep -A 30 '"SessionStart"'

Repository: jmagar/arcane-mcp

Length of output: 1206


Update docs/plugin/HOOKS.md to document the new SessionStart npm install hook.

The hooks.json file now includes an inline npm install command in the SessionStart hooks (lines 9-11) that handles package.json synchronization. This command is not documented in docs/plugin/HOOKS.md, creating a documentation gap. Add the npm install hook and its logic to the SessionStart example in the documentation to keep it in sync with the actual configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/hooks.json` around lines 9 - 11, The SessionStart hook in hooks.json
now contains an inline npm install command that syncs package.json and runs npm
install (the command string starting with diff -q
"${CLAUDE_PLUGIN_ROOT}/package.json" ... npm install), but this behavior isn't
documented; update docs/plugin/HOOKS.md by adding a SessionStart example entry
that describes this npm install hook, include the exact purpose and behavior
(compare root vs data package.json, copy when different, run npm install, and
cleanup on failure), and show the command string or an equivalent snippet and
its timeout setting so the documentation matches the hooks.json configuration.

Comment on lines +69 to 70
"Orchestrate Arcane Docker resources. Manages environments, projects, containers, images, networks, volumes, system operations, image updates, vulnerabilities, registries, and GitOps syncs. For image-update:check: pass id (Arcane imageId) OR params.imageRef (image reference like nginx:latest) — not both. Run with action=help for top-level help, or action=<domain> subaction=help for per-domain docs.",
{
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n 'arcane_help' tests/test_live.sh tests/TEST_COVERAGE.md -C2

Repository: jmagar/arcane-mcp

Length of output: 7625


🏁 Script executed:

#!/bin/bash
# Check tool definitions and exports in arcane.ts
cd src/mcp/tools
wc -l arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 74


🏁 Script executed:

# Search for tool registration/definition patterns in arcane.ts
rg -n 'arcane_help|registerTool|export.*tool' src/mcp/tools/arcane.ts -A2 -B2

Repository: jmagar/arcane-mcp

Length of output: 43


🏁 Script executed:

# Look for the complete tool list/registry
rg -n 'const.*tools|tools.*=|register|Tool\(' src/mcp/tools/arcane.ts | head -20

Repository: jmagar/arcane-mcp

Length of output: 205


🏁 Script executed:

# Read the tool registration section of arcane.ts
sed -n '60,120p' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 2042


🏁 Script executed:

# Check the entire tool definitions - look for tool list structure
rg -n 'tools|Tool' src/mcp/tools/arcane.ts | head -30

Repository: jmagar/arcane-mcp

Length of output: 99


🏁 Script executed:

# Look for where tools are registered/listed
sed -n '1,100p' src/mcp/tools/arcane.ts | tail -50

Repository: jmagar/arcane-mcp

Length of output: 1872


🏁 Script executed:

# Search for all server.tool calls in arcane.ts
rg -n 'server\.tool' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 78


🏁 Script executed:

# Check the end of the file to see if there are multiple tool registrations
tail -100 src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 4607


🏁 Script executed:

# Search for arcane_help anywhere in the entire src directory
rg -l 'arcane_help' src/

Repository: jmagar/arcane-mcp

Length of output: 43


🏁 Script executed:

# Verify that arcane_help is not defined anywhere in src
rg 'arcane_help' src/ && echo "FOUND" || echo "NOT_FOUND"

Repository: jmagar/arcane-mcp

Length of output: 70


🏁 Script executed:

# Double-check: count how many tools are registered in arcane.ts
rg -c 'server\.tool\(' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 62


Tests still expect arcane_help tool that no longer exists in implementation.

This PR removes the arcane_help tool from src/mcp/tools/arcane.ts, but tests/test_live.sh and tests/TEST_COVERAGE.md actively reference it in protocol assertions and test functions:

  • Protocol assertions check for arcane_help in the tools list (lines 419, 454 in test_live.sh)
  • Test functions test_arcane_help_http and test_arcane_help_stdio (lines 462–490 in test_live.sh) call the removed tool
  • Documentation describes arcane_help as one of the two exposed tools (TEST_COVERAGE.md line 20)

These tests will fail when the tool list no longer includes arcane_help.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/tools/arcane.ts` around lines 69 - 70, Tests and docs still reference
a removed tool named arcane_help; restore compatibility by either reintroducing
an alias named "arcane_help" that delegates to the existing arcane tool
implementation in src/mcp/tools/arcane.ts (so existing behavior and help output
remain identical), or update tests and documentation to remove/replace
references to arcane_help: modify tests/test_live.sh to stop asserting / calling
arcane_help (remove protocol checks for arcane_help and the
test_arcane_help_http/test_arcane_help_stdio functions) and update
TEST_COVERAGE.md to reflect the current exposed tools list. Use the symbol
"arcane_help" to add the alias or update the test function names
test_arcane_help_http and test_arcane_help_stdio and the protocol assertions
accordingly.

Comment on lines +1048 to +1086
describe("help routing", () => {
let callTool: ReturnType<typeof setupTool>["callTool"];
beforeEach(() => {
({ callTool } = setupTool(createMockServices()));
});

it("action=help subaction=list returns top-level help containing domain names", async () => {
const result = await callTool({ action: "help", subaction: "list" });
expect(result.isError).toBeFalsy();
const text = result.content[0].text;
expect(text).toContain("environment");
expect(text).toContain("container");
expect(text).toContain("registry");
});

it("action=help subaction=help returns top-level help (not an error)", async () => {
const result = await callTool({ action: "help", subaction: "help" });
expect(result.isError).toBeFalsy();
expect(result.content[0].text).toContain("environment");
});

it("action=help subaction=container returns container-specific help", async () => {
const result = await callTool({ action: "help", subaction: "container" });
expect(result.isError).toBeFalsy();
expect(result.content[0].text).toContain("Container Domain");
});

it("action=container subaction=help returns container help", async () => {
const result = await callTool({ action: "container", subaction: "help" });
expect(result.isError).toBeFalsy();
expect(result.content[0].text).toContain("Container Domain");
});

it("action=registry subaction=help contains NOT environment-scoped note", async () => {
const result = await callTool({ action: "registry", subaction: "help" });
expect(result.isError).toBeFalsy();
expect(result.content[0].text).toContain("NOT environment-scoped");
});
});
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Show direct handler extraction bypassing SDK validation:"
rg -n --type=ts '_registeredTools|getHandler\(|return entry.handler' tests/mcp/tools/arcane.test.ts -C2

echo
echo "Show help tests using direct handler with domain-hint subaction:"
rg -n --type=ts 'describe\("help routing"|action:\s*"help"|subaction:\s*"container"' tests/mcp/tools/arcane.test.ts -C2

Repository: jmagar/arcane-mcp

Length of output: 2480


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Finding Zod schema for arcane tool ==="
rg -n "schema|Zod|z\." tests/mcp/tools/arcane.test.ts | head -30

echo
echo "=== Look for handler definition and schema in main source files ==="
find . -name "*.ts" -type f ! -path "./node_modules/*" | xargs rg -l "registerArcaneTool|schema" | head -20

Repository: jmagar/arcane-mcp

Length of output: 354


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for the arcane tool registration and schema definition ==="
rg -n "registerArcaneTool|const.*schema|export.*schema" --type=ts -B2 -A5 | grep -A10 -B10 "arcane\|schema"

Repository: jmagar/arcane-mcp

Length of output: 3266


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find the handler implementation ==="
find . -name "*.ts" -type f ! -path "./node_modules/*" | xargs rg -l "action.*help.*container" | head -10

Repository: jmagar/arcane-mcp

Length of output: 133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read arcane.ts to see tool definition and schema ==="
wc -l src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 145


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== First part: tool registration, schema definition ==="
head -150 src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 4852


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for schema in arcane.ts ==="
rg -n "schema|Zod|z\." src/mcp/tools/arcane.ts | head -40

Repository: jmagar/arcane-mcp

Length of output: 451


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read handler implementation (lines 150-250) ==="
sed -n '150,250p' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 3225


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read handler implementation showing help dispatch ==="
sed -n '145,200p' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 2269


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find HELP_DOMAINS definition ==="
rg -n "HELP_DOMAINS" src/mcp/tools/arcane.ts -B2 -A5

Repository: jmagar/arcane-mcp

Length of output: 601


🏁 Script executed:

#!/bin/bash
set -euo pipefuralkıpio pipefail

echo "=== Check where HELP_DOMAINS is defined (dispatch module) ==="
rg -n "HELP_DOMAINS\s*=" src/mcp/tools/dispatch/ -B2 -A5

Repository: jmagar/arcane-mcp

Length of output: 120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check where HELP_DOMAINS is defined (dispatch module) ==="
rg -n "HELP_DOMAINS\s*=" src/mcp/tools/dispatch/ -B2 -A10

Repository: jmagar/arcane-mcp

Length of output: 942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find dispatch/index.ts to see HELP_DOMAINS export ==="
cat src/mcp/tools/dispatch/index.ts

Repository: jmagar/arcane-mcp

Length of output: 783


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check what's in DOMAIN_HELP to confirm container is present ==="
rg -n "container.*:" src/mcp/tools/dispatch/help.ts | head -20

Repository: jmagar/arcane-mcp

Length of output: 1634


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Verify the full subaction enum in schema ==="
sed -n '95,135p' src/mcp/tools/arcane.ts | grep -A40 'subaction'

Repository: jmagar/arcane-mcp

Length of output: 109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Get full subaction enum definition ==="
sed -n '101,133p' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 1043


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== List keys in DOMAIN_HELP ==="
rg -n "^  \w+:" src/mcp/tools/dispatch/help.ts | head -20

Repository: jmagar/arcane-mcp

Length of output: 399


Tests bypass Zod schema validation and accept invalid subaction values.

These tests extract the raw handler directly, bypassing MCP SDK schema validation. For example, line 1070 passes subaction: "container", which violates the registered schema (the subaction enum does not include domain names). While the handler accepts this for help routing (checking HELP_DOMAINS), the schema mismatch should be caught by integration tests that validate inputs through the registered tool contract. Add tests that call the tool through the MCP SDK validation layer to ensure the public contract matches the implementation.

As per coding guidelines, **/*.{test,spec}.{js,jsx,ts,tsx,py}: "Write tests for all public functions and critical business logic".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mcp/tools/arcane.test.ts` around lines 1048 - 1086, The current tests
call the handler directly via setupTool()/callTool(), bypassing MCP SDK Zod
validation and letting invalid subaction values (e.g., "container") slip
through; add integration-style tests that exercise the registered tool contract
through the MCP SDK validation layer instead of the raw handler so schema errors
are surfaced. Specifically, create tests that invoke the public tool entrypoint
(the same function used by the SDK registration) rather than the raw handler
from setupTool, and include cases that pass invalid subaction values (like
"container") expecting validation errors; reference the existing test utilities
(setupTool and callTool) and HELP_DOMAINS to ensure help routing behavior is
still asserted but only after validating via the SDK contract. Ensure the new
tests assert that inputs failing the Zod schema produce validation failures and
that valid help queries still return the expected domain/help text.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 17 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="hooks/hooks.json">

<violation number="1" location="hooks/hooks.json:9">
P2: Syncing only package.json means lockfile-only dependency updates won’t be applied in the plugin data dir. Include package-lock.json in the diff/copy so dependency changes tracked in the lockfile trigger a reinstall.</violation>

<violation number="2" location="hooks/hooks.json:9">
P3: Document this new SessionStart package sync/install command in `docs/plugin/HOOKS.md` so the documented hook sequence matches `hooks/hooks.json`.</violation>
</file>

<file name="docs/plugin/HOOKS.md">

<violation number="1" location="docs/plugin/HOOKS.md:27">
P3: Update the “File location” tree/description to reference `bin/` so the doc matches the new command paths.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/sync-env.sh",
"command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\"",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

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

P2: Syncing only package.json means lockfile-only dependency updates won’t be applied in the plugin data dir. Include package-lock.json in the diff/copy so dependency changes tracked in the lockfile trigger a reinstall.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/hooks.json, line 9:

<comment>Syncing only package.json means lockfile-only dependency updates won’t be applied in the plugin data dir. Include package-lock.json in the diff/copy so dependency changes tracked in the lockfile trigger a reinstall.</comment>

<file context>
@@ -6,12 +6,17 @@
           {
             "type": "command",
-            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/sync-env.sh",
+            "command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\"",
+            "timeout": 60
+          },
</file context>
Suggested change
"command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\"",
"command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 && diff -q \"${CLAUDE_PLUGIN_ROOT}/package-lock.json\" \"${CLAUDE_PLUGIN_DATA}/package-lock.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_ROOT}/package-lock.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package-lock.json\"",
Fix with Cubic

{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/sync-env.sh",
"command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\"",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

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

P3: Document this new SessionStart package sync/install command in docs/plugin/HOOKS.md so the documented hook sequence matches hooks/hooks.json.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/hooks.json, line 9:

<comment>Document this new SessionStart package sync/install command in `docs/plugin/HOOKS.md` so the documented hook sequence matches `hooks/hooks.json`.</comment>

<file context>
@@ -6,12 +6,17 @@
           {
             "type": "command",
-            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/sync-env.sh",
+            "command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\"",
+            "timeout": 60
+          },
</file context>
Fix with Cubic

{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/sync-env.sh",
"command": "${CLAUDE_PLUGIN_ROOT}/bin/sync-env.sh",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

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

P3: Update the “File location” tree/description to reference bin/ so the doc matches the new command paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/plugin/HOOKS.md, line 27:

<comment>Update the “File location” tree/description to reference `bin/` so the doc matches the new command paths.</comment>

<file context>
@@ -24,12 +24,12 @@ hooks/
           {
             "type": "command",
-            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/sync-env.sh",
+            "command": "${CLAUDE_PLUGIN_ROOT}/bin/sync-env.sh",
             "timeout": 10
           },
</file context>
Fix with Cubic

jmagar added 3 commits April 5, 2026 20:38
Blocks any *.env* file from being committed except .env.example.
Prevents credential leaks like the .env.backup incidents.
- Add gitleaks/gitleaks-action@v2 job (full history scan, fetch-depth: 0)
- Fix docker-security steps to reference bin/ (scripts/ dir removed)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
docs/mcp/MCPORTER.md (1)

5-21: ⚠️ Potential issue | 🟠 Major

Smoke-test behavior text is stale after arcane_help removal.

The section still says the smoke test validates arcane_help, but this PR removes that tool. Update this list to reflect routed help via arcane (action=help / domain subaction=help).

📝 Proposed doc update
-2. Lists available tools -- verifies `arcane` and `arcane_help` are registered
-3. Calls `arcane_help` -- verifies it returns markdown text
+2. Lists available tools -- verifies `arcane` is registered
+3. Calls `arcane` help routes -- verifies markdown help output (`action=help` and domain-scoped `subaction=help`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/mcp/MCPORTER.md` around lines 5 - 21, The docs list for
bin/smoke-test.ts is stale: it still claims the smoke test verifies the removed
tool `arcane_help`; update the "What it tests" list to remove `arcane_help` and
instead state that help is validated via `arcane` with help parameters (e.g.,
action=help and domain/subaction=help), and adjust step 3 to reflect calling
`arcane` with help routing and verifying markdown text; ensure references to
`arcane_help` are deleted and any mention of help testing points to `arcane`
(bin/smoke-test.ts) with the proper action/subaction parameters.
docs/INVENTORY.md (1)

86-86: ⚠️ Potential issue | 🟡 Minor

Inventory still lists removed arcane_help tool.

Line 86 lists arcane_help as an available MCP tool, but this tool is being removed in this PR (replaced by action=help and subaction=help routing within the arcane tool). Remove this row to keep the inventory accurate.

,

📝 Suggested fix
 | `arcane` | `gitops` | `browse` | Browse GitOps repository | no |
-| `arcane_help` | -- | -- | Return action reference as Markdown | no |
+| `arcane` | `help` | -- | Return top-level action reference as Markdown | no |

Alternatively, add rows for the new help routing patterns:

  • arcane with action=help for top-level help
  • Each domain with subaction=help for domain-specific help
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/INVENTORY.md` at line 86, Remove the outdated `arcane_help` inventory
entry: delete the row referencing `arcane_help` and, if desired, replace it with
entries documenting the new help routing patterns by adding rows for the
`arcane` tool with `action=help` (top-level help) and for domain-specific help
using `arcane` with `subaction=help` for each domain; update any description
text to reflect the new routing.
hooks/hooks.json (2)

2-2: ⚠️ Potential issue | 🟡 Minor

Description no longer matches actual hook behavior.

The description states "Sync userConfig credentials to .env, enforce 600 permissions, ensure gitignore" but the current configuration only performs package.json sync and npm install. Update the description to accurately reflect the new behavior.

📝 Suggested fix
 {
-  "description": "Sync userConfig credentials to .env, enforce 600 permissions, ensure gitignore",
+  "description": "Sync package.json and install dependencies on session start",
   "hooks": {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/hooks.json` at line 2, Update the top-level "description" value in
hooks.json to reflect the current hook behavior (it no longer syncs userConfig
credentials or manages .env permissions); replace the existing text ("Sync
userConfig credentials to .env, enforce 600 permissions, ensure gitignore") with
a concise description that states the hook now synchronizes package.json and
runs npm install (e.g., "Sync package.json and run npm install").

4-14: ⚠️ Potential issue | 🔴 Critical

Critical security hooks removed without replacement.

The previous hooks that performed essential security functions have been removed:

  1. sync-env.sh (SessionStart) — Synced credentials from Claude Code userConfig to .env. Without this, ARCANE_API_KEY, ARCANE_API_URL, and ARCANE_MCP_TOKEN won't be populated from userConfig, causing authentication failures.

  2. fix-env-perms.sh (PostToolUse) — Enforced chmod 600 on .env after any Write/Edit/Bash operations. Without this, sensitive credentials in .env may have overly permissive file permissions.

  3. ensure-ignore-files.sh (SessionStart) — Verified .gitignore and .dockerignore contain required patterns.

The scripts still exist in bin/ but are no longer invoked. If these hooks were intentionally removed, this represents a security regression. If they should still run, restore them alongside the new npm install hook.

🔧 Suggested fix to restore security hooks
 {
-  "description": "Sync userConfig credentials to .env, enforce 600 permissions, ensure gitignore",
+  "description": "Sync credentials, enforce permissions, ensure gitignore, sync dependencies",
   "hooks": {
     "SessionStart": [
       {
         "hooks": [
+          {
+            "type": "command",
+            "command": "${CLAUDE_PLUGIN_ROOT}/bin/sync-env.sh",
+            "timeout": 10
+          },
+          {
+            "type": "command",
+            "command": "${CLAUDE_PLUGIN_ROOT}/bin/ensure-ignore-files.sh",
+            "timeout": 10
+          },
           {
             "type": "command",
             "command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\"",
             "timeout": 60
           }
         ]
       }
-    ]
+    ],
+    "PostToolUse": [
+      {
+        "matcher": "Write|Edit|MultiEdit|Bash",
+        "hooks": [
+          {
+            "type": "command",
+            "command": "${CLAUDE_PLUGIN_ROOT}/bin/fix-env-perms.sh",
+            "timeout": 5
+          }
+        ]
+      }
+    ]
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/hooks.json` around lines 4 - 14, Restore the removed security hooks by
adding back the SessionStart entries that invoke bin/sync-env.sh and
bin/ensure-ignore-files.sh (so ARCANE_API_KEY/URL/MCP_TOKEN are populated and
ignore files validated) and add a PostToolUse hook to run bin/fix-env-perms.sh
(to enforce chmod 600 on .env) while keeping the new npm-install command
present; ensure you reference the existing "SessionStart" and "PostToolUse"
sections in hooks.json, preserve the current npm install command string ("diff
-q \"${CLAUDE_PLUGIN_ROOT}/package.json\" ... npm install") and set reasonable
timeouts for the restored hooks consistent with the other entries.
docs/mcp/TESTS.md (1)

58-63: ⚠️ Potential issue | 🟡 Minor

Documentation references removed arcane_help tool.

Line 61 states the smoke test verifies "Tool listing returns arcane and arcane_help", but according to the PR objectives, the arcane_help tool is being removed. This documentation is now stale and should be updated to reflect that only the arcane tool is returned (with action=help for help functionality).

,

📝 Suggested fix
 TypeScript smoke test that connects to the MCP server and verifies:
-- Tool listing returns `arcane` and `arcane_help`
-- Help tool returns valid markdown
+- Tool listing returns `arcane`
+- Help action returns valid markdown
 - Basic tool calls succeed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/mcp/TESTS.md` around lines 58 - 63, Update the smoke test documentation
to remove the obsolete reference to the `arcane_help` tool and state that the
tool listing returns only `arcane`; explain that help functionality is accessed
via `action=help` on the `arcane` tool (referencing the smoke test script
`bin/smoke-test.ts` and the "Tool listing" bullet). Ensure the three
verification points reflect: tool listing returns `arcane` only, help via
`arcane` with `action=help` returns valid markdown, and basic tool calls
succeed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.pre-commit-config.yaml:
- Around line 39-40: The current pre-commit hook pattern files: '\.env' is too
broad; update the files pattern (the entry labeled files: '\.env') to a
dotenv-style regex that only matches filenames like .env, .env.local,
.env.development, etc. (for example use a pattern such as '(^|/)\.env(\.|$)' or
'(^|/)\.env(\..+)?$') so hooks only run on true dotenv files, and leave the
existing exclude: '\.env\.example$' unchanged.

In `@bin/block-env-commits.sh`:
- Around line 4-6: The check currently inspects all staged paths (including
deletions) via the staged variable and then blocks any matching .env filenames;
update staged to exclude deletions so removing a tracked .env isn't blocked.
Replace the git command that populates staged to filter out deleted files (e.g.,
use git diff --cached --name-only --diff-filter=ACM or otherwise exclude the D
flag) so blocked only matches added/modified staged paths; keep the existing
blocked grep pattern unchanged.

---

Outside diff comments:
In `@docs/INVENTORY.md`:
- Line 86: Remove the outdated `arcane_help` inventory entry: delete the row
referencing `arcane_help` and, if desired, replace it with entries documenting
the new help routing patterns by adding rows for the `arcane` tool with
`action=help` (top-level help) and for domain-specific help using `arcane` with
`subaction=help` for each domain; update any description text to reflect the new
routing.

In `@docs/mcp/MCPORTER.md`:
- Around line 5-21: The docs list for bin/smoke-test.ts is stale: it still
claims the smoke test verifies the removed tool `arcane_help`; update the "What
it tests" list to remove `arcane_help` and instead state that help is validated
via `arcane` with help parameters (e.g., action=help and domain/subaction=help),
and adjust step 3 to reflect calling `arcane` with help routing and verifying
markdown text; ensure references to `arcane_help` are deleted and any mention of
help testing points to `arcane` (bin/smoke-test.ts) with the proper
action/subaction parameters.

In `@docs/mcp/TESTS.md`:
- Around line 58-63: Update the smoke test documentation to remove the obsolete
reference to the `arcane_help` tool and state that the tool listing returns only
`arcane`; explain that help functionality is accessed via `action=help` on the
`arcane` tool (referencing the smoke test script `bin/smoke-test.ts` and the
"Tool listing" bullet). Ensure the three verification points reflect: tool
listing returns `arcane` only, help via `arcane` with `action=help` returns
valid markdown, and basic tool calls succeed.

In `@hooks/hooks.json`:
- Line 2: Update the top-level "description" value in hooks.json to reflect the
current hook behavior (it no longer syncs userConfig credentials or manages .env
permissions); replace the existing text ("Sync userConfig credentials to .env,
enforce 600 permissions, ensure gitignore") with a concise description that
states the hook now synchronizes package.json and runs npm install (e.g., "Sync
package.json and run npm install").
- Around line 4-14: Restore the removed security hooks by adding back the
SessionStart entries that invoke bin/sync-env.sh and bin/ensure-ignore-files.sh
(so ARCANE_API_KEY/URL/MCP_TOKEN are populated and ignore files validated) and
add a PostToolUse hook to run bin/fix-env-perms.sh (to enforce chmod 600 on
.env) while keeping the new npm-install command present; ensure you reference
the existing "SessionStart" and "PostToolUse" sections in hooks.json, preserve
the current npm install command string ("diff -q
\"${CLAUDE_PLUGIN_ROOT}/package.json\" ... npm install") and set reasonable
timeouts for the restored hooks consistent with the other entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88ed4025-d005-4343-ab11-3cdeb7f22f55

📥 Commits

Reviewing files that changed from the base of the PR and between 886941b and 680c8f2.

📒 Files selected for processing (17)
  • .pre-commit-config.yaml
  • CLAUDE.md
  • Justfile
  • bin/CLAUDE.md
  • bin/block-env-commits.sh
  • bin/check-docker-security.sh
  • bin/check-no-baked-env.sh
  • bin/check-outdated-deps.sh
  • bin/ensure-ignore-files.sh
  • bin/smoke-test.ts
  • docs/INVENTORY.md
  • docs/mcp/DEV.md
  • docs/mcp/MCPORTER.md
  • docs/mcp/PRE-COMMIT.md
  • docs/mcp/TESTS.md
  • hooks/CLAUDE.md
  • hooks/hooks.json

Comment on lines +39 to +40
files: '\.env'
exclude: '\.env\.example$'
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.

🧹 Nitpick | 🔵 Trivial

Tighten hook file matcher to dotenv-style paths.

Current files: '\.env' is very broad and may trigger unnecessarily. Consider matching dotenv-like filenames explicitly to keep hook execution scoped.

🔧 Suggested regex refinement
-        files: '\.env'
-        exclude: '\.env\.example$'
+        files: '(^|/)[^/]*\.env[^/]*$'
+        exclude: '(^|/)[^/]*\.env\.example$'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
files: '\.env'
exclude: '\.env\.example$'
files: '(^|/)[^/]*\.env[^/]*$'
exclude: '(^|/)[^/]*\.env\.example$'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.pre-commit-config.yaml around lines 39 - 40, The current pre-commit hook
pattern files: '\.env' is too broad; update the files pattern (the entry labeled
files: '\.env') to a dotenv-style regex that only matches filenames like .env,
.env.local, .env.development, etc. (for example use a pattern such as
'(^|/)\.env(\.|$)' or '(^|/)\.env(\..+)?$') so hooks only run on true dotenv
files, and leave the existing exclude: '\.env\.example$' unchanged.

Comment on lines +4 to +6
staged=$(git diff --cached --name-only)
blocked=$(printf '%s
' "$staged" | grep -E '(^|/)[^/]*\.env[^/]*$' | grep -v '\.env\.example$' || true)
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.

⚠️ Potential issue | 🟠 Major

Do not block staged deletions of .env files.

This currently scans all staged paths, including deletes, which can prevent users from removing previously tracked secret files.

🔧 Proposed fix
-staged=$(git diff --cached --name-only)
+staged=$(git diff --cached --name-only --diff-filter=ACMR)
 blocked=$(printf '%s
 ' "$staged" | grep -E '(^|/)[^/]*\.env[^/]*$' | grep -v '\.env\.example$' || true)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/block-env-commits.sh` around lines 4 - 6, The check currently inspects
all staged paths (including deletions) via the staged variable and then blocks
any matching .env filenames; update staged to exclude deletions so removing a
tracked .env isn't blocked. Replace the git command that populates staged to
filter out deleted files (e.g., use git diff --cached --name-only
--diff-filter=ACM or otherwise exclude the D flag) so blocked only matches
added/modified staged paths; keep the existing blocked grep pattern unchanged.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 17 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/mcp/TESTS.md">

<violation number="1" location="docs/mcp/TESTS.md:58">
P3: Smoke-test documentation still references the removed `arcane_help` tool, so the expected results are outdated after this breaking change.</violation>
</file>

<file name="docs/mcp/PRE-COMMIT.md">

<violation number="1" location="docs/mcp/PRE-COMMIT.md:36">
P3: The documented `docker-security` hook command does not match the actual pre-commit entry path.</violation>

<violation number="2" location="docs/mcp/PRE-COMMIT.md:44">
P3: The documented `no-baked-env` hook command uses `bin/` but pre-commit is still configured to run `scripts/`.</violation>

<violation number="3" location="docs/mcp/PRE-COMMIT.md:48">
P3: The `ensure-ignore-files` command in docs is out of sync with the configured pre-commit hook path.</violation>
</file>

<file name="bin/block-env-commits.sh">

<violation number="1" location="bin/block-env-commits.sh:4">
P2: This includes staged deletions in the check. A user trying to `git rm` a previously tracked `.env` file will be blocked. Add `--diff-filter=ACMR` to exclude deletions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

#!/usr/bin/env bash
set -euo pipefail

staged=$(git diff --cached --name-only)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P2: This includes staged deletions in the check. A user trying to git rm a previously tracked .env file will be blocked. Add --diff-filter=ACMR to exclude deletions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bin/block-env-commits.sh, line 4:

<comment>This includes staged deletions in the check. A user trying to `git rm` a previously tracked `.env` file will be blocked. Add `--diff-filter=ACMR` to exclude deletions.</comment>

<file context>
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+staged=$(git diff --cached --name-only)
+blocked=$(printf '%s
+' "$staged" | grep -E '(^|/)[^/]*\.env[^/]*$' | grep -v '\.env\.example$' || true)
</file context>
Suggested change
staged=$(git diff --cached --name-only)
staged=$(git diff --cached --name-only --diff-filter=ACMR)
Fix with Cubic

Comment on lines +58 to 61
### Smoke test (`bin/smoke-test.ts`)

TypeScript smoke test that connects to the MCP server and verifies:
- Tool listing returns `arcane` and `arcane_help`
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P3: Smoke-test documentation still references the removed arcane_help tool, so the expected results are outdated after this breaking change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/mcp/TESTS.md, line 58:

<comment>Smoke-test documentation still references the removed `arcane_help` tool, so the expected results are outdated after this breaking change.</comment>

<file context>
@@ -55,7 +55,7 @@ bash tests/test_live.sh --mode stdio
 The script exercises all action domains against the real Arcane API.
 
-### Smoke test (`scripts/smoke-test.ts`)
+### Smoke test (`bin/smoke-test.ts`)
 
 TypeScript smoke test that connects to the MCP server and verifies:
</file context>
Fix with Cubic

### ensure-ignore-files

Runs `bash scripts/ensure-ignore-files.sh --check .`. Verifies `.gitignore` and `.dockerignore` contain required patterns (`.env`, `*.secret`, etc.).
Runs `bash bin/ensure-ignore-files.sh --check .`. Verifies `.gitignore` and `.dockerignore` contain required patterns (`.env`, `*.secret`, etc.).
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P3: The ensure-ignore-files command in docs is out of sync with the configured pre-commit hook path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/mcp/PRE-COMMIT.md, line 48:

<comment>The `ensure-ignore-files` command in docs is out of sync with the configured pre-commit hook path.</comment>

<file context>
@@ -33,19 +33,19 @@ Runs `npx skills-ref validate skills/` when any Markdown file under `skills/` ch
 ### ensure-ignore-files
 
-Runs `bash scripts/ensure-ignore-files.sh --check .`. Verifies `.gitignore` and `.dockerignore` contain required patterns (`.env`, `*.secret`, etc.).
+Runs `bash bin/ensure-ignore-files.sh --check .`. Verifies `.gitignore` and `.dockerignore` contain required patterns (`.env`, `*.secret`, etc.).
 
 ## Claude Code session hooks
</file context>
Suggested change
Runs `bash bin/ensure-ignore-files.sh --check .`. Verifies `.gitignore` and `.dockerignore` contain required patterns (`.env`, `*.secret`, etc.).
Runs `bash scripts/ensure-ignore-files.sh --check .`. Verifies `.gitignore` and `.dockerignore` contain required patterns (`.env`, `*.secret`, etc.).
Fix with Cubic

### no-baked-env

Runs `bash scripts/check-no-baked-env.sh .`. Scans Docker-related files for baked credentials. Triggers on changes to Dockerfile, compose, dockerignore, or entrypoint.
Runs `bash bin/check-no-baked-env.sh .`. Scans Docker-related files for baked credentials. Triggers on changes to Dockerfile, compose, dockerignore, or entrypoint.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P3: The documented no-baked-env hook command uses bin/ but pre-commit is still configured to run scripts/.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/mcp/PRE-COMMIT.md, line 44:

<comment>The documented `no-baked-env` hook command uses `bin/` but pre-commit is still configured to run `scripts/`.</comment>

<file context>
@@ -33,19 +33,19 @@ Runs `npx skills-ref validate skills/` when any Markdown file under `skills/` ch
 ### no-baked-env
 
-Runs `bash scripts/check-no-baked-env.sh .`. Scans Docker-related files for baked credentials. Triggers on changes to Dockerfile, compose, dockerignore, or entrypoint.
+Runs `bash bin/check-no-baked-env.sh .`. Scans Docker-related files for baked credentials. Triggers on changes to Dockerfile, compose, dockerignore, or entrypoint.
 
 ### ensure-ignore-files
</file context>
Suggested change
Runs `bash bin/check-no-baked-env.sh .`. Scans Docker-related files for baked credentials. Triggers on changes to Dockerfile, compose, dockerignore, or entrypoint.
Runs `bash scripts/check-no-baked-env.sh .`. Scans Docker-related files for baked credentials. Triggers on changes to Dockerfile, compose, dockerignore, or entrypoint.
Fix with Cubic

### docker-security

Runs `bash scripts/check-docker-security.sh Dockerfile`. Checks for:
Runs `bash bin/check-docker-security.sh Dockerfile`. Checks for:
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P3: The documented docker-security hook command does not match the actual pre-commit entry path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/mcp/PRE-COMMIT.md, line 36:

<comment>The documented `docker-security` hook command does not match the actual pre-commit entry path.</comment>

<file context>
@@ -33,19 +33,19 @@ Runs `npx skills-ref validate skills/` when any Markdown file under `skills/` ch
 ### docker-security
 
-Runs `bash scripts/check-docker-security.sh Dockerfile`. Checks for:
+Runs `bash bin/check-docker-security.sh Dockerfile`. Checks for:
 - Non-root USER directive
 - No COPY of `.env` files
</file context>
Suggested change
Runs `bash bin/check-docker-security.sh Dockerfile`. Checks for:
Runs `bash scripts/check-docker-security.sh Dockerfile`. Checks for:
Fix with Cubic

- Fix action=help schema: make subaction optional with z.union so domain
  names (e.g. "container") are accepted without being in the enum,
  and action=help calls without subaction are no longer rejected (P1)
- Sync .claude-plugin/plugin.json version to 1.2.0 to match package.json (P2)
- Fix CHANGELOG.md MD022: add blank lines after every ## and ### heading (P2)
- VALID_DOMAINS deduplication and dynamic error message already done in
  commit 886941b (P3 — no additional changes needed)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".claude-plugin/plugin.json">

<violation number="1" location=".claude-plugin/plugin.json:3">
P2: Version bump is inconsistent across plugin manifests; `.codex-plugin/plugin.json` and `gemini-extension.json` remain at 1.1.5 while this file is 1.2.0.</violation>
</file>

<file name="src/mcp/tools/arcane.ts">

<violation number="1" location="src/mcp/tools/arcane.ts:136">
P2: `z.string()` in the `subaction` union makes `subaction` effectively unconstrained, so invalid operations are accepted by schema validation and fail only at runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

{
"name": "arcane-mcp",
"version": "1.1.5",
"version": "1.2.0",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P2: Version bump is inconsistent across plugin manifests; .codex-plugin/plugin.json and gemini-extension.json remain at 1.1.5 while this file is 1.2.0.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude-plugin/plugin.json, line 3:

<comment>Version bump is inconsistent across plugin manifests; `.codex-plugin/plugin.json` and `gemini-extension.json` remain at 1.1.5 while this file is 1.2.0.</comment>

<file context>
@@ -1,6 +1,6 @@
 {
   "name": "arcane-mcp",
-  "version": "1.1.5",
+  "version": "1.2.0",
   "description": "Manage Docker containers, images, volumes, networks, and stacks via the Arcane API.",
   "author": {
</file context>
Fix with Cubic

]),
// Accept domain names as subaction when action=help
// e.g. { action: "help", subaction: "container" } for per-domain docs
z.string(),
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P2: z.string() in the subaction union makes subaction effectively unconstrained, so invalid operations are accepted by schema validation and fail only at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp/tools/arcane.ts, line 136:

<comment>`z.string()` in the `subaction` union makes `subaction` effectively unconstrained, so invalid operations are accepted by schema validation and fail only at runtime.</comment>

<file context>
@@ -85,52 +85,60 @@ export function registerArcaneTool(
+          ]),
+          // Accept domain names as subaction when action=help
+          // e.g. { action: "help", subaction: "container" } for per-domain docs
+          z.string(),
         ])
-        .describe("Operation to perform"),
</file context>
Suggested change
z.string(),
z.enum([
"environment",
"project",
"container",
"image",
"network",
"volume",
"system",
"image-update",
"vulnerability",
"registry",
"gitops",
]),
Fix with Cubic

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 106-115: In the gitleaks job replace mutable refs with immutable
40-char commit SHAs: change the uses entries "actions/checkout@v4" and
"gitleaks/gitleaks-action@v2" to their corresponding full commit SHAs (e.g.,
actions/checkout@<40-char-sha> and gitleaks/gitleaks-action@<40-char-sha>),
keeping the surrounding keys (runs-on, steps, env: GITHUB_TOKEN, and
fetch-depth) intact so the job behavior doesn't change; after updating, verify
no remaining unpinned actions by running the provided verification script.

In `@src/mcp/tools/arcane.ts`:
- Around line 157-160: The code currently silences invalid help domain hints by
mapping unknown subaction values to undefined; change the logic in the help
branch where domainHint is computed (the block that references HELP_DOMAINS,
action === "help", and subaction) to validate subaction explicitly: if subaction
is present and not contained in HELP_DOMAINS, surface an error (throw or return
a structured error response) instead of downgrading to undefined, otherwise set
domainHint to the valid subaction; ensure any callers of this branch handle the
new error shape.
- Around line 88-138: The current subaction schema uses z.union([z.enum(...),
z.string()]) which makes the enum meaningless because z.string() will always
accept any string; change this to conditional schemas keyed off the action field
(e.g., replace the loose z.union with a z.discriminatedUnion('action', [...]) or
separate object schemas) so that for action === "help" subaction may be
z.string(), but for all other actions subaction is strictly z.enum([...]) or
omitted; update the schema where subaction and action are defined to use this
discriminated/conditional approach instead of the global z.string() branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ef052b90-66e2-45a9-83b7-4ebcaf5233a5

📥 Commits

Reviewing files that changed from the base of the PR and between 680c8f2 and 0c87f29.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json and included by **/*
📒 Files selected for processing (4)
  • .claude-plugin/plugin.json
  • .github/workflows/ci.yml
  • CHANGELOG.md
  • src/mcp/tools/arcane.ts

Comment on lines +106 to +115
gitleaks:
name: Secret Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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.

⚠️ Potential issue | 🟠 Major

Pin action refs to immutable SHAs in the new secret-scan job.

Using mutable tags (@v4, @v2) allows upstream tag retargeting and weakens CI supply-chain integrity. Please pin to full commit SHAs.

Suggested change
  gitleaks:
    name: Secret Scan
    runs-on: ubuntu-latest
    steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<full-40-char-commit-sha>
        with:
          fetch-depth: 0
-      - uses: gitleaks/gitleaks-action@v2
+      - uses: gitleaks/gitleaks-action@<full-40-char-commit-sha>
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Run this to verify unpinned actions across workflows:

#!/bin/bash
set -euo pipefail

# Lists `uses:` entries that are NOT pinned to a 40-char SHA.
rg -n '^\s*-\s*uses:\s*[^@]+@([^\s]+)' .github/workflows \
| awk '
{
  line=$0
  ref=$0
  sub(/^.*@/, "", ref)
  if (ref !~ /^[0-9a-fA-F]{40}$/) {
    print line
  }
}'

Expected result after fix: no output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 106 - 115, In the gitleaks job replace
mutable refs with immutable 40-char commit SHAs: change the uses entries
"actions/checkout@v4" and "gitleaks/gitleaks-action@v2" to their corresponding
full commit SHAs (e.g., actions/checkout@<40-char-sha> and
gitleaks/gitleaks-action@<40-char-sha>), keeping the surrounding keys (runs-on,
steps, env: GITHUB_TOKEN, and fetch-depth) intact so the job behavior doesn't
change; after updating, verify no remaining unpinned actions by running the
provided verification script.

Comment on lines +88 to +138
.union([
z.enum([
"list",
"get",
"create",
"update",
"delete",
"start",
"stop",
"restart",
"pull",
"up",
"down",
"prune",
"scan",
"stats",
// NOTE: "logs" intentionally omitted — the Arcane REST API has no
// logs endpoint; dispatchContainer retains a "logs" case as a safety
// net against future schema drift, but exposing it in the enum would
// be a lying contract that always errors.
"destroy",
"redeploy",
"test",
"build",
"stop-all",
"start-all",
"docker-info",
"convert",
"check-all",
"check",
"check-batch",
"summary",
"ignore",
"unignore",
"scanner-status",
"list-ignored",
"browse",
"list-backups",
"create-backup",
"restore",
"restore-files",
"delete-backup",
"status",
"sync",
"help",
]),
// Accept domain names as subaction when action=help
// e.g. { action: "help", subaction: "container" } for per-domain docs
z.string(),
])
.describe("Operation to perform"),
.optional()
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Zod, does z.union([z.enum(["list","get"]), z.string()]) accept arbitrary strings like "foobar" outside the enum?

💡 Result:

Yes. Because z.union([...]) “tests the input against each of the options in order” and succeeds if any option validates; since one option is z.string(), any string (including "foobar") will be accepted. [1]

Also, the inferred TypeScript type for z.union([z.enum(["list","get"]), z.string()]) collapses to just string (the enum doesn’t narrow it), because string already includes those literals. [1]

Source(s): [1]


🏁 Script executed:

# Check the file structure and line counts
wc -l src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 88


🏁 Script executed:

# Examine lines 88-138 in context
sed -n '85,140p' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 1886


🏁 Script executed:

# Examine lines 171-176 in context
sed -n '168,180p' src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 495


🏁 Script executed:

# Look for where this schema is used/validated downstream
rg -A 5 -B 5 "subaction" src/mcp/tools/arcane.ts

Repository: jmagar/arcane-mcp

Length of output: 9646


🏁 Script executed:

# Get exact context around lines 171-176 with line numbers
sed -n '171,176p' src/mcp/tools/arcane.ts | cat -n

Repository: jmagar/arcane-mcp

Length of output: 332


🏁 Script executed:

# Search for other union definitions with enum + z.string()
rg -n "z\.union" src/mcp/tools/arcane.ts -A 2 -B 2

Repository: jmagar/arcane-mcp

Length of output: 43


🏁 Script executed:

# Search for all z.union patterns (case-insensitive)
rg -i "union" src/mcp/tools/arcane.ts -n

Repository: jmagar/arcane-mcp

Length of output: 80


🏁 Script executed:

# Look at the full schema definition around lines 88-176
sed -n '70,180p' src/mcp/tools/arcane.ts | cat -n

Repository: jmagar/arcane-mcp

Length of output: 4440


🏁 Script executed:

# Look at the exact lines 171-176 in the file (not relative to sed output)
sed -n '165,180p' src/mcp/tools/arcane.ts | cat -n

Repository: jmagar/arcane-mcp

Length of output: 672


🏁 Script executed:

# Search for any other schema definitions or patterns that might use z.union
rg -n "subaction\|z\.union\|z\.enum" src/mcp/tools/arcane.ts -B 1 -A 1

Repository: jmagar/arcane-mcp

Length of output: 43


🏁 Script executed:

# Look at the full file to understand structure better
head -n 200 src/mcp/tools/arcane.ts | tail -n 50 | cat -n

Repository: jmagar/arcane-mcp

Length of output: 2416


Remove z.string() from the subaction union or condition validation by action.

The union at lines 18–68 accepts any string because z.union([z.enum([...]), z.string()]) succeeds if any branch validates; z.string() makes the enum validation ineffective. While the inline comment indicates this is intentional for the help action (to accept domain names), the schema applies globally to all actions, allowing unsupported subactions and pushing validation errors downstream. Either use conditional validation based on the action value, or make the enum constraint strict and handle domain-name flexibility separately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/tools/arcane.ts` around lines 88 - 138, The current subaction schema
uses z.union([z.enum(...), z.string()]) which makes the enum meaningless because
z.string() will always accept any string; change this to conditional schemas
keyed off the action field (e.g., replace the loose z.union with a
z.discriminatedUnion('action', [...]) or separate object schemas) so that for
action === "help" subaction may be z.string(), but for all other actions
subaction is strictly z.enum([...]) or omitted; update the schema where
subaction and action are defined to use this discriminated/conditional approach
instead of the global z.string() branch.

Comment on lines +157 to +160
if (action === "help") {
const domainHint =
subaction && HELP_DOMAINS.has(subaction) ? subaction : undefined;
return {
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.

⚠️ Potential issue | 🟠 Major

Do not silently ignore invalid domain hints in action=help.

Line 159 currently downgrades unknown subaction values to undefined, so typos (e.g., contianer) return top-level help instead of surfacing an invalid domain error.

🔧 Proposed fix
         if (action === "help") {
-          const domainHint =
-            subaction && HELP_DOMAINS.has(subaction) ? subaction : undefined;
+          const domainHint =
+            subaction && subaction !== "help" ? subaction : undefined;
           return {
             content: [
               { type: "text" as const, text: dispatchHelp(domainHint) },
             ],
           };
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (action === "help") {
const domainHint =
subaction && HELP_DOMAINS.has(subaction) ? subaction : undefined;
return {
if (action === "help") {
const domainHint =
subaction && subaction !== "help" ? subaction : undefined;
return {
content: [
{ type: "text" as const, text: dispatchHelp(domainHint) },
],
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/tools/arcane.ts` around lines 157 - 160, The code currently silences
invalid help domain hints by mapping unknown subaction values to undefined;
change the logic in the help branch where domainHint is computed (the block that
references HELP_DOMAINS, action === "help", and subaction) to validate subaction
explicitly: if subaction is present and not contained in HELP_DOMAINS, surface
an error (throw or return a structured error response) instead of downgrading to
undefined, otherwise set domainHint to the valid subaction; ensure any callers
of this branch handle the new error shape.

@jmagar jmagar merged commit f0c57e5 into main Apr 6, 2026
12 checks passed
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.

2 participants