igittigitt 2.2.2__tar.gz → 2.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. {igittigitt-2.2.2 → igittigitt-2.2.4}/.claude-plugin/plugin.json +1 -1
  2. {igittigitt-2.2.2 → igittigitt-2.2.4}/.env.example +29 -99
  3. {igittigitt-2.2.2 → igittigitt-2.2.4}/.gitignore +8 -4
  4. igittigitt-2.2.4/CHANGELOG.md +242 -0
  5. igittigitt-2.2.4/CONFIG.md +131 -0
  6. {igittigitt-2.2.2 → igittigitt-2.2.4}/DEVELOPMENT.md +1 -34
  7. {igittigitt-2.2.2 → igittigitt-2.2.4}/Makefile +38 -4
  8. {igittigitt-2.2.2 → igittigitt-2.2.4}/PKG-INFO +24 -19
  9. {igittigitt-2.2.2 → igittigitt-2.2.4}/README.md +3 -0
  10. {igittigitt-2.2.2 → igittigitt-2.2.4}/docs/systemdesign/module_reference.md +5 -0
  11. {igittigitt-2.2.2 → igittigitt-2.2.4}/pyproject.toml +24 -19
  12. {igittigitt-2.2.2 → igittigitt-2.2.4}/skills/python-gitignore/SKILL.md +11 -2
  13. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/__init__conf__.py +1 -1
  14. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/commands/_common.py +6 -2
  15. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/commands/config.py +180 -44
  16. igittigitt-2.2.4/src/igittigitt/adapters/cli/config_load.py +171 -0
  17. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/context.py +22 -1
  18. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/exit_codes.py +3 -1
  19. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/main.py +6 -4
  20. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/root.py +15 -29
  21. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/defaultconfig.d/40-layered-config.toml +12 -3
  22. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/defaultconfig.d/90-logging.toml +18 -9
  23. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/deploy.py +22 -9
  24. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/overrides.py +56 -11
  25. igittigitt-2.2.4/src/igittigitt/adapters/logging/setup.py +286 -0
  26. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/memory/config.py +3 -2
  27. igittigitt-2.2.4/src/igittigitt/adapters/memory/logging.py +61 -0
  28. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/application/ports.py +11 -5
  29. igittigitt-2.2.4/tests/conftest.py +158 -0
  30. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_adapters_integration.py +1 -1
  31. igittigitt-2.2.4/tests/test_cli_config_errors.py +518 -0
  32. igittigitt-2.2.4/tests/test_cli_main_exit.py +81 -0
  33. igittigitt-2.2.4/tests/test_config_overrides.py +76 -0
  34. igittigitt-2.2.4/tests/test_declared_dependencies.py +93 -0
  35. igittigitt-2.2.4/tests/test_deploy_mode_safety.py +189 -0
  36. igittigitt-2.2.4/tests/test_logging_dotenv_isolation.py +345 -0
  37. igittigitt-2.2.4/tests/test_memory_logging.py +68 -0
  38. igittigitt-2.2.4/tests/test_permission_defaults.py +588 -0
  39. igittigitt-2.2.2/CHANGELOG.md +0 -119
  40. igittigitt-2.2.2/CONFIG.md +0 -68
  41. igittigitt-2.2.2/src/igittigitt/adapters/config/permissions.py +0 -186
  42. igittigitt-2.2.2/src/igittigitt/adapters/logging/setup.py +0 -134
  43. igittigitt-2.2.2/src/igittigitt/adapters/memory/logging.py +0 -18
  44. igittigitt-2.2.2/tests/conftest.py +0 -75
  45. {igittigitt-2.2.2 → igittigitt-2.2.4}/.claude-plugin/marketplace.json +0 -0
  46. {igittigitt-2.2.2 → igittigitt-2.2.4}/.devcontainer/devcontainer.json +0 -0
  47. {igittigitt-2.2.2 → igittigitt-2.2.4}/.devcontainer/settings.json +0 -0
  48. {igittigitt-2.2.2 → igittigitt-2.2.4}/.github/actions/extract-metadata/action.yml +0 -0
  49. {igittigitt-2.2.2 → igittigitt-2.2.4}/.github/dependabot.yml +0 -0
  50. {igittigitt-2.2.2 → igittigitt-2.2.4}/.github/workflows/codeql.yml +0 -0
  51. {igittigitt-2.2.2 → igittigitt-2.2.4}/.github/workflows/default_cicd_public.yml +0 -0
  52. {igittigitt-2.2.2 → igittigitt-2.2.4}/.github/workflows/default_release_public.yml +0 -0
  53. {igittigitt-2.2.2 → igittigitt-2.2.4}/.qlty/qlty.toml +0 -0
  54. {igittigitt-2.2.2 → igittigitt-2.2.4}/.snyk +0 -0
  55. {igittigitt-2.2.2 → igittigitt-2.2.4}/CONTRIBUTING.md +0 -0
  56. {igittigitt-2.2.2 → igittigitt-2.2.4}/INSTALL.md +0 -0
  57. {igittigitt-2.2.2 → igittigitt-2.2.4}/LICENSE +0 -0
  58. {igittigitt-2.2.2 → igittigitt-2.2.4}/SECURITY.md +0 -0
  59. {igittigitt-2.2.2 → igittigitt-2.2.4}/ai-disclosure.md +0 -0
  60. {igittigitt-2.2.2 → igittigitt-2.2.4}/ai-stance.md +0 -0
  61. {igittigitt-2.2.2 → igittigitt-2.2.4}/codecov.yml +0 -0
  62. {igittigitt-2.2.2 → igittigitt-2.2.4}/notebooks/Quickstart.ipynb +0 -0
  63. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/__init__.py +0 -0
  64. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/__main__.py +0 -0
  65. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/__init__.py +0 -0
  66. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/__init__.py +0 -0
  67. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/commands/__init__.py +0 -0
  68. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/commands/check.py +0 -0
  69. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/commands/filter.py +0 -0
  70. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/commands/info.py +0 -0
  71. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/commands/logging.py +0 -0
  72. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/constants.py +0 -0
  73. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/py.typed +0 -0
  74. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/cli/typed_click.py +0 -0
  75. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/__init__.py +0 -0
  76. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/defaultconfig.d/50-performance.toml +0 -0
  77. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/defaultconfig.toml +0 -0
  78. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/display.py +0 -0
  79. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/loader.py +0 -0
  80. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/performance.py +0 -0
  81. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/config/py.typed +0 -0
  82. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/logging/__init__.py +0 -0
  83. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/logging/py.typed +0 -0
  84. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/memory/__init__.py +0 -0
  85. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/adapters/py.typed +0 -0
  86. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/application/__init__.py +0 -0
  87. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/application/py.typed +0 -0
  88. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/composition/__init__.py +0 -0
  89. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/composition/py.typed +0 -0
  90. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/conf_igittigitt.py +0 -0
  91. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/domain/__init__.py +0 -0
  92. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/domain/enums.py +0 -0
  93. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/domain/py.typed +0 -0
  94. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/entry.py +0 -0
  95. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/igittigitt.py +0 -0
  96. {igittigitt-2.2.2 → igittigitt-2.2.4}/src/igittigitt/py.typed +0 -0
  97. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/default_pattern/.config/git/ignore +0 -0
  98. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/default_pattern/git/ignore +0 -0
  99. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/.test_gitignore +0 -0
  100. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/.test_gitignore_empty +0 -0
  101. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/.test_venv/some_file.txt +0 -0
  102. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/not_excluded/.test_gitignore +0 -0
  103. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/not_excluded/excluded/excluded.txt +0 -0
  104. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/not_excluded/excluded_not/sub_excluded.txt +0 -0
  105. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/not_excluded/excluded_not.txt +0 -0
  106. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/not_excluded/not_excluded2/sub_excluded.txt +0 -0
  107. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/not_excluded/not_excluded2.txt +0 -0
  108. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/not_excluded.txt +0 -0
  109. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/test__pycache__/.test_gitignore +0 -0
  110. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/test__pycache__/excluded/excluded/excluded.txt +0 -0
  111. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/test__pycache__/some_file.txt +0 -0
  112. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example/test__pycache__/test +0 -0
  113. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example_negation/foo/bar/file.txt +0 -0
  114. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/example_negation/foo/other/file.txt +0 -0
  115. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/target_expected/.test_gitignore +0 -0
  116. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/target_expected/not_excluded/.test_gitignore +0 -0
  117. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/target_expected/not_excluded/excluded_not.txt +0 -0
  118. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/target_expected/not_excluded/not_excluded2.txt +0 -0
  119. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/target_expected/not_excluded.txt +0 -0
  120. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_cli.py +0 -0
  121. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_git_compat.py +0 -0
  122. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_include.py +0 -0
  123. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_memory.py +0 -0
  124. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_metadata.py +0 -0
  125. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_perf_config.py +0 -0
  126. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_pytest.py +0 -0
  127. {igittigitt-2.2.2 → igittigitt-2.2.4}/tests/test_shell_pipe.py +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "igittigitt",
3
3
  "description": "Parse .gitignore files and filter/whitelist large numbers of paths in .gitignore style from Python or the shell. Ships the python-gitignore skill: install, configure (config-deploy + config.d overrides), library API (IgnoreParser/IncludeParser, shutil.copytree filter), the CLI, and bash piping.",
4
- "version": "1.0.1",
4
+ "version": "2.2.6",
5
5
  "author": {
6
6
  "name": "bitranox"
7
7
  },
@@ -11,83 +11,6 @@ PYPI_API_TOKEN=
11
11
  GITHUB_TOKEN=
12
12
 
13
13
 
14
- # ==============================================================================
15
- # Email Configuration (SMTP)
16
- # ==============================================================================
17
- #
18
- # Naming Convention
19
- # -----------------
20
- # Format: <SECTION>__<KEY>=value (double underscore separates section from key)
21
- # Lists use comma-separated values, booleans use true/false, floats use decimal notation.
22
- #
23
- # Full environment variable (outside .env):
24
- # IGITTIGITT___EMAIL__<KEY>=value
25
- # (triple underscore separates the application slug from the section)
26
-
27
- # Purpose: SMTP servers for email delivery, tried in order (host:port, comma-separated)
28
- # Type: comma-separated strings in "host[:port]" format
29
- # Default: (empty, must be configured to send email)
30
- # Environment Variable: IGITTIGITT___EMAIL__SMTP_HOSTS=smtp.example.com:587
31
- # .env: EMAIL__SMTP_HOSTS=smtp.example.com:587
32
- # EMAIL__SMTP_HOSTS=smtp.example.com:587
33
-
34
- # Purpose: Default sender address for outgoing emails
35
- # Type: string (valid email address)
36
- # Default: (empty, must be configured or passed via --from)
37
- # Environment Variable: IGITTIGITT___EMAIL__FROM_ADDRESS=noreply@example.com
38
- # .env: EMAIL__FROM_ADDRESS=noreply@example.com
39
- # EMAIL__FROM_ADDRESS=noreply@example.com
40
-
41
- # Purpose: Default recipient addresses (comma-separated)
42
- # Type: comma-separated strings (valid email addresses)
43
- # Default: (empty, must be configured or passed via --to)
44
- # Environment Variable: IGITTIGITT___EMAIL__RECIPIENTS=admin@example.com,ops@example.com
45
- # .env: EMAIL__RECIPIENTS=admin@example.com,ops@example.com
46
- # EMAIL__RECIPIENTS=admin@example.com,ops@example.com
47
-
48
- # Purpose: SMTP authentication username
49
- # Type: string
50
- # Default: (empty, no authentication)
51
- # Environment Variable: IGITTIGITT___EMAIL__SMTP_USERNAME=noreply@example.com
52
- # .env: EMAIL__SMTP_USERNAME=noreply@example.com
53
- # EMAIL__SMTP_USERNAME=noreply@example.com
54
-
55
- # Purpose: SMTP authentication password
56
- # Type: string
57
- # Default: (empty, no authentication)
58
- # Environment Variable: IGITTIGITT___EMAIL__SMTP_PASSWORD=your-app-password
59
- # .env: EMAIL__SMTP_PASSWORD=your-app-password
60
- # EMAIL__SMTP_PASSWORD=your-app-password
61
-
62
- # Purpose: Enable STARTTLS negotiation before authentication
63
- # Type: boolean (true/false)
64
- # Default: true
65
- # Environment Variable: IGITTIGITT___EMAIL__USE_STARTTLS=true
66
- # .env: EMAIL__USE_STARTTLS=true
67
- # EMAIL__USE_STARTTLS=true
68
-
69
- # Purpose: Socket timeout in seconds for SMTP operations
70
- # Type: float
71
- # Default: 30.0
72
- # Environment Variable: IGITTIGITT___EMAIL__TIMEOUT=30.0
73
- # .env: EMAIL__TIMEOUT=30.0
74
- # EMAIL__TIMEOUT=30.0
75
-
76
- # Purpose: Raise FileNotFoundError when attachment files are missing
77
- # Type: boolean (true/false)
78
- # Default: true
79
- # Environment Variable: IGITTIGITT___EMAIL__RAISE_ON_MISSING_ATTACHMENTS=true
80
- # .env: EMAIL__RAISE_ON_MISSING_ATTACHMENTS=true
81
- # EMAIL__RAISE_ON_MISSING_ATTACHMENTS=true
82
-
83
- # Purpose: Raise ValueError when recipient addresses are invalid
84
- # Type: boolean (true/false)
85
- # Default: true
86
- # Environment Variable: IGITTIGITT___EMAIL__RAISE_ON_INVALID_RECIPIENT=true
87
- # .env: EMAIL__RAISE_ON_INVALID_RECIPIENT=true
88
- # EMAIL__RAISE_ON_INVALID_RECIPIENT=true
89
-
90
-
91
14
  # ==============================================================================
92
15
  # Logging Configuration (lib_log_rich)
93
16
  # ==============================================================================
@@ -95,7 +18,14 @@ GITHUB_TOKEN=
95
18
  # Naming Convention
96
19
  # -----------------
97
20
  # Format: <SECTION>__<KEY>=value (double underscore separates section from key)
98
- # Strings need no quotes. Booleans: true/false. Floats: 1.0. Lists: comma-separated.
21
+ # Strings need no quotes. An unquoted value is converted (in .env and in the environment
22
+ # alike): true/false become booleans, null/none become None, a JSON array or object is parsed,
23
+ # and a number becomes a number only when it converts back to the same text (007 and 1.50 stay
24
+ # text); quote a .env value to keep it as text. A list is a JSON array and a table a JSON
25
+ # object, written UNQUOTED in .env (LIB_LOG_RICH__CONSOLE_STYLES={"ERROR":"bold red"}), or one
26
+ # key per entry (LIB_LOG_RICH__SCRUB_PATTERNS__API_KEY=.+). A comma-separated value is NOT a
27
+ # list or a table: it arrives as ONE string and is refused. In the environment, quote a JSON
28
+ # value for the shell: IGITTIGITT___LIB_LOG_RICH__RATE_LIMIT='[100,60]'.
99
29
  #
100
30
  # Full environment variable (outside .env):
101
31
  # IGITTIGITT___LIB_LOG_RICH__<KEY>=value
@@ -157,12 +87,12 @@ GITHUB_TOKEN=
157
87
  # .env: LIB_LOG_RICH__CONSOLE_THEME=dark
158
88
  # LIB_LOG_RICH__CONSOLE_THEME=dark
159
89
 
160
- # Purpose: Override Rich styles per severity level (comma-separated LEVEL=style pairs)
161
- # Type: comma-separated LEVEL=style pairs
90
+ # Purpose: Override Rich styles per severity level
91
+ # Type: table of LEVEL = style (a JSON object keeps the level names upper case)
162
92
  # Default: (empty, uses theme defaults)
163
- # Environment Variable: IGITTIGITT___LIB_LOG_RICH__CONSOLE_STYLES=DEBUG=dim,ERROR=bold red
164
- # .env: LIB_LOG_RICH__CONSOLE_STYLES=DEBUG=dim,ERROR=bold red
165
- # LIB_LOG_RICH__CONSOLE_STYLES=DEBUG=dim,ERROR=bold red
93
+ # Environment Variable: IGITTIGITT___LIB_LOG_RICH__CONSOLE_STYLES='{"DEBUG":"dim","ERROR":"bold red"}'
94
+ # .env: LIB_LOG_RICH__CONSOLE_STYLES={"DEBUG":"dim","ERROR":"bold red"}
95
+ # LIB_LOG_RICH__CONSOLE_STYLES={"DEBUG":"dim","ERROR":"bold red"}
166
96
 
167
97
  # Purpose: Force ANSI color output even when stderr is not a TTY
168
98
  # Type: boolean (true/false)
@@ -210,12 +140,12 @@ GITHUB_TOKEN=
210
140
  # .env: LIB_LOG_RICH__ENABLE_GRAYLOG=true
211
141
  # LIB_LOG_RICH__ENABLE_GRAYLOG=true
212
142
 
213
- # Purpose: Graylog server address (host:port)
214
- # Type: string "host:port"
143
+ # Purpose: Graylog server address
144
+ # Type: JSON array [host, port]; a "host:port" string is refused
215
145
  # Default: (none, must be configured to enable Graylog)
216
- # Environment Variable: IGITTIGITT___LIB_LOG_RICH__GRAYLOG_ENDPOINT=graylog.example.com:12201
217
- # .env: LIB_LOG_RICH__GRAYLOG_ENDPOINT=graylog.example.com:12201
218
- # LIB_LOG_RICH__GRAYLOG_ENDPOINT=graylog.example.com:12201
146
+ # Environment Variable: IGITTIGITT___LIB_LOG_RICH__GRAYLOG_ENDPOINT='["graylog.example.com",12201]'
147
+ # .env: LIB_LOG_RICH__GRAYLOG_ENDPOINT=["graylog.example.com",12201]
148
+ # LIB_LOG_RICH__GRAYLOG_ENDPOINT=["graylog.example.com",12201]
219
149
 
220
150
  # Purpose: Transport protocol for Graylog (tcp, udp); TLS only supported with tcp
221
151
  # Type: string: "tcp", "udp"
@@ -309,19 +239,19 @@ GITHUB_TOKEN=
309
239
 
310
240
  # --- Security & Payload ---
311
241
 
312
- # Purpose: Regex patterns to redact sensitive data (comma-separated field=regex pairs)
313
- # Type: comma-separated field=regex pairs
314
- # Default: password=.+,secret=.+,token=.+
315
- # Environment Variable: IGITTIGITT___LIB_LOG_RICH__SCRUB_PATTERNS=password=.+,secret=.+,token=.+,api_key=.+
316
- # .env: LIB_LOG_RICH__SCRUB_PATTERNS=password=.+,secret=.+,token=.+,api_key=.+
317
- # LIB_LOG_RICH__SCRUB_PATTERNS=password=.+,secret=.+,token=.+,api_key=.+
242
+ # Purpose: Regex patterns to redact sensitive data (field = regex)
243
+ # Type: table of field = regex; merged with the defaults
244
+ # Default: password = .+, secret = .+, token = .+
245
+ # Environment Variable, one key per field: IGITTIGITT___LIB_LOG_RICH__SCRUB_PATTERNS__API_KEY=.+
246
+ # .env, one key per field: LIB_LOG_RICH__SCRUB_PATTERNS__API_KEY=.+
247
+ # LIB_LOG_RICH__SCRUB_PATTERNS__API_KEY=.+
318
248
 
319
- # Purpose: Throttle log emissions (MAX_EVENTS:WINDOW_SECONDS)
320
- # Type: string "max_events:window_seconds"
249
+ # Purpose: Throttle log emissions (max_events per window_seconds)
250
+ # Type: JSON array [max_events, window_seconds]; a "100:60" string is refused
321
251
  # Default: (none, no rate limiting)
322
- # Environment Variable: IGITTIGITT___LIB_LOG_RICH__RATE_LIMIT=100:60
323
- # .env: LIB_LOG_RICH__RATE_LIMIT=100:60
324
- # LIB_LOG_RICH__RATE_LIMIT=100:60
252
+ # Environment Variable: IGITTIGITT___LIB_LOG_RICH__RATE_LIMIT='[100,60]'
253
+ # .env: LIB_LOG_RICH__RATE_LIMIT=[100,60]
254
+ # LIB_LOG_RICH__RATE_LIMIT=[100,60]
325
255
 
326
256
  # --- Payload Limits ---
327
257
 
@@ -47,7 +47,8 @@ tmp_*
47
47
  __pycache__/
48
48
  src/igittigitt/__pycache__/
49
49
  *.py[cod]
50
- *$py.class # other compiled artifacts
50
+ # other compiled artifacts
51
+ *$py.class
51
52
 
52
53
  # pytest, coverage, MyPy, Hypothesis
53
54
  .pytest_cache/
@@ -73,12 +74,15 @@ coverage.xml
73
74
  # source/package builds
74
75
  build/
75
76
  dist/
76
- *.egg-info/ # metadata for pip installs
77
+ # metadata for pip installs
78
+ *.egg-info/
77
79
  *.egg
78
80
  *.whl
79
81
  pip-wheel-metadata/
80
- *.manifest # PyInstaller metadata
81
- *.spec # PyInstaller spec files
82
+ # PyInstaller metadata
83
+ *.manifest
84
+ # PyInstaller spec files
85
+ *.spec
82
86
 
83
87
  # Eggs, parts, wheels, sdist, etc.
84
88
  develop-eggs/
@@ -0,0 +1,242 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file following
4
+ the [Keep a Changelog](https://keepachangelog.com/) format.
5
+
6
+ This project adheres to semantic versioning: MAJOR for incompatible API changes,
7
+ MINOR for backwards-compatible functionality, PATCH for backwards-compatible fixes.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [2.2.4] 2026-10-08 10:19:56
12
+
13
+ ### Fixed
14
+
15
+ - **`build_testing()` can run a command.** Every CLI command except `check` and `filter` binds job
16
+ context onto the process-global lib_log_rich runtime, but the testing composition's
17
+ `init_logging` was a no-op, so `info`, `config` and the deploy commands raised
18
+ `RuntimeError('lib_log_rich.init() must be called before using the logging API')` under
19
+ `build_testing()`. The in-memory initializer now starts a quiet runtime: journald, event log,
20
+ Graylog and the queue off, console at ERROR, and no `.env` loading.
21
+ - **`main()` keeps the exit code of a command that exits through click's context.** rich_click's
22
+ `main()` returns the code of a `ctx.exit(N)` under `standalone_mode=False`; `main()` discarded
23
+ that return value and reported 0, and its `except click.exceptions.Exit` branch could never
24
+ fire. It now returns the code (exit code change: such a command exits N instead of 0).
25
+ - **Conflicting `--set` overrides are a usage error.** `--set a.b=1 --set a.b.c=2` escaped as a
26
+ `TypeError` from the override nesting, and the other order, `--set a.b.c=2 --set a.b=1`,
27
+ silently dropped the earlier override. All `--set` values are now checked together and a key
28
+ that one gives a value and another puts a key under is refused, naming both (exit code change:
29
+ exit 2 for every command, where the second order used to exit 0). The same key given twice still
30
+ takes the last value; `a.b` and `a.bc` stay siblings.
31
+ - **A broken configuration file no longer disables every command.** The root group loaded the
32
+ configuration before any subcommand option was parsed and let a load error escape, so a
33
+ malformed `config.toml`, a `.env` that is not UTF-8 or an unreadable file made every command,
34
+ `--help` and `config-deploy` (the command that replaces the file) exit 1. The root now records
35
+ the failure (`adapters/cli/config_load.py`): `config`, `check` and `filter`, which read the
36
+ configuration, refuse with exit 78 and one line naming the file, after the loader's traceback
37
+ with `--traceback`; `info`, `config-deploy`, `config-generate-examples` and help still run
38
+ (exit code change: 78 or 0 where every command exited 1). What the command line gets wrong is
39
+ checked before loading, so a broken file cannot hide it: a malformed or conflicting `--set` or
40
+ an invalid `--profile` name is a usage error (exit 2) for every command, where an invalid
41
+ root `--profile` used to exit 22 and an invalid `config-deploy --profile` failed inside the
42
+ deploy with exit 1. Any other exception from the loader is a bug and propagates as one.
43
+ `config --profile X` reloads with the root's `--env-file` instead of searching for another
44
+ `.env`.
45
+ - **Logging takes only `LOG_*` lines from a `.env`.** `init_logging` called lib_log_rich's
46
+ `enable_dotenv()`, which copied every line of the nearest `.env` into the process environment,
47
+ so a later configuration load (`config --profile`, the deploy's permission read) took an
48
+ app-prefixed `.env` line for the environment layer: a prefixed `default_permissions` line in the
49
+ working directory's `.env` refused `config-deploy`, and a prefixed `[performance]` value showed
50
+ up in `config --profile`. Logging now copies only `LOG_*` lines, never over a variable that is
51
+ already set, from `--env-file` when given, otherwise from the nearest `.env` up to the project
52
+ root, without `chdir` and passing over unreadable directories; a `.env` that is not UTF-8 no
53
+ longer stops logging.
54
+ - **An invalid `[lib_log_rich]` value is a configuration failure, not a crash of every command.**
55
+ A value lib_log_rich refuses (a wrong type, or its own range checks such as `queue_maxsize = 0`)
56
+ stopped every command with exit 22 and pydantic's multi-line report. It is now recorded like a load
57
+ failure: logging starts with its defaults, `config`, `check` and `filter` exit 78 with one
58
+ `Error:` line per problem naming the key, never the value, and `info`, `config-deploy` and help
59
+ run (exit code change). The `InitLogging` port takes `dotenv_path`, and the root types the
60
+ services factory instead of ignoring the type.
61
+ - **A refused `LOG_*` variable no longer disables every command (exit code change).** A value
62
+ lib_log_rich refuses in a `LOG_*` variable, set in the environment or in the `.env` logging reads
63
+ (`LOG_CONSOLE_LEVEL=bogus`), made every command but a bare `--help` exit 22 with lib_log_rich's
64
+ `ValueError`. Logging now falls back to its defaults with every `LOG_*` variable hidden for that
65
+ start (and put back afterwards), so only `config`, `check` and `filter` exit 78, and `info`,
66
+ `config-deploy` and the help of every command run with exit 0. The 78 carries lib_log_rich's own
67
+ message, `Error: lib_log_rich: Unknown log level: 'bogus'`, which may name neither the variable
68
+ nor where it was set. A refused `[lib_log_rich]` value still leaves every valid `LOG_*` variable
69
+ in force for the fallback: only a refused variable hides them.
70
+ - **`config-deploy` leaves every permission decision to lib_layered_config.** The command read
71
+ `[lib_layered_config.default_permissions]` from its own merged configuration, so a `.env` found
72
+ upward from the working directory decided or blocked a system deploy, and of the whole section
73
+ only `enabled` was ever used: the configured per-layer modes never reached a deploy, and a
74
+ section that was not a table crashed with `AttributeError`. It now passes its options and any
75
+ `--set` of that section to `deploy_config` unchanged; the library reads the section from the
76
+ bundled defaults, the files the deploy does not overwrite and the environment, never from
77
+ `.env`, and applies each target's own layer modes. A refused setting exits 78 with one `Error:`
78
+ line per problem naming the key and its source, plus a hint in the CLI's spelling when both mode
79
+ options would get past it (exit code change: 78 where an invalid section used to exit 1 or
80
+ deploy). "Deployed configuration" is logged after the deploy, and the report says
81
+ "(permissions not set)" only for an explicit `--no-permissions`.
82
+ - **The documented `.env` and environment syntax for lists and tables works.** `.env.example`,
83
+ `90-logging.toml` and `CONFIG.md` showed comma-separated `LEVEL=style` and `field=regex` pairs
84
+ for `console_styles` and `scrub_patterns` and `host:port` / `100:60` strings for
85
+ `graylog_endpoint` and `rate_limit`; each arrives as ONE string and is refused. They now show
86
+ a JSON array or object (unquoted in `.env`, shell-quoted in the environment) or one key per entry
87
+ (`LIB_LOG_RICH__SCRUB_PATTERNS__API_KEY=.+`), and say how an unquoted value is converted.
88
+ - **Tests no longer depend on test order, colour or terminal width.** An autouse fixture shuts the
89
+ logging runtime down and restores the root logger's handlers, level and propagate flag after
90
+ every test (production `init_logging` attaches a stdlib handler and raises the root level, which
91
+ `runtime.shutdown()` does not undo). A second one pins rich-click's colour and width globals,
92
+ which it reads from the terminal and from GITHUB_ACTIONS once at import, so a usage-error box
93
+ no longer wraps a message the test looks for on a narrow terminal.
94
+
95
+ ### Changed
96
+
97
+ - **Requires lib_layered_config 7.0.1**, whose `deploy_config` reads and validates the permission
98
+ section itself. An unquoted `.env` value now converts like the environment layer, so
99
+ `ENABLED=false` arrives as the boolean `false`.
100
+ - **`--no-permissions` together with `--dir-mode` or `--file-mode` is a usage error** (exit 2):
101
+ a mode cannot be applied while permission setting is off.
102
+ - **`python-dotenv` is a declared dependency** (logging reads the `LOG_*` lines of a `.env` with
103
+ it), and lib_log_rich is required at 6.3.9, the version the logging refusal is tested against.
104
+ - **`click` is a declared dependency.** The package imports it directly (`adapters/cli/main.py`)
105
+ but only had it through rich-click. A new test fails when a module imported at run time is
106
+ missing from `[project].dependencies`.
107
+
108
+ ### Removed
109
+
110
+ - `adapters.config.permissions` (`PermissionDefaults`, `parse_mode`, `get_permission_defaults`,
111
+ `get_modes_for_target`): lib_layered_config reads and validates the section now. `parse_mode`
112
+ silently fell back to the default on a malformed value, and `get_modes_for_target` had no
113
+ production caller, which is why the configured modes never took effect.
114
+
115
+ ### Security
116
+
117
+ - **`config-deploy` refuses unsafe and malformed modes.** `--dir-mode -1` passed the unbounded
118
+ octal parser and reached the deploy as mode -1, and `--dir-mode 777` or `--file-mode 666` were
119
+ applied as given. `--dir-mode`/`--file-mode` and the configured modes are now parsed by
120
+ lib_layered_config's `DeployMode`: only a plain octal literal inside 0..0o7777 is accepted, and
121
+ setuid/setgid/sticky, group or world write, an execute bit on a file and a mode that takes away
122
+ the owner's access are refused (exit 2 for an option, 78 for a configured value). A bare integer
123
+ in the configuration is refused because it is read as decimal (`400` is `0o620`).
124
+
125
+ ## [2.2.3] 2026-07-30 18:08:55
126
+
127
+ ### Changed
128
+
129
+ - **The shipped skill's plugin version now tracks the package version.** bmk 3.14.0 raises
130
+ `.claude-plugin/plugin.json` to the package version on bump, push and release, and never lowers
131
+ it. An install re-fetches a skill only when that version changes, so the two numbers drifting
132
+ apart meant a skill edit could ship to nobody. No functional change to the library.
133
+
134
+ ## [2.2.2] 2026-07-24 18:15:33
135
+
136
+ ### Fixed
137
+ - Latest `ruff` (0.16+) widened default rule coverage; the project's curated
138
+ `[tool.ruff.lint]` blanket ignore list (`RUF002`, `RUF022`, `PLC0415`, `TC001-3`,
139
+ `TC006`) is removed and every violation it was masking is fixed at the root
140
+ instead: keyword-only params on the six flagged Click callbacks/helpers
141
+ (`build_ignore_parser`, `cli_check`, `cli_filter`, `cli_config_deploy`,
142
+ `_execute_deploy`, `create_rule_variations`) rather than adding rule
143
+ suppressions, an ASCII hyphen in place of an ambiguous en-dash in a docstring,
144
+ and top-level (rather than deferred) imports where no circular-import or
145
+ lazy-load reason applied. A doctest in `adapters/config/permissions.py` that
146
+ relied on `DeployTarget` moving out of `TYPE_CHECKING` is made self-contained.
147
+ - Deferred imports in `tests/*.py` are now a declared per-file ignore
148
+ (`PLC0415`) rather than silently unflagged - they are a deliberate test idiom
149
+ (exercising import/cache behaviour), not an oversight.
150
+
151
+ ### Changed
152
+ - CI (template-managed, from `default_cicd_public`): `pip-audit` now audits this
153
+ project's own resolved dependency tree instead of the whole `--system`
154
+ environment, so runner-image packages we neither declare nor ship (`setuptools`,
155
+ `pip`, `wheel`) can no longer report findings against us. `actions/cache` moves
156
+ to v6 in the same distribution.
157
+ - Dev dependency floor: `httpx2>=2.7.0` (was `>=2.6.0`).
158
+ - `[tool.ruff.lint.flake8-type-checking].runtime-evaluated-base-classes` now
159
+ lists `pydantic.BaseModel`, so the `TC00x` autofix leaves Pydantic model base
160
+ classes importable at runtime instead of moving them into `TYPE_CHECKING`.
161
+ - `skills/python-gitignore/SKILL.md` example code blocks reformatted to match
162
+ ruff's latest formatting (cosmetic only; no semantic change to the skill).
163
+ `.claude-plugin/plugin.json` bumped to `1.0.1` to ship the change.
164
+
165
+ ## [2.2.1] - 2026-07-14
166
+
167
+ A maintenance release: no library or CLI behaviour changes beyond the profile
168
+ validation fix below. The public API is unchanged.
169
+
170
+ ### Fixed
171
+ - `validate_profile()` raises `ValueError` for every invalid profile again. Newer
172
+ `lib_layered_config` raises its own `ValidationError`, which escaped the
173
+ documented `ValueError` contract and slipped past callers' `except ValueError`
174
+ guards; the dependency's exception is now normalised back to `ValueError`.
175
+ - Pyright strict passes against click 8.3+. The `argument` decorator's re-exported
176
+ signature carries an unknown `type` parameter, so it joins `option` and
177
+ `version_option` behind a fully-typed wrapper in `adapters/cli/typed_click`
178
+ instead of the rule being disabled.
179
+
180
+ ### Documentation
181
+ - `ExitCode`'s docstring no longer claims the application never exits with a
182
+ signal code. `BROKEN_PIPE` (141) is raised directly by `check` and `filter` when
183
+ their output pipe closes early; only 130 and 143 are informational.
184
+ - `INSTALL.md`: the install-from-tag example pointed at `v1.1.0`, a tag that was
185
+ never cut, and the closing line named the installed command twice. Both fixed.
186
+
187
+ ### Changed
188
+ - `codecov-cli` is disabled as a dev dependency. It pins `click<8.3.0`, which both
189
+ held click at a version with a known `click.edit()` command-injection flaw and
190
+ silently backtracked `bmk` to 3.1.7. CI uploads coverage via
191
+ `codecov/codecov-action`, so nothing is lost.
192
+ - The bmk-managed `Makefile` regenerates at 3.6.0 (from 2.9.5), and bmk now lives
193
+ in a per-project `.venv-bmk` tool environment rather than a shared one. This is
194
+ the first bmk update the repo has picked up since the `click` pin was removed.
195
+ - The `[tool.pip-audit]` ignore list is now empty. Every entry it carried had
196
+ become inert (fixed upstream, or naming a package absent from the dependency
197
+ tree), and a stale entry silently suppresses any future advisory filed under the
198
+ same id.
199
+
200
+ ## [2.2.0] - 2026-06-26
201
+
202
+ Full rebuild of the project scaffolding on the `bitranox_template_py_cli` template
203
+ (src layout, hatchling, ruff + pyright strict, clean-architecture CLI). The public
204
+ library API (`import igittigitt; igittigitt.IgnoreParser`) is unchanged.
205
+
206
+ ### Added
207
+ - **`IncludeParser`** - a directory-aware include / whitelist mode with
208
+ `shutil_include` for use as a `shutil.copytree` filter.
209
+ - **Scriptable CLI** built on rich-click: `check` (mirrors `git check-ignore`,
210
+ including `-v`) and `filter` (a streaming Unix filter reading paths from stdin),
211
+ with newline- or NUL-separated (`-z`) I/O and clean SIGPIPE handling. Plus
212
+ `info`, `config`, `config-deploy`, `config-generate-examples` and `logdemo`.
213
+ - **Layered configuration** (`lib_layered_config`) and **structured logging**
214
+ (`lib_log_rich`). All engine/CLI tuning knobs are exposed and documented in the
215
+ `[performance]` config section (`dir_cache_max`, `pattern_cache_max`,
216
+ `stdin_chunk_bytes`, `max_token_bytes`) and overridable via file / `.env` / env
217
+ var / `--set`.
218
+ - Differential test suite comparing igittigitt against real `git check-ignore`,
219
+ plus memory-boundedness, include, config-knob and real-pipe/SIGPIPE tests.
220
+
221
+ ### Changed
222
+ - **Matching is now 100% git-compatible.** The engine performs a single ordered
223
+ pass (last-matching-pattern-wins) with correct negations, parent-directory
224
+ exclusion blocking re-inclusion, and per-directory `.gitignore` precedence. This
225
+ resolves the long-standing negation and nested-precedence limitations.
226
+ - Matching is memory-bounded: per-path cost is `O(depth x rules)` and memory scales
227
+ with the number of rules, not the number of files.
228
+ - Matching is ~5x faster: globs are translated and compiled to `re.Pattern` once
229
+ (cached), and the ancestor-directory decision is memoized in a bounded LRU - both
230
+ keep memory bounded and preserve 100% git compatibility.
231
+ - Dropped the `attrs` runtime dependency in favour of `dataclasses` and `pydantic`.
232
+ - Python baseline raised to **3.10+** (was 3.8).
233
+ - CLI migrated from `click` to `rich-click` with `lib_cli_exit_tools`.
234
+ - Documentation moved from reStructuredText to Markdown.
235
+
236
+ ### Removed
237
+ - The old two-phase (sort-and-deduplicate) rule evaluation that broke negations.
238
+
239
+ ## [2.1.5] - 2024-10-16
240
+ - Final release on the previous (PizzaCutter / setuptools-scm) scaffolding.
241
+ - Earlier history: a spec-compliant gitignore parser whose negation and nested
242
+ precedence handling was known to be incomplete (see this release's README).
@@ -0,0 +1,131 @@
1
+ # Configuration
2
+
3
+ igittigitt is configured with [lib_layered_config](https://github.com/bitranox/lib_layered_config).
4
+ Settings are merged from several layers; later layers win:
5
+
6
+ ```
7
+ bundled defaults -> app -> host -> user -> .env file -> environment variables -> --set
8
+ ```
9
+
10
+ - **Inspect** the merged result (with provenance for each value): `igittigitt config`
11
+ - **Deploy** editable copies into the standard config directories: `igittigitt config-deploy`
12
+ - **Examples** to copy and edit: `igittigitt config-generate-examples`
13
+
14
+ ## Where config files live
15
+
16
+ | Layer | Linux | macOS |
17
+ |-------|--------------------------------------------------|--------------------------------------------------------------|
18
+ | app | `/etc/xdg/igittigitt/config.*` (+ `config.d/*`) | `/Library/Application Support/bitranox/igittigitt/config.*` |
19
+ | user | `~/.config/igittigitt/config.*` (+ `config.d/*`) | `~/Library/Application Support/bitranox/igittigitt/config.*` |
20
+
21
+ `.env` files (`SECTION__KEY=value`, found by searching upward from the working directory)
22
+ and environment variables (`IGITTIGITT___SECTION__KEY=value`) override the files. A single
23
+ value can be overridden per run with `--set SECTION.KEY=VALUE`.
24
+
25
+ Value coercion (in `.env` and the environment alike): an unquoted `true`/`false` -> bool,
26
+ `null`/`none` -> None, a number -> int/float only when it converts back to the same text (`007`
27
+ stays text), and a JSON array or object is parsed. Quote a `.env` value to keep it as text. A
28
+ list or table is that JSON written unquoted in `.env` (quoted for the shell in the environment),
29
+ or one key per entry (`LIB_LOG_RICH__SCRUB_PATTERNS__API_KEY=.+`); a comma-separated value is ONE
30
+ string, not a list.
31
+
32
+ A configuration file that cannot be loaded (invalid TOML, not UTF-8, unreadable) does not stop
33
+ every command. `config`, `check` and `filter`, which read the configuration, refuse with exit 78
34
+ and one line naming the file (`--traceback` adds the loader's traceback); `info`,
35
+ `config-deploy` (the command that replaces the broken file), `config-generate-examples` and
36
+ `--help` still run. A malformed or conflicting `--set` and an invalid `--profile` name are usage
37
+ errors (exit 2) for every command, whether or not the configuration loads.
38
+
39
+ ## Settings reference
40
+
41
+ The authoritative, fully-commented reference is the set of bundled TOML files (one key per
42
+ documented block); `config-deploy` / `config-generate-examples` write them out:
43
+
44
+ - `defaultconfig.d/50-performance.toml` - the `[performance]` engine/CLI tuning knobs.
45
+ - `defaultconfig.d/90-logging.toml` - the `[lib_log_rich]` logging settings.
46
+ - `defaultconfig.d/40-layered-config.toml` - config-deploy permission defaults.
47
+
48
+ ### `[performance]`
49
+
50
+ Only affects speed and cache memory, never which paths match. All defaults keep memory
51
+ bounded and were chosen by measurement.
52
+
53
+ | Key | Default | Meaning |
54
+ |---------------------|-----------|-----------------------------------------------------------------------------------------------------------------------|
55
+ | `dir_cache_max` | `8192` | Directory-decision LRU capacity per parser (`0` disables). Main speed-up on trees; memory `O(this)`, not `O(#files)`. |
56
+ | `pattern_cache_max` | `4096` | Process-wide compiled-regex cache capacity (keyed by distinct pattern). |
57
+ | `stdin_chunk_bytes` | `65536` | Stdin read granularity for the streaming commands. |
58
+ | `max_token_bytes` | `1048576` | Per-token safety bound; a separator-less token larger than this is rejected, not buffered unbounded. |
59
+
60
+ ```bash
61
+ igittigitt --set performance.dir_cache_max=32768 filter -C repo
62
+ IGITTIGITT___PERFORMANCE__MAX_TOKEN_BYTES=4096 igittigitt check -C repo --stdin < paths
63
+ ```
64
+
65
+ ### `[lib_layered_config.default_permissions]`
66
+
67
+ The Unix modes `config-deploy` gives the directories and files it writes (POSIX only), per layer:
68
+ `app_directory`/`app_file`, `host_directory`/`host_file`, `user_directory`/`user_file`, and
69
+ `enabled`. Built-in modes are 755/644 for app and host, 700/600 for user.
70
+
71
+ `config-deploy` decides none of this itself: it passes its options and any `--set` of this
72
+ section to lib_layered_config, which applies each target's own layer modes. The library reads the
73
+ section from the bundled defaults, the configuration files this deploy does not overwrite and the
74
+ environment, with the `--set` values laid over them. It never reads `.env` for it (neither one
75
+ found from the working directory nor an explicit `--env-file`), so a `.env` can neither change a
76
+ deployed mode nor block a deploy. `--dir-mode`/`--file-mode` override the configured modes for
77
+ every target. Without `--permissions`/`--no-permissions`, `enabled` decides; `enabled = false`
78
+ behaves like `--no-permissions`, and `--permissions` sets the modes anyway. `--no-permissions`
79
+ cannot be combined with a mode option (exit 2).
80
+
81
+ A mode is a plain octal STRING (`"0o750"`, `"750"`) inside 0..0o7777, never setuid/setgid/sticky,
82
+ group or world write, an execute bit on a file, or less than owner rwx (directory) / rw (file).
83
+ A bare integer is refused because TOML, the environment and `--set` all read `400` as the DECIMAL
84
+ number 400, which is `0o620`. Quote it in TOML (`user_file = "640"`); in an environment variable
85
+ or `--set` use the `0o` prefix (`0o640`). `enabled` must be a real boolean; `"no"`, `"off"`, `0`
86
+ or `1` are refused. An unknown key is refused rather than ignored. `--dir-mode`/`--file-mode`
87
+ follow the same rules and are refused as a usage error (exit 2). Any refused setting stops
88
+ `config-deploy` before it writes anything, with exit 78, one line per problem naming the key and
89
+ where it was set, and a hint, for example:
90
+
91
+ ```text
92
+ Error: lib_layered_config.default_permissions.user_file: a bare integer is read as decimal (400 = 0o620); write the mode as an octal string: "0o640" (quoted) in a file, 0o640 in the environment or a runtime override (such as an application's --set) (source: env)
93
+ Hint: to deploy anyway, pass both --dir-mode and --file-mode (the built-in modes are 700 and 600 for user, 755 and 644 for app and host); --no-permissions also deploys, but leaves every mode to the umask, which can make a user file that holds secrets readable by other accounts.
94
+ ```
95
+
96
+ A refused `--set` of the section names `(source: override)` and has no hint, since no option gets
97
+ past it: fix or drop the `--set`. The files a deploy writes are never read for it, so
98
+ `config-deploy --force` replaces a destination that carries a bad value, or does not parse at all,
99
+ without further options.
100
+
101
+ ### `[lib_log_rich]`
102
+
103
+ Logging configuration (console level/theme, journald/eventlog/Graylog backends, queueing,
104
+ scrubbing, payload limits). Each key is documented inline in `90-logging.toml`. Common ones:
105
+
106
+ | Key | Example | Meaning |
107
+ |---------------------------------------|----------------------------|----------------------------------------|
108
+ | `console_level` | `DEBUG` | Minimum level shown on the console. |
109
+ | `console_theme` | `dark` | Console colour theme. |
110
+ | `enable_journald` | `true` | Also emit to systemd-journald (Linux). |
111
+ | `enable_graylog` / `graylog_endpoint` | `true` / `["host", 12201]` | Ship logs to Graylog (GELF). |
112
+
113
+ ```bash
114
+ igittigitt --set lib_log_rich.console_level=DEBUG info
115
+ IGITTIGITT___LIB_LOG_RICH__CONSOLE_LEVEL=DEBUG igittigitt info
116
+ ```
117
+
118
+ lib_log_rich also reads its own `LOG_*` variables. Logging takes those, and only those, from a
119
+ `.env`: from `--env-file` when given, otherwise from the nearest `.env` from the working directory
120
+ up to the project root, never replacing a variable that is already set. No other `.env` line
121
+ reaches the process environment.
122
+
123
+ A `[lib_log_rich]` value or `LOG_*` variable lib_log_rich refuses (a wrong type, its own range
124
+ checks such as `queue_maxsize = 0`, or an unknown level such as `LOG_CONSOLE_LEVEL=bogus`) is a
125
+ configuration failure like a broken file: logging starts with its defaults, `config`, `check` and
126
+ `filter` exit 78, and the other commands run. A refused `[lib_log_rich]` value leaves every valid
127
+ `LOG_*` variable in force; only a refused `LOG_*` variable makes logging start without all of them.
128
+ A problem the type check of the section finds gets one `Error:` line naming the key, never its
129
+ value; a value only lib_log_rich itself refuses is reported in lib_log_rich's own words
130
+ (`Error: lib_log_rich: Unknown log level: 'bogus'`), which may name neither the setting nor where
131
+ it was set.
@@ -95,7 +95,7 @@ make menu
95
95
 
96
96
  ## Running Integration Tests
97
97
 
98
- Some tests require external resources (SMTP servers, databases) and are excluded from the default test run. These are marked with `@pytest.mark.local_only`.
98
+ Some tests require external resources and are excluded from the default test run. These are marked with `@pytest.mark.local_only`.
99
99
 
100
100
  ### Quick Reference
101
101
 
@@ -105,39 +105,6 @@ Some tests require external resources (SMTP servers, databases) and are excluded
105
105
  | `make test-slow` | ONLY `local_only` integration tests |
106
106
  | `pytest tests/` | ALL tests (no marker filter) |
107
107
 
108
- ### Email Integration Tests
109
-
110
- To run email tests that actually send messages:
111
-
112
- 1. **Create a `.env` file** in the project root with your SMTP settings:
113
-
114
- ```bash
115
- # .env (copy from .env.example)
116
- EMAIL__SMTP_HOSTS=smtp.example.com:587
117
- EMAIL__FROM_ADDRESS=sender@example.com
118
- EMAIL__RECIPIENTS=recipient@example.com
119
- EMAIL__SMTP_USER=your_username
120
- EMAIL__SMTP_PASSWORD=your_password
121
- ```
122
-
123
- Alternatively, use `--env-file` to point at an existing `.env` file:
124
-
125
- ```bash
126
- igittigitt --env-file /path/to/my/.env send-notification --subject "Test" --message "Hello"
127
- ```
128
-
129
- 2. **Run the integration tests**:
130
-
131
- ```bash
132
- make test-slow
133
- ```
134
-
135
- 3. **Or run specific email tests**:
136
-
137
- ```bash
138
- pytest tests/test_cli_email_smtp.py -v
139
- ```
140
-
141
108
  ### Adding New Integration Tests
142
109
 
143
110
  Mark tests that require external resources: