Skip to content

Commit 5e7d6fc

Browse files
author
Brad Kinnard
committed
refactor(cli): table-driven mutual-exclusion check
Replaces nine pairwise if-print-exit branches with a single _PAIRWISE_CONFLICTS table walked by _die_on_mode_conflict. Exit code 2 and stderr messages match the prior implementation byte for byte (string contents lifted verbatim), so existing tests cover the behavior. The multi-emit cardinality check and the emit-mode-vs-history loop stay separate because their phrasing is distinct from the pairwise pattern.
1 parent 5e96a49 commit 5e7d6fc

2 files changed

Lines changed: 71 additions & 88 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- CLI mutual-exclusion block refactored to a single `_PAIRWISE_CONFLICTS` table walked by one loop, replacing nine hand-written `if ... and ...:` branches. Exit code 2 and stderr messages are unchanged.
1213
- `--history` now fans out across every target path instead of silently writing only when exactly one path is supplied. Each SKILL.md still gets its own per-skill `.skillcheck-history.json` next to it. `--fail-on-regression` escalates to exit 1 when any target regresses.
1314
- `--show-history` with multiple paths still reads only the first path's ledger (each skill has its own), but the extra paths now produce a stderr warning instead of being silently dropped.
1415
- README Configuration section now documents that `--ignore PREFIX` accepts any dotted rule prefix, not just top-level categories. The worked example shows `--ignore compat.unverified` so users can silence the unverified-field info diagnostics while keeping `compat.claude-only` and `compat.vscode-dirname`.

src/skillcheck/cli.py

Lines changed: 70 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -570,12 +570,13 @@ def main() -> None:
570570
if args.semantic and args.ingest_graph is None:
571571
args.analyze_graph = True
572572

573-
# -----------------------------------------------------------------------
574-
# Mutual exclusion checks
575-
# -----------------------------------------------------------------------
576-
577573
# -----------------------------------------------------------------------
578574
# Mode conflict resolution
575+
#
576+
# Conflicts are declared as a table: each entry pairs two CLI flags with a
577+
# one-line reason. _die_on_mode_conflict walks the table once, reports the
578+
# first conflicting pair, and exits 2. The exit messages are identical to
579+
# the prior hand-written branches; the refactor is behavior-preserving.
579580
# -----------------------------------------------------------------------
580581

581582
_EMIT_MODES: dict[str, bool] = {
@@ -594,12 +595,71 @@ def main() -> None:
594595
"--ingest-graph": args.ingest_graph is not None,
595596
}
596597

598+
_PAIRWISE_CONFLICTS: list[tuple[str, str, str]] = [
599+
(
600+
"--emit-critique-prompt", "--ingest-critique",
601+
"Cannot use --emit-critique-prompt and --ingest-critique together. "
602+
"Pick one: emit a prompt for your agent to execute, or ingest the agent's response.",
603+
),
604+
(
605+
"--emit-graph", "--analyze-graph",
606+
"Cannot use --emit-graph with --analyze-graph. "
607+
"--emit-graph is an emit mode (replaces the report). "
608+
"--analyze-graph is an augment mode (adds to the report).",
609+
),
610+
(
611+
"--emit-graph", "--ingest-critique",
612+
"Cannot use --emit-graph with --ingest-critique. "
613+
"--emit-graph replaces the report; --ingest-critique augments it.",
614+
),
615+
(
616+
"--emit-graph-prompt", "--ingest-critique",
617+
"Cannot use --emit-graph-prompt with --ingest-critique. "
618+
"--emit-graph-prompt is an emit mode; --ingest-critique is an augment mode.",
619+
),
620+
(
621+
"--emit-graph-prompt", "--analyze-graph",
622+
"Cannot use --emit-graph-prompt with --analyze-graph. "
623+
"--emit-graph-prompt is an emit mode; --analyze-graph is an augment mode.",
624+
),
625+
(
626+
"--emit-graph-prompt", "--ingest-graph",
627+
"Cannot use --emit-graph-prompt with --ingest-graph. "
628+
"--emit-graph-prompt emits a prompt; --ingest-graph ingests the agent's response. "
629+
"Use them in separate invocations.",
630+
),
631+
(
632+
"--ingest-graph", "--emit-graph",
633+
"Cannot use --ingest-graph with --emit-graph. "
634+
"--emit-graph replaces the report; --ingest-graph augments it.",
635+
),
636+
(
637+
"--ingest-graph", "--emit-critique-prompt",
638+
"Cannot use --ingest-graph with --emit-critique-prompt. "
639+
"--emit-critique-prompt is an emit mode; --ingest-graph is an augment mode.",
640+
),
641+
(
642+
"--ingest-graph", "--analyze-graph",
643+
"Cannot use --ingest-graph with --analyze-graph. "
644+
"--ingest-graph supersedes heuristic-only graph analysis.",
645+
),
646+
]
647+
648+
def _flag_active(flag: str) -> bool:
649+
if flag in _EMIT_MODES:
650+
return _EMIT_MODES[flag]
651+
if flag in _AUGMENT_FLAGS:
652+
return _AUGMENT_FLAGS[flag]
653+
# Defensive: an entry referenced a flag not in either dict. The
654+
# table is internal, so this should never happen at runtime.
655+
raise AssertionError(f"Unknown flag in conflict table: {flag}")
656+
597657
def _die_on_mode_conflict() -> None:
598658
"""Check for mode conflicts and exit with code 2 on any conflict."""
599659
active_emits = [flag for flag, on in _EMIT_MODES.items() if on]
600-
active_augments = [flag for flag, on in _AUGMENT_FLAGS.items() if on]
601660

602-
# Two or more emit modes active → pick-one conflict.
661+
# Two or more emit modes active -> pick-one conflict. Reported before
662+
# the pairwise table so the multi-emit case has a dedicated message.
603663
if len(active_emits) > 1:
604664
print(
605665
f"Cannot use {' and '.join(active_emits[:2])} together. "
@@ -608,88 +668,10 @@ def _die_on_mode_conflict() -> None:
608668
)
609669
sys.exit(2)
610670

611-
# --emit-critique-prompt + --ingest-critique → emit/ingest pair conflict.
612-
if args.emit_critique_prompt and args.ingest_critique is not None:
613-
print(
614-
"Cannot use --emit-critique-prompt and --ingest-critique together. "
615-
"Pick one: emit a prompt for your agent to execute, or ingest the agent's response.",
616-
file=sys.stderr,
617-
)
618-
sys.exit(2)
619-
620-
# emit_graph vs analyze_graph
621-
if args.emit_graph and args.analyze_graph:
622-
print(
623-
"Cannot use --emit-graph with --analyze-graph. "
624-
"--emit-graph is an emit mode (replaces the report). "
625-
"--analyze-graph is an augment mode (adds to the report).",
626-
file=sys.stderr,
627-
)
628-
sys.exit(2)
629-
630-
# emit_graph vs ingest_critique
631-
if args.emit_graph and args.ingest_critique is not None:
632-
print(
633-
"Cannot use --emit-graph with --ingest-critique. "
634-
"--emit-graph replaces the report; --ingest-critique augments it.",
635-
file=sys.stderr,
636-
)
637-
sys.exit(2)
638-
639-
# emit_graph_prompt vs ingest_critique
640-
if args.emit_graph_prompt and args.ingest_critique is not None:
641-
print(
642-
"Cannot use --emit-graph-prompt with --ingest-critique. "
643-
"--emit-graph-prompt is an emit mode; --ingest-critique is an augment mode.",
644-
file=sys.stderr,
645-
)
646-
sys.exit(2)
647-
648-
# emit_graph_prompt vs analyze_graph
649-
if args.emit_graph_prompt and args.analyze_graph:
650-
print(
651-
"Cannot use --emit-graph-prompt with --analyze-graph. "
652-
"--emit-graph-prompt is an emit mode; --analyze-graph is an augment mode.",
653-
file=sys.stderr,
654-
)
655-
sys.exit(2)
656-
657-
# emit_graph_prompt vs ingest_graph
658-
if args.emit_graph_prompt and args.ingest_graph is not None:
659-
print(
660-
"Cannot use --emit-graph-prompt with --ingest-graph. "
661-
"--emit-graph-prompt emits a prompt; --ingest-graph ingests the agent's response. "
662-
"Use them in separate invocations.",
663-
file=sys.stderr,
664-
)
665-
sys.exit(2)
666-
667-
# ingest_graph vs emit_graph
668-
if args.ingest_graph is not None and args.emit_graph:
669-
print(
670-
"Cannot use --ingest-graph with --emit-graph. "
671-
"--emit-graph replaces the report; --ingest-graph augments it.",
672-
file=sys.stderr,
673-
)
674-
sys.exit(2)
675-
676-
# ingest_graph vs emit_critique_prompt
677-
if args.ingest_graph is not None and args.emit_critique_prompt:
678-
print(
679-
"Cannot use --ingest-graph with --emit-critique-prompt. "
680-
"--emit-critique-prompt is an emit mode; --ingest-graph is an augment mode.",
681-
file=sys.stderr,
682-
)
683-
sys.exit(2)
684-
685-
# ingest_graph vs analyze_graph
686-
if args.ingest_graph is not None and args.analyze_graph:
687-
print(
688-
"Cannot use --ingest-graph with --analyze-graph. "
689-
"--ingest-graph supersedes heuristic-only graph analysis.",
690-
file=sys.stderr,
691-
)
692-
sys.exit(2)
671+
for flag_a, flag_b, reason in _PAIRWISE_CONFLICTS:
672+
if _flag_active(flag_a) and _flag_active(flag_b):
673+
print(reason, file=sys.stderr)
674+
sys.exit(2)
693675

694676
# Emit modes + --history / --show-history incompatibility.
695677
for emit_flag, emit_active in _EMIT_MODES.items():

0 commit comments

Comments
 (0)