diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py
index 14a22b9..c4768ff 100644
--- a/pre_commit/clientlib.py
+++ b/pre_commit/clientlib.py
@@ -18,6 +18,8 @@ from pre_commit.util import parse_version
 
 logger = logging.getLogger('pre_commit')
 
+check_string_regex = cfgv.check_and(cfgv.check_string, cfgv.check_regex)
+
 
 def check_type_tag(tag):
     if tag not in ALL_TAGS:
@@ -53,12 +55,8 @@ MANIFEST_HOOK_DICT = cfgv.Map(
     cfgv.Required('language', cfgv.check_one_of(all_languages)),
     cfgv.Optional('alias', cfgv.check_string, ''),
 
-    cfgv.Optional(
-        'files', cfgv.check_and(cfgv.check_string, cfgv.check_regex), '',
-    ),
-    cfgv.Optional(
-        'exclude', cfgv.check_and(cfgv.check_string, cfgv.check_regex), '^$',
-    ),
+    cfgv.Optional('files', check_string_regex, ''),
+    cfgv.Optional('exclude', check_string_regex, '^$'),
     cfgv.Optional('types', cfgv.check_array(check_type_tag), ['file']),
     cfgv.Optional('exclude_types', cfgv.check_array(check_type_tag), []),
 
@@ -260,7 +258,8 @@ CONFIG_SCHEMA = cfgv.Map(
         cfgv.check_array(cfgv.check_one_of(C.STAGES)),
         C.STAGES,
     ),
-    cfgv.Optional('exclude', cfgv.check_regex, '^$'),
+    cfgv.Optional('files', check_string_regex, ''),
+    cfgv.Optional('exclude', check_string_regex, '^$'),
     cfgv.Optional('fail_fast', cfgv.check_bool, False),
     cfgv.Optional(
         'minimum_pre_commit_version',
@@ -272,6 +271,7 @@ CONFIG_SCHEMA = cfgv.Map(
             'repos',
             'default_language_version',
             'default_stages',
+            'files',
             'exclude',
             'fail_fast',
             'minimum_pre_commit_version',
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 0b1f7b7..f5a5b1e 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -206,7 +206,9 @@ def _run_hooks(config, hooks, args, environ):
     skips = _get_skips(environ)
     cols = _compute_cols(hooks, args.verbose)
     filenames = _all_filenames(args)
-    filenames = filter_by_include_exclude(filenames, '', config['exclude'])
+    filenames = filter_by_include_exclude(
+        filenames, config['files'], config['exclude'],
+    )
     classifier = Classifier(filenames)
     retval = 0
     for hook in hooks:

make `files` available for use at top-level
We have a codebase that we are trying to add pre-commit to, but not everyone on the team wants or trusts the workflow. For this reason, we wanted to make it an opt-in system where by default all directories are excluded from pre-commit, except certain ones.

Ideally, we could accomplish this by being able to use the `files:` option at the top-level yaml. Because we cannot do this at the top level, we are forced to either add it for every single repo, or invert the value of the regular expression used in the `exclude:` option at the top-level.

Ergonomically, neither of these choices are good ones - the former requires many repeated `files:` options and is brittle, and the latter is synactically unintelligible for non-regex experts:
```regex
(?x)^(?!( 
    parent\/opt-in-dir1|
    opt-in-dir2
)(\/.*)?$).+
```

Could you allow us to use `files:` at the top level?
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index 4221134..63d0925 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -180,6 +180,22 @@ def test_global_exclude(cap_out, store, tempdir_factory):
         assert printed.endswith(expected)
 
 
+def test_global_files(cap_out, store, tempdir_factory):
+    git_path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
+    with cwd(git_path):
+        with modify_config() as config:
+            config['files'] = '^bar.py$'
+        open('foo.py', 'a').close()
+        open('bar.py', 'a').close()
+        cmd_output('git', 'add', '.')
+        opts = run_opts(verbose=True)
+        ret, printed = _do_run(cap_out, store, git_path, opts)
+        assert ret == 0
+        # Does not contain foo.py since it was not included
+        expected = b'hookid: bash_hook\n\nbar.py\nHello World\n\n'
+        assert printed.endswith(expected)
+
+
 @pytest.mark.parametrize(
     ('args', 'expected_out'),
     [

pip install -e .
parse_log_pytest
requirements.txt
pytest
3.7
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aspy.yaml==1.3.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cfgv==3.3.1
coverage==7.2.7
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.12.2
identify==2.5.24
importlib-metadata==6.7.0
iniconfig==2.0.0
mock==5.2.0
nodeenv==1.9.1
packaging==24.0
platformdirs==4.0.0
pluggy==1.2.0
-e git+https://github.com/pre-commit/pre-commit.git@0ecd50b80d9dd8387d4486140a8dfd471261ec75#egg=pre_commit
pytest==7.4.4
pytest-env==1.0.1
PyYAML==6.0.1
six==1.17.0
toml==0.10.2
tomli==2.0.1
typing_extensions==4.7.1
virtualenv==20.26.6
zipp==3.15.0

name: pre-commit
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aspy-yaml==1.3.0
      - cfgv==3.3.1
      - coverage==7.2.7
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.12.2
      - identify==2.5.24
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - mock==5.2.0
      - nodeenv==1.9.1
      - packaging==24.0
      - platformdirs==4.0.0
      - pluggy==1.2.0
      - pytest==7.4.4
      - pytest-env==1.0.1
      - pyyaml==6.0.1
      - six==1.17.0
      - toml==0.10.2
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - virtualenv==20.26.6
      - zipp==3.15.0
prefix: /opt/conda/envs/pre-commit

tests/commands/run_test.py::test_global_files
tests/commands/run_test.py::test_run_all_hooks_failing tests/commands/run_test.py::test_arbitrary_bytes_hook tests/commands/run_test.py::test_hook_that_modifies_but_returns_zero tests/commands/run_test.py::test_types_hook_repository tests/commands/run_test.py::test_exclude_types_hook_repository tests/commands/run_test.py::test_global_exclude tests/commands/run_test.py::test_show_diff_on_failure[args0-All tests/commands/run_test.py::test_show_diff_on_failure[args1-All tests/commands/run_test.py::test_show_diff_on_failure[args2-reproduce tests/commands/run_test.py::test_run[options0-outputs0-0-True] tests/commands/run_test.py::test_run[options1-outputs1-0-True] tests/commands/run_test.py::test_run[options2-outputs2-0-True] tests/commands/run_test.py::test_run[options3-outputs3-1-True] tests/commands/run_test.py::test_run[options4-outputs4-1-True] tests/commands/run_test.py::test_run[options5-outputs5-0-True] tests/commands/run_test.py::test_run[options6-outputs6-0-True] tests/commands/run_test.py::test_run[options7-outputs7-0-False] tests/commands/run_test.py::test_run_output_logfile tests/commands/run_test.py::test_always_run tests/commands/run_test.py::test_always_run_alt_config tests/commands/run_test.py::test_hook_verbose_enabled tests/commands/run_test.py::test_origin_source_error_msg_error[master-] tests/commands/run_test.py::test_origin_source_error_msg_error[-master] tests/commands/run_test.py::test_origin_source_both_ok tests/commands/run_test.py::test_has_unmerged_paths tests/commands/run_test.py::test_merge_conflict tests/commands/run_test.py::test_merge_conflict_modified tests/commands/run_test.py::test_merge_conflict_resolved tests/commands/run_test.py::test_compute_cols[hooks0-True-80] tests/commands/run_test.py::test_compute_cols[hooks1-False-81] tests/commands/run_test.py::test_compute_cols[hooks2-True-85] tests/commands/run_test.py::test_compute_cols[hooks3-False-82] tests/commands/run_test.py::test_get_skips[environ0-expected_output0] tests/commands/run_test.py::test_get_skips[environ1-expected_output1] tests/commands/run_test.py::test_get_skips[environ2-expected_output2] tests/commands/run_test.py::test_get_skips[environ3-expected_output3] tests/commands/run_test.py::test_get_skips[environ4-expected_output4] tests/commands/run_test.py::test_get_skips[environ5-expected_output5] tests/commands/run_test.py::test_get_skips[environ6-expected_output6] tests/commands/run_test.py::test_skip_hook tests/commands/run_test.py::test_skip_aliased_hook tests/commands/run_test.py::test_hook_id_not_in_non_verbose_output tests/commands/run_test.py::test_hook_id_in_verbose_output tests/commands/run_test.py::test_multiple_hooks_same_id tests/commands/run_test.py::test_aliased_hook_run tests/commands/run_test.py::test_non_ascii_hook_id tests/commands/run_test.py::test_stdout_write_bug_py26 tests/commands/run_test.py::test_lots_of_files tests/commands/run_test.py::test_stages tests/commands/run_test.py::test_commit_msg_hook tests/commands/run_test.py::test_prepare_commit_msg_hook tests/commands/run_test.py::test_local_hook_passes tests/commands/run_test.py::test_local_hook_fails tests/commands/run_test.py::test_pcre_deprecation_warning tests/commands/run_test.py::test_meta_hook_passes tests/commands/run_test.py::test_error_with_unstaged_config tests/commands/run_test.py::test_no_unstaged_error_with_all_files_or_files[opts0] tests/commands/run_test.py::test_no_unstaged_error_with_all_files_or_files[opts1] tests/commands/run_test.py::test_files_running_subdir tests/commands/run_test.py::test_pass_filenames[True-hook_args0-foo.py] tests/commands/run_test.py::test_pass_filenames[False-hook_args1-] tests/commands/run_test.py::test_pass_filenames[True-hook_args2-some tests/commands/run_test.py::test_pass_filenames[False-hook_args3-some tests/commands/run_test.py::test_fail_fast tests/commands/run_test.py::test_classifier_removes_dne tests/commands/run_test.py::test_classifier_normalizes_filenames_on_windows_to_forward_slashes tests/commands/run_test.py::test_classifier_does_not_normalize_backslashes_non_windows tests/commands/run_test.py::test_include_exclude_base_case tests/commands/run_test.py::test_matches_broken_symlink tests/commands/run_test.py::test_include_exclude_total_match tests/commands/run_test.py::test_include_exclude_does_search_instead_of_match tests/commands/run_test.py::test_include_exclude_exclude_removes_files tests/commands/run_test.py::test_args_hook_only
pre_commit/clientlib.py pre_commit/commands/run.py
pre_commit/clientlib.py pre_commit/commands/run.py