feat(commands): add flattenedCommandNaming option for collision-free flattened command names#2314
Merged
dyoshikawa merged 1 commit intoJul 20, 2026
Conversation
…flattened command names For tools without subdirectory command support (supportsSubdirectory: false, e.g. Cursor, Antigravity IDE), nested commands are flattened to their basename, so pj/test.md and ops/test.md both map to test.md and the last one processed silently wins (a warning is logged). Add an opt-in, config-file-only option flattenedCommandNaming: - "basename" (default): current behavior, unchanged - "path": join directory segments into the filename (pj/test.md -> pj-test.md), keeping flattened names unique Tools that support subdirectories (e.g. Claude Code, OpenCode) are unaffected by the option. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an opt-in configuration option to control how nested command paths are flattened for tools that don’t support subdirectories, enabling collision-free generation by deriving filenames from the full command path.
Changes:
- Introduces
flattenedCommandNamingconfig option ("basename"default,"path"join strategy). - Threads the option through config resolution and
generateintoCommandsProcessor’s flattening logic. - Adds/updates tests and documentation for the new option.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types/features.ts | Adds FlattenedCommandNamingSchema and type for the new option. |
| src/config/config.ts | Extends config schema/state with flattenedCommandNaming and a getter. |
| src/config/config-resolver.ts | Adds defaults/merge behavior and enforces config-file-only resolution. |
| src/features/commands/commands-processor.ts | Implements "basename" vs "path" flattening strategy when subdirectories aren’t supported. |
| src/lib/generate.ts | Passes flattenedCommandNaming from Config into CommandsProcessor. |
| src/lib/generate.test.ts | Updates config mocks to include getFlattenedCommandNaming. |
| src/cli/commands/generate.test.ts | Updates config mocks to include getFlattenedCommandNaming. |
| src/features/commands/commands-processor.test.ts | Adds tests covering path-join naming and subdirectory-tool behavior. |
| docs/guide/configuration.md | Documents the new flattenedCommandNaming option. |
| skills/rulesync/configuration.md | Documents the new flattenedCommandNaming option (synced copy). |
| // Naming strategy for command files flattened for tools without subdirectory | ||
| // support: "basename" keeps only the filename (`pj/test.md` -> `test.md`, | ||
| // colliding paths overwrite each other), "path" joins the directory segments | ||
| // into the filename (`pj/test.md` -> `pj-test.md`), keeping names unique. |
Comment on lines
+57
to
+58
| // (`pj/test.md` -> `pj-test.md`), keeping flattened names unique. | ||
| // Tools that support subdirectories (e.g. Claude Code) are unaffected. |
Comment on lines
+57
to
+58
| // (`pj/test.md` -> `pj-test.md`), keeping flattened names unique. | ||
| // Tools that support subdirectories (e.g. Claude Code) are unaffected. |
| expect(calledArgs.rulesyncCommand.getRelativeFilePath()).toBe("update-confluence-docs.md"); | ||
| }); | ||
|
|
||
| it('should not warn about collisions when flattenedCommandNaming is "path" keeps names unique', async () => { |
Owner
|
@Millon15 Thank you! |
This was referenced Jul 20, 2026
Open
shaneholloman
pushed a commit
to shaneholloman/rulesync
that referenced
this pull request
Jul 20, 2026
…ow-ups Resolves the concrete follow-ups from the PR dyoshikawa#2314 review (dyoshikawa#2323): - wire config.getFlattenedCommandNaming() into the CommandsProcessor constructed by the convert commands strategy, with a regression test (previously the configured "path" naming was silently ignored by rulesync convert and fell back to "basename") - soften the uniqueness wording for "path" naming in src/types/features.ts and docs (collisions are reduced, not ruled out; the collision warning still applies) and add a migration note about stale flat-named files and delete: true - add config-resolution tests: file value reaches Config, local config override, default, and invalid-value rejection - use toPosixPath() instead of a hand-rolled separator regex for the path flattening, per the coding guidelines Item 6 of the issue (top-level key vs featureOptions placement) is a design decision deliberately split out to dyoshikawa#2325. Closes dyoshikawa#2323 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
For tools with
supportsSubdirectory: false(Cursor, Antigravity IDE, Codex CLI, …), nested commands are flattened to their basename (introduced in #1108). When two nested commands share a basename, they map to the same output file and the last one processed silently wins (with a warning since the collision-warn was added).This is easy to hit in real repos that namespace commands by category. Our project hit it with 12 of 45 nested commands, e.g.:
plan/run.md,qa/run.md,ralphex/run.md→ allrun.mdupdate/docs.md,update/confluence/docs.md,update/google/docs.md,update/slack/docs.md→ alldocs.mdThe warning helps, but there is currently no way to get all nested commands generated for flat-namespace tools — some are always lost.
Solution
Add an opt-in, config-file-only option
flattenedCommandNaming:"basename"(default)pj/test.md→test.md"path"pj/test.md→pj-test.md,update/confluence/docs.md→update-confluence-docs.mdsupportsSubdirectory: true(Claude Code, OpenCode, Roo, …) are unaffected — nested paths are preserved as before.pj-test.mdnext topj/test.md).sources), no CLI flag — it is a project-level generation policy, mirroring howConfigResolverResolveParamsalready excludessources.Changes
src/types/features.ts—FlattenedCommandNamingSchema("basename" | "path")src/config/config.ts,src/config/config-resolver.ts— schema field, defaults, local-config merge, gettersrc/features/commands/commands-processor.ts— naming strategy inflattenRelativeFilePathsrc/lib/generate.ts— pass the option throughdocs/guide/configuration.md+skills/rulesync/configuration.md— option documented (kept in sync)commands-processor.test.ts(path naming, no-collision assertion, subdirectory tools unaffected); Config mocks extended in the two generate test suitesTest Plan
pnpm vitest run src/features/commands/commands-processor.test.ts— 47/47pnpm test— 323 files, 7270 tests passedpnpm run check— oxfmt, oxlint, tsgo all clean;pnpm cspellcleangenerate --checkidempotent🤖 Generated with Claude Code