Skip to content

feat: add --rulesync-dir flag to decouple source rules from output directory#1514

Draft
maxime-bentin-cko wants to merge 26 commits intodyoshikawa:mainfrom
maxime-bentin-cko:maxime/dir-path
Draft

feat: add --rulesync-dir flag to decouple source rules from output directory#1514
maxime-bentin-cko wants to merge 26 commits intodyoshikawa:mainfrom
maxime-bentin-cko:maxime/dir-path

Conversation

@maxime-bentin-cko
Copy link
Copy Markdown

@maxime-bentin-cko maxime-bentin-cko commented Apr 20, 2026

Problem

Currently rulesync generate always reads source rules from .rulesync/ inside the current working directory. This forces users to duplicate their rules in every repository.

Solution

Add a --rulesync-dir <path> CLI flag that points to the parent of the .rulesync/ directory. When set, all feature processors read source files from <rulesyncDir>/.rulesync/ while generated output still lands in the working directory (or --base-dirs paths).

This enables a single central rules store — e.g. ~/.aiglobal/.rulesync/ — shared across any number of repositories:

rulesync generate --rulesync-dir ~/.aiglobal --targets '*'

Changes

  • CLI (src/cli/index.ts): adds --rulesync-dir <path> option to the generate command; also fixes --base-dir--base-dirs (plural, consistent with existing code)
  • Config (src/config/config.ts): adds rulesyncDir to ConfigParams / Config; getRulesyncDir() falls back to process.cwd() when not set (no breaking change)
  • All feature processors (rules, ignore, mcp, commands, subagents, skills): accept and propagate rulesyncDir so source-file resolution is decoupled from the output base directory
  • Types (src/types/dir-feature-processor.ts, feature-processor.ts): expose rulesyncDir on processor option types
  • Tests: existing generate/import tests updated; rulesyncDir threaded through generate.ts integration layer

Checklist

  • All existing tests pass (pnpm cicheck)
  • No breaking change — flag is optional, default behaviour unchanged
  • Works with --base-dirs, --dry-run, --check, and all --features

@maxime-bentin-cko maxime-bentin-cko changed the title feat: introduce rulesyncDir option for custom directory handling acro… feat: add --rulesync-dir flag to decouple source rules from output directory Apr 20, 2026
@maxime-bentin-cko maxime-bentin-cko marked this pull request as ready for review April 20, 2026 14:46
Copilot AI review requested due to automatic review settings April 20, 2026 14:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a --rulesync-dir option to decouple where Rulesync reads .rulesync/ sources from where it writes generated tool configs, enabling centralized/shared rule stores (e.g. ~/.aiglobal/.rulesync) while still generating into the working directory (or --base-dirs).

Changes:

  • Add rulesyncDir to config + resolver and propagate it through feature processor constructors and file loaders.
  • Update rulesync generate CLI flags (--rulesync-dir, --base-dirs) and thread rulesyncDir through the generate pipeline.
  • Update tests and documentation to reflect the new source-directory behavior.

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/utils/file.ts Allows absolute baseDirs by skipping traversal checks for absolute paths.
src/types/feature-processor.ts Adds rulesyncDir to base FeatureProcessor state/constructor.
src/types/dir-feature-processor.ts Adds rulesyncDir to base DirFeatureProcessor state/constructor.
src/test-utils/test-directories.ts Changes temp test directory root selection (now considers TMPDIR).
src/lib/import.test.ts Updates mock config to include getRulesyncDir.
src/lib/generate.ts Passes rulesyncDir into each feature processor.
src/lib/generate.test.ts Updates mock config to include getRulesyncDir.
src/index.test.ts Updates mock config to include getRulesyncDir.
src/features/subagents/subagents-processor.ts Loads subagent sources from rulesyncDir and passes it into parsing.
src/features/subagents/rulesync-subagent.ts Makes source parsing baseDir-configurable (instead of hardcoded cwd).
src/features/skills/skills-processor.ts Loads skills sources from rulesyncDir instead of cwd.
src/features/rules/rulesync-rule.ts Makes rule source parsing baseDir-configurable.
src/features/rules/rules-processor.ts Reads rule sources from rulesyncDir and passes it into parsing.
src/features/mcp/rulesync-mcp.ts Adds baseDir support to MCP source parsing params.
src/features/mcp/mcp-processor.ts Reads MCP source from rulesyncDir.
src/features/mcp/mcp-processor.test.ts Updates expectation for MCP parsing call to include baseDir.
src/features/ignore/rulesync-ignore.ts Makes ignore source parsing baseDir-configurable.
src/features/ignore/ignore-processor.ts Reads ignore source from rulesyncDir.
src/features/commands/rulesync-command.ts Makes command source parsing baseDir-configurable.
src/features/commands/commands-processor.ts Reads command sources from rulesyncDir and passes it into parsing.
src/features/commands/commands-processor.test.ts Updates expectations to include baseDir and absolute glob roots.
src/config/config.ts Adds rulesyncDir to config schema/state with getRulesyncDir() accessor.
src/config/config-resolver.ts Threads rulesyncDir through resolution (incl. config path base + global override).
src/cli/index.ts Adds --rulesync-dir option and renames --base-dir--base-dirs.
src/cli/commands/import.test.ts Updates mock config to include getRulesyncDir.
src/cli/commands/generate.ts Checks .rulesync existence based on config.getRulesyncDir().
src/cli/commands/generate.test.ts Updates mock config to include getRulesyncDir.
skills/rulesync/cli-commands.md Documents --rulesync-dir and expands generate command reference.
docs/reference/cli-commands.md Documents --rulesync-dir and expands generate command reference.
docs/guide/separate-rulesync-dir.md New guide explaining centralized rules and interaction with --global.

Comment thread src/config/config-resolver.ts Outdated
Comment thread src/lib/generate.test.ts
Comment thread src/test-utils/test-directories.ts Outdated
Comment thread src/cli/index.ts Outdated
Comment thread docs/guide/separate-rulesync-dir.md Outdated
Copy link
Copy Markdown
Owner

@dyoshikawa dyoshikawa left a comment

Choose a reason for hiding this comment

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

Thanks for this — decoupling the source .rulesync/ from the output dir is a real quality-of-life win for teams with shared rules. A few concerns before merge:

  1. rulesyncDir is threaded into rules/ignore/mcp/commands/subagents/skills but HooksProcessor and PermissionsProcessor still read from process.cwd(), so a user running rulesync generate --rulesync-dir ~/.aiglobal will get a split-brain state where hooks and permissions silently come from CWD while everything else comes from the central dir. See comment on src/lib/generate.ts.

  2. Renaming -b, --base-dir to -b, --base-dirs is a breaking CLI change that's unrelated to this feature and contradicts the PR description's "default behaviour unchanged" claim. See comment on src/cli/index.ts.

  3. configByFile.rulesyncDir appears to be unreachable because mergeConfigs does not copy the field through — so users who put "rulesyncDir" in rulesync.jsonc will have it silently dropped. See comment on src/config/config-resolver.ts.

  4. The new guide docs/guide/separate-rulesync-dir.md is not registered in the VitePress sidebar, which means it won't render on the docs site and — because scripts/sync-skill-docs.ts iterates the sidebar — it will never be synced to skills/rulesync/. That breaks the repo invariant.

  5. The test-directories.ts change touches the whole test suite and looks unrelated to the feature. Worth splitting off.

  6. No e2e coverage was added. The project convention is to keep an end-to-end happy-path per Tool × Feature matrix entry; an e2e that proves "read from dir A, write to dir B" through the actual CLI would be valuable.

On the security side nothing critical stood out, but validateBaseDir is now a no-op for absolute paths — see that comment.

Comment thread src/cli/index.ts Outdated
.option("--delete", "Delete all existing files in output directories before generating")
.option(
"-b, --base-dir <paths>",
"-b, --base-dirs <paths>",
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Renaming -b, --base-dir to -b, --base-dirs is a breaking change for anyone already scripting --base-dir (commander will reject the old flag as unknown). The PR description says "default behaviour unchanged", which isn't quite true with this rename bundled in. Could we either keep the old name as a deprecated alias (commander supports defining both) or split the rename into its own release-noted PR?

Comment thread src/config/config-resolver.ts Outdated
getDefaults().gitignoreDestination,
dryRun: dryRun ?? configByFile.dryRun ?? getDefaults().dryRun,
check: check ?? configByFile.check ?? getDefaults().check,
rulesyncDir: rulesyncDir ?? configByFile.rulesyncDir ?? getDefaults().rulesyncDir,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

configByFile.rulesyncDir looks unreachable here: mergeConfigs (~lines 81-104) does not copy the rulesyncDir field through, so putting "rulesyncDir": "..." in rulesync.jsonc is silently dropped. Either extend mergeConfigs and add a test that covers config-file sourcing, or explicitly reject rulesyncDir at the schema level and document that this is CLI/programmatic only.

Comment thread src/config/config-resolver.ts Outdated
// When --rulesync-dir is explicitly provided the user is decoupling source
// from output, so "global: true" from the config file must not apply unless
// the caller also explicitly passes --global.
const configGlobal = rulesyncDir !== undefined ? false : configByFile.global;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This silently drops configByFile.global whenever rulesyncDir is set — with no warning. A user whose central rulesync.jsonc sets "global": true and then runs rulesync generate --rulesync-dir ~/.aiglobal will find the output scope has changed out from under them. Consider emitting a logger.warn when we override, or — since the file is explicitly their central config — honouring the file value when --global isn't passed explicitly.

Comment thread src/utils/file.ts Outdated
checkPathTraversal({ relativePath: baseDir, intendedRootDir: process.cwd() });
// Traversal check only applies to relative paths; absolute paths are
// explicitly provided by the caller and may point anywhere on the filesystem.
if (!isAbsolute(baseDir)) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Before this change validateBaseDir rejected absolute paths that escape CWD. After this change any absolute path is accepted — including one passed via --base-dirs (since config-resolver.ts normalises all baseDirs to absolute before calling validateBaseDir). Net effect: the traversal check is effectively disabled for all baseDirs in practice. At minimum, please add unit tests in file.test.ts covering both the allowed absolute-path case and pathological inputs like /foo/../../etc. Consider resolving + normalising the path first and verifying the result still matches intent.

Comment thread src/test-utils/test-directories.ts Outdated
}> {
const testsDir = join(originalCwd, "tmp", "tests");
// Use TMPDIR environment variable to ensure writes are sandboxed-friendly
const root = process.env.TMPDIR || originalCwd;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This change is unrelated to --rulesync-dir and affects every test in the repo (the default root flips from ./tmp/tests to $TMPDIR). It also conflicts with .claude/rules/testing-guidelines.md, which pins the convention to ./tmp/tests/projects/{RANDOM}. If the sandbox filesystem restriction is real, fixing it by rewriting the shared helper feels like the wrong layer — could we either set TMPDIR in the sandbox env itself, or at minimum split this out into its own PR so it can be reviewed on its own merits?

Comment thread src/features/ignore/rulesync-ignore.ts Outdated
const baseDir = process.cwd();
static async fromFile({
baseDir = process.cwd(),
}: { baseDir?: string } = {}): Promise<RulesyncIgnore> {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Minor consistency nit: every other RulesyncXxx.fromFile in the repo uses a named parameter type (RulesyncFileFromFileParams, or a Pick<…> of it — see rulesync-mcp.ts, rulesync-subagent.ts). An inline anonymous type here breaks that pattern. Worth defining RulesyncIgnoreFromFileParams = Pick<RulesyncFileFromFileParams, "baseDir"> and using it.

Comment thread src/lib/generate.ts Outdated
try {
const processor = new IgnoreProcessor({
baseDir: baseDir === process.cwd() ? "." : baseDir,
rulesyncDir: config.getRulesyncDir(),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

rulesyncDir is threaded into the processors here and in the other generateXxxCore functions, but generateHooksCore and generatePermissionsCore (further down in this file) still construct their processors without it. HooksProcessor.loadRulesyncFiles and PermissionsProcessor.loadRulesyncFiles still call RulesyncHooks.fromFile({ baseDir: process.cwd() }) / RulesyncPermissions.fromFile({ baseDir: process.cwd() }). Running with --rulesync-dir ~/.aiglobal will therefore read hooks/permissions from CWD while reading everything else from the central dir — a subtle, silent split. Please extend both processors and their matching RulesyncHooks.fromFile / RulesyncPermissions.fromFile the same way.

Comment thread docs/guide/separate-rulesync-dir.md Outdated
@@ -0,0 +1,52 @@
# Separate Rulesync Directory
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This page isn't registered in docs/.vitepress/config.ts sidebar, so two things follow: (1) the page won't appear in the docs site navigation, and (2) scripts/sync-skill-docs.ts iterates the sidebar, so skills/rulesync/separate-rulesync-dir.md will never be generated. That breaks the repo's docs/skills/rulesync/ sync invariant. Please add an entry under the Guide section and re-run the sync script.

Also: the "global is not applied" note at the end of this file is load-bearing behaviour — worth promoting it out of a blockquote into a ::: warning admonition so readers don't miss it.

@dyoshikawa
Copy link
Copy Markdown
Owner

Follow-up on the naming discussion — proposing this split:

In scope for this PR: rename rulesyncDirinputRoot everywhere it was introduced. Rationale: the parameter is brand-new to this PR, so renaming it now costs nothing, and keeping rulesyncDir would carry an inferior name into every processor constructor, Config accessor, and file-class fromFile signature. The CLI flag would become --input-root <path>.

Deferred to a separate issue: rename the existing baseDir / baseDirsoutputRoot / outputRoots for naming symmetry. baseDir is pervasive (~4385 occurrences across ~310 files) and the CLI flag / rulesync.jsonc field are public API, so it deserves its own review pass with a proper deprecation path. Tracked in #1519.

This means the two names will be temporarily asymmetric (inputRoot alongside baseDir) on main until #1519 lands, which I think is acceptable — the asymmetry is visible and motivating rather than silently wrong, and #1519 is trivially mechanical once started.

Does this split work for you?

@maxime-bentin-cko
Copy link
Copy Markdown
Author

@dyoshikawa Yes that works for me. I will have time to work on that only on Friday. Cheers 😄

maxime-bentin-cko and others added 16 commits April 24, 2026 20:06
…ystem compatibility

- Update commands-processor.test.ts to expect baseDir parameter in RulesyncCommand.fromFile() calls
- Update mcp-processor.test.ts to expect baseDir parameter in RulesyncMcp.fromFile() calls
- Fix setupTestDirectory to use $TMPDIR environment variable for sandbox-friendly test directory creation
- Resolves EPERM filesystem permission errors in test suite by using sandboxed-allowed paths

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: the TMPDIR fallback was an unrelated test-infrastructure
change that affected every test in the repo and conflicted with the
documented convention in .claude/rules/testing-guidelines.md. Splitting
this into a separate PR for independent review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: use a named 'Pick<RulesyncFileFromFileParams, "baseDir">'
alias instead of an inline anonymous object type, matching the pattern
used by RulesyncMcpFromFileParams and other sibling file classes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: mergeConfigs was not copying the new inputRoot field,
so a value set in rulesync.jsonc or rulesync.local.jsonc was silently
dropped before reaching the final Config. Add the field to the merge and
cover both 'sourced from rulesync.jsonc' and 'rulesync.local.jsonc
overrides rulesync.jsonc' cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: previously we silently overrode 'global: true' from
rulesync.jsonc when --input-root was provided, which could surprise users
whose central config has global enabled. Now we emit a one-time warning
naming the config file and pointing at --global as the opt-in to keep
user-scope output. Scope behavior itself is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: the inputRoot option previously only affected rules,
ignore, mcp, commands, subagents, and skills — leaving hooks and
permissions readers pinned to process.cwd(). With a decoupled source
directory that produced a split-brain state where some features came from
the central directory and others from the working directory. Both
processors now accept and propagate inputRoot the same way the other
processors do, and lib/generate.ts wires config.getInputRoot() through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback:
- Add the new guide to the VitePress sidebar so it actually renders on the
  documentation site.
- Run scripts/sync-skill-docs.ts so skills/rulesync/ mirrors the new page
  (keeps the repo's sync invariant intact).
- Promote the '--input-root does not enable --global' note to a ':::warning'
  admonition and reference the new one-time runtime warning so users see
  the scope interaction prominently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: after the initial PR, validateBaseDir skipped the
traversal check for all absolute paths, which effectively disabled the
check for the baseDirs pipeline since baseDirs are resolved to absolute
paths up-stream. An input like '/foo/../../etc' would pass silently.

- Reject absolute paths that still contain '..' segments so unsafely
  constructed paths are surfaced immediately with the same error as the
  relative branch.
- Keep normalized absolute paths allowed (central rules directories
  outside the cwd are a valid use case for --input-root).
- Cover the new branch with tests for legitimate absolutes, absolutes
  outside cwd, absolutes containing '..' at the start/middle/end, and
  mixed-separator inputs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-run of scripts/sync-skill-docs.ts picks up two drifts missed in
the prior docs commit:

- SKILL.md: add 'Separate Input Root' to the Guide nav list.
- separate-input-root.md: strip the ':::warning' admonition wrapper
  the script no longer emits in skill output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review feedback: add end-to-end coverage that proves the flag
separates source-of-rules from output-destination. The spec stages
.rulesync/rules/overview.md in a dedicated sourceDir, chdir's into
a distinct outputDir, invokes 'rulesync generate --input-root
<sourceDir>', and asserts:

- Generated files land in cwd (outputDir).
- No tool output leaks into sourceDir.

Parameterized over claudecode / cursor / codexcli to exercise the
Tool x Feature matrix while keeping runtime bounded. runGenerate()
gains an 'inputRoot' option that appends the --input-root flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 24, 2026 18:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Comment thread src/cli/index.ts
Comment on lines +254 to +257
.option(
"--input-root <path>",
"Path to the directory containing .rulesync/ (parent of .rulesync/)",
)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

PR metadata describes a --rulesync-dir flag, but the implementation/docs add --input-root. If this rename is intentional, please update the PR title/description (or provide --rulesync-dir as an alias) to avoid breaking users following the PR text.

Copilot uses AI. Check for mistakes.
Comment thread src/utils/file.ts
Comment on lines +288 to +296
// An absolute path pointing anywhere on the filesystem is allowed, but
// reject ones that still contain `..` segments: they indicate the caller
// is constructing the path unsafely rather than passing a normalized
// intent. Normalize first so the error message names the resolved form
// the caller would actually hit.
const segments = baseDir.split(/[/\\]/);
if (segments.includes("..")) {
throw new Error(`Path traversal detected: ${baseDir}`);
}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The comment says “Normalize first so the error message names the resolved form…”, but the code never normalizes baseDir before checking/throwing. Either normalize (e.g., via resolve/normalize) before splitting/throwing, or adjust the comment so it matches the actual behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +168 to +173
if (inputRoot !== undefined && global === undefined && configByFile.global === true) {
// oxlint-disable-next-line no-console
console.warn(
`rulesync: ignoring "global: true" from ${validatedConfigPath} because --input-root ` +
`was provided; pass --global explicitly to keep user-scope output.`,
);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

console.warn here bypasses the CLI Logger (and therefore bypasses --silent and JSON mode suppression). In --json runs this can inject non-JSON output unexpectedly. Prefer routing this warning through the configured Logger (or returning a warning to be logged by the caller) so output formatting modes remain reliable.

Copilot uses AI. Check for mistakes.
Comment thread src/config/config-resolver.ts Outdated
const validatedConfigPath = resolvePath(configPath, process.cwd());
// When inputRoot is set, resolve the config path relative to it so that
// the user's central .rulesync source dir is also the config source.
const configBaseDir = inputRoot ?? process.cwd();
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

resolvePath()/checkPathTraversal() assume intendedRootDir is a stable base. Here configBaseDir can be a relative inputRoot, which can make traversal checks unreliable because path.relative() is computed against the non-resolved base. Consider resolving/normalizing inputRoot (and validating it) before using it as the base for resolvePath.

Suggested change
const configBaseDir = inputRoot ?? process.cwd();
const configBaseDir = resolve(inputRoot ?? process.cwd());
validateBaseDir(configBaseDir);

Copilot uses AI. Check for mistakes.
Comment thread src/cli/index.ts
Comment on lines +254 to +257
.option(
"--input-root <path>",
"Path to the directory containing .rulesync/ (parent of .rulesync/)",
)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

In this generate command, the CLI option is still --base-dir, but ConfigResolver.resolve() destructures baseDirs (plural) and wrapCommand forwards options verbatim. As a result, --base-dir overrides won’t be applied. Consider renaming to --base-dirs (and optionally keeping --base-dir as a deprecated alias) so the CLI flag actually works.

Copilot uses AI. Check for mistakes.
@maxime-bentin-cko maxime-bentin-cko marked this pull request as draft April 24, 2026 18:17
@maxime-bentin-cko maxime-bentin-cko marked this pull request as draft April 24, 2026 18:17
maxime-bentin-cko and others added 9 commits April 24, 2026 21:04
Remove the "Normalize first" sentence from the comment: the code only
splits/checks segments, it never normalizes before throwing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously ConfigResolver used inputRoot as-is when resolving configPath.
A relative inputRoot would make the traversal checks in resolvePath
unreliable. Resolve to an absolute path and validate via validateBaseDir
before using it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously ConfigResolver emitted a console.warn when --input-root caused
the config-file global flag to be dropped. That bypassed the configured
Logger, so --silent and JSON output modes could not suppress or reroute
the message.

Accept an optional logger in ConfigResolver.resolve's second argument and
call logger?.warn instead. generateCommand now forwards its logger.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commander maps --base-dir <paths> to options.baseDir (camelCase, singular),
but ConfigResolver.resolve destructures baseDirs (plural). This meant
--base-dir overrides from the CLI were silently dropped.

Normalize at the command boundary: accept both shapes in GenerateOptions
and pass baseDirs (with baseDirs winning when both happen to be set).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

3 participants