Skip to content

feat: add per-harness global overrides#32

Merged
Jercik merged 5 commits intomainfrom
feat/per-harness-global-overrides
Apr 6, 2026
Merged

feat: add per-harness global overrides#32
Jercik merged 5 commits intomainfrom
feat/per-harness-global-overrides

Conversation

@Jercik
Copy link
Copy Markdown
Owner

@Jercik Jercik commented Apr 6, 2026

Summary

Replaces the flat BUILT_IN_GLOBAL_TARGETS array with a typed harness registry and adds a globalOverrides config field for per-harness rule customization.

Closes #31

Changes

New: Harness Registry (src/core/harness-registry.ts)

  • Typed registry mapping harness names (claude, gemini, opencode, codex) to their target paths
  • Single source of truth for valid harness names and write targets

Config Schema (src/config/config.ts)

  • Added globalOverrides field: Record<HarnessName, string[]> with closed-world key validation
  • Made projects optional — configs with only global and/or globalOverrides are valid (globals-only mode)
  • Top-level refinement ensures at least one of global, globalOverrides, or projects is specified
  • Empty globalOverrides: {} rejected at parse time (matching existing global: [] and projects: [] behavior)
  • Reusable hasPositiveGlob helper extracted for DRY validation

Global Sync (src/core/sync-global.ts)

  • Per-harness content composition: shared global rules + harness-specific override rules
  • Same-rule overlap detection within a harness (throws clear error)
  • Cross-harness reuse of the same rule file is allowed
  • Empty targets silently skipped (no writes, no errors)
  • Unmatched override globs reported as warnings with globalOverrides.<harness>: prefix
  • Override glob results cached from overlap detection and reused by loadRules to avoid redundant filesystem traversal

Pattern Warning Parsing (src/cli/collect-pattern-warnings.ts)

  • Extracted pattern warning collection into a dedicated module
  • Parses globalOverrides.<harness>: prefixed patterns into distinct source labels
  • Uses "global" as canonical source in machine-readable output (--json, --porcelain); maps to "global config" only in human-readable stderr

Run Sync Command (src/cli/run-sync-command.ts)

  • Handles optional projects array (?? [] fallback)
  • Verbose output correctly reports global file writes in globals-only mode
  • Uses collectGlobalPatternWarnings for structured warning parsing

Documentation

  • README updated with globalOverrides config example, field documentation, and globals-only mode

Backward Compatibility

Existing configs without globalOverrides continue to work identically. Machine-readable output (--json, --porcelain) preserves "global" as the warning source label. All original tests pass unchanged (with minor type-safety adjustments for the now-optional projects field).

Tests

14 new tests covering:

  • Schema validation: valid harness names, unknown harness rejection, empty arrays, only-negation globs, globals-only mode, empty config rejection
  • Composition: shared + override content merging, override-only harnesses, cross-harness reuse
  • Overlap detection: same file in global + override fails loudly
  • Unmatched pattern reporting for overrides
  • Skip behavior for empty targets

Replace flat BUILT_IN_GLOBAL_TARGETS with a typed harness registry
mapping harness names (claude, gemini, opencode, codex) to target paths.

Add globalOverrides config field with closed-world key validation so
each harness can receive tailored global rules on top of the shared
global content.

Key changes:
- New harness registry in src/core/harness-registry.ts
- globalOverrides field in Zod config schema with per-key validation
- projects field is now optional (globals-only mode)
- Per-harness content composition: shared global + override rules
- Same-rule overlap detection within a harness (fails loudly)
- Cross-harness rule reuse is allowed
- Empty targets silently skipped (no auto-delete)
- Unmatched override globs reported as warnings
- Full backward compatibility with existing configs

Closes #31

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github-actions[bot]

This comment was marked as resolved.

Jercik

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

…-only mode

- Reject `globalOverrides: {}` in config validation, matching the existing
  behavior for `global: []` and `projects: []`.
- Fix misleading 'No projects configured; nothing to do.' verbose message
  when only global rules are synced (no projects configured).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

…abels

- Document globalOverrides in README with config example and field descriptions
- Note that projects is optional (globals-only config is valid)
- Optimize syncGlobal to pre-glob shared global paths once and pass
  resolved paths to detectOverlap instead of re-globbing per harness
- Accept pre-computed GlobResult in loadRules to avoid redundant globbing
- Extract pattern warning logic into collect-pattern-warnings module
- Parse globalOverrides prefix in unmatched pattern warnings to assign
  distinct source labels (e.g. 'in globalOverrides.claude' not 'in global')
- Fix test mock sequence to match optimized glob call order
- Use resetAllMocks in beforeEach to prevent mock leakage between tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Jercik Jercik force-pushed the feat/per-harness-global-overrides branch from 4e29858 to 853c24d Compare April 6, 2026 15:53
github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

Move sharedPaths variable inside the if(hasOverrides) block where it is
actually consumed. Hoists sharedResult to function scope so both the
global loading and override detection blocks can access it. This makes
the coupling between global glob results and overlap detection explicit,
preventing future misuse of a silently-empty sharedPaths array.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github-actions[bot]

This comment was marked as resolved.

github-actions[bot]

This comment was marked as resolved.

@Jercik Jercik merged commit ff669f7 into main Apr 6, 2026
8 checks passed
@Jercik Jercik deleted the feat/per-harness-global-overrides branch April 6, 2026 16:10
@github-actions

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: per-harness global overrides

1 participant