bitranox-template-py-cli 1.5.4__tar.gz → 1.6.1__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 (112) hide show
  1. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.devcontainer/devcontainer.json +0 -0
  2. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.devcontainer/settings.json +0 -0
  3. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.env.example +0 -0
  4. bitranox_template_py_cli-1.6.1/.gitattributes +15 -0
  5. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/actions/extract-metadata/action.yml +16 -0
  6. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/dependabot.yml +0 -0
  7. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/workflows/codeql.yml +1 -1
  8. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/workflows/default_cicd_public.yml +59 -9
  9. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/workflows/default_release_public.yml +23 -7
  10. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.gitignore +15 -0
  11. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.qlty/qlty.toml +0 -0
  12. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.snyk +0 -0
  13. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/CHANGELOG.md +42 -14
  14. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/CONFIG.md +0 -0
  15. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/CONTRIBUTING.md +0 -0
  16. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/DEVELOPMENT.md +44 -28
  17. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/INSTALL.md +0 -0
  18. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/LICENSE +0 -0
  19. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/Makefile +174 -20
  20. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/PKG-INFO +20 -20
  21. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/README.md +0 -0
  22. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/SECURITY.md +0 -0
  23. bitranox_template_py_cli-1.6.1/ai-stance.md +90 -0
  24. bitranox_template_py_cli-1.6.1/ai-transparency.md +117 -0
  25. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/codecov.yml +0 -0
  26. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/docs/adr/0001-memory-adapters-in-src.md +0 -0
  27. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/docs/systemdesign/module_reference.md +0 -0
  28. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/notebooks/Quickstart.ipynb +1 -1
  29. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/pyproject.toml +30 -21
  30. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/__init__.py +0 -0
  31. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/__init__conf__.py +1 -1
  32. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/__main__.py +0 -0
  33. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/__init__.py +0 -0
  34. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/__init__.py +0 -0
  35. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/__init__.py +0 -0
  36. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/config.py +4 -1
  37. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/__init__.py +0 -0
  38. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/_common.py +0 -0
  39. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_email.py +0 -0
  40. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_notification.py +0 -0
  41. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/info.py +0 -0
  42. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/logging.py +0 -0
  43. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/constants.py +0 -0
  44. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/context.py +0 -0
  45. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/exit_codes.py +0 -0
  46. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/main.py +0 -0
  47. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/py.typed +0 -0
  48. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/root.py +0 -0
  49. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/typed_click.py +0 -0
  50. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/__init__.py +0 -0
  51. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/40-layered-config.toml +0 -0
  52. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/50-mail.toml +0 -0
  53. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/90-logging.toml +0 -0
  54. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.toml +0 -0
  55. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/deploy.py +0 -0
  56. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/display.py +0 -0
  57. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/loader.py +11 -1
  58. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/overrides.py +0 -0
  59. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/permissions.py +0 -0
  60. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/py.typed +0 -0
  61. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/__init__.py +0 -0
  62. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/config.py +0 -0
  63. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/py.typed +0 -0
  64. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/sender.py +0 -0
  65. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/transport.py +9 -0
  66. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/validation.py +0 -0
  67. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/logging/__init__.py +0 -0
  68. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/logging/py.typed +0 -0
  69. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/logging/setup.py +0 -0
  70. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/__init__.py +0 -0
  71. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/config.py +0 -0
  72. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/email.py +0 -0
  73. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/logging.py +0 -0
  74. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/py.typed +0 -0
  75. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/application/__init__.py +0 -0
  76. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/application/ports.py +0 -0
  77. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/application/py.typed +0 -0
  78. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/composition/__init__.py +0 -0
  79. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/composition/py.typed +0 -0
  80. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/__init__.py +0 -0
  81. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/behaviors.py +0 -0
  82. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/enums.py +0 -0
  83. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/errors.py +0 -0
  84. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/py.typed +0 -0
  85. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/entry.py +0 -0
  86. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/py.typed +0 -0
  87. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/conftest.py +0 -0
  88. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_behaviors.py +0 -0
  89. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cache_effectiveness.py +0 -0
  90. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_config.py +4 -0
  91. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_core.py +0 -0
  92. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_email.py +0 -0
  93. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_env_file.py +0 -0
  94. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_exit_codes.py +0 -0
  95. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_overrides.py +0 -0
  96. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_validation.py +0 -0
  97. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_config_overrides.py +0 -0
  98. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_deploy_permissions.py +0 -0
  99. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_display.py +0 -0
  100. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_enums.py +0 -0
  101. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_errors.py +0 -0
  102. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_logging.py +0 -0
  103. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_mail.py +246 -150
  104. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_metadata.py +0 -0
  105. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_metadata_sync.py +0 -0
  106. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_module_entry.py +0 -0
  107. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_ports.py +0 -0
  108. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_property_email.py +0 -0
  109. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_property_overrides.py +0 -0
  110. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/rename.sh +0 -0
  111. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/rename_dry.sh +0 -0
  112. {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/reset_git_history.sh +0 -0
@@ -0,0 +1,15 @@
1
+ # Normalize every text file to LF in the repo AND the working tree, on every OS.
2
+ # `text=auto` auto-detects binary and leaves it untouched; `eol=lf` forces LF checkout
3
+ # so a Windows edit/save cannot reintroduce CRLF churn.
4
+ * text=auto eol=lf
5
+
6
+ # Runtime-critical: CRLF here breaks execution on Linux (^M in shebang / make recipes).
7
+ Makefile text eol=lf
8
+ *.sh text eol=lf
9
+ *.py text eol=lf
10
+ *.ps1 text eol=lf
11
+ *.toml text eol=lf
12
+ *.md text eol=lf
13
+ *.json text eol=lf
14
+ *.yml text eol=lf
15
+ *.yaml text eol=lf
@@ -29,6 +29,11 @@ outputs:
29
29
  BANDIT_SKIP_FLAG:
30
30
  description: Bandit skip flag string (e.g. "-s B101,B601") or empty
31
31
  value: ${{ steps.meta.outputs.BANDIT_SKIP_FLAG }}
32
+ PYTEST_MARKER_EXPR:
33
+ description: >-
34
+ Marker expression for the CI test run (e.g. "not local_only and not integration"),
35
+ built from tool.ci.pytest-exclude-markers
36
+ value: ${{ steps.meta.outputs.PYTEST_MARKER_EXPR }}
32
37
 
33
38
  runs:
34
39
  using: composite
@@ -69,6 +74,15 @@ runs:
69
74
  bandit_skips = data.get('tool', {}).get('bandit', {}).get('skips', [])
70
75
  bandit_skip_flag = f"-s {','.join(bandit_skips)}" if bandit_skips else ""
71
76
 
77
+ # --- [tool.ci] section ---
78
+ # CI always skips local_only (those tests need a dev box). A project whose
79
+ # suite also carries markers CI cannot satisfy - integration tests needing a
80
+ # container that a separate workflow starts - names them here, rather than
81
+ # every such repo forking this workflow to edit one -m expression.
82
+ ci_config = data.get('tool', {}).get('ci', {})
83
+ exclude_markers = ['local_only', *ci_config.get('pytest-exclude-markers', [])]
84
+ pytest_marker_expr = ' and '.join(f'not {m}' for m in exclude_markers)
85
+
72
86
  # Write to GITHUB_OUTPUT for composite action outputs
73
87
  with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as out:
74
88
  out.write(f"PROJECT_NAME={project}\n")
@@ -79,6 +93,7 @@ runs:
79
93
  out.write(f"COV_REPORT_FILE={cov_report_file}\n")
80
94
  out.write(f"COV_FAIL_UNDER={cov_fail_under}\n")
81
95
  out.write(f"BANDIT_SKIP_FLAG={bandit_skip_flag}\n")
96
+ out.write(f"PYTEST_MARKER_EXPR={pytest_marker_expr}\n")
82
97
 
83
98
  # Also write to GITHUB_ENV so subsequent steps can use ${{ env.VAR }}
84
99
  with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as env:
@@ -90,3 +105,4 @@ runs:
90
105
  env.write(f"COV_REPORT_FILE={cov_report_file}\n")
91
106
  env.write(f"COV_FAIL_UNDER={cov_fail_under}\n")
92
107
  env.write(f"BANDIT_SKIP_FLAG={bandit_skip_flag}\n")
108
+ env.write(f"PYTEST_MARKER_EXPR={pytest_marker_expr}\n")
@@ -23,7 +23,7 @@ jobs:
23
23
 
24
24
  steps:
25
25
  - name: Checkout repository
26
- uses: actions/checkout@v6
26
+ uses: actions/checkout@v7
27
27
 
28
28
  - name: Initialize CodeQL
29
29
  uses: github/codeql-action/init@v4
@@ -18,7 +18,7 @@ jobs:
18
18
  python-latest: ${{ steps.metadata.outputs.python-latest }}
19
19
  os-matrix: ${{ steps.metadata.outputs.os-matrix }}
20
20
  steps:
21
- - uses: actions/checkout@v6
21
+ - uses: actions/checkout@v7
22
22
  - uses: actions/setup-python@v6
23
23
  with:
24
24
  python-version: "3.x"
@@ -66,7 +66,7 @@ jobs:
66
66
  UV_LINK_MODE: "copy"
67
67
  UV_BREAK_SYSTEM_PACKAGES: "1"
68
68
  steps:
69
- - uses: actions/checkout@v6
69
+ - uses: actions/checkout@v7
70
70
  - uses: actions/setup-python@v6
71
71
  with:
72
72
  python-version: ${{ matrix.python }}
@@ -169,7 +169,7 @@ jobs:
169
169
  - name: Restore ruff cache
170
170
  if: runner.environment == 'github-hosted'
171
171
  id: ruff-cache
172
- uses: actions/cache/restore@v5
172
+ uses: actions/cache/restore@v6
173
173
  with:
174
174
  path: .ruff_cache
175
175
  key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
@@ -201,7 +201,7 @@ jobs:
201
201
  # and stale journal files cause "database is locked" errors.
202
202
  COVERAGE_FILE: "${{ runner.temp }}/.coverage"
203
203
  run: >-
204
- pytest -m "not local_only"
204
+ pytest -m "${{ env.PYTEST_MARKER_EXPR }}"
205
205
  --cov=${{ env.SRC_PATH }}/${{ env.PACKAGE_MODULE }}
206
206
  --cov-report=term-missing
207
207
  --cov-report=xml:${{ env.COV_REPORT_FILE }}
@@ -236,12 +236,62 @@ jobs:
236
236
  continue-on-error: true
237
237
  shell: python
238
238
  run: |
239
- import subprocess, sys
239
+ import subprocess, sys, tempfile
240
240
  import rtoml
241
241
  from pathlib import Path
242
+
243
+ # Audit the project's OWN resolved dependency tree, not the ambient
244
+ # environment. "Install dev deps" uses --system, so the interpreter
245
+ # being audited is the runner image's, and that image ships packages
246
+ # this project neither declares nor distributes (setuptools, pip,
247
+ # wheel). Auditing those reported CVEs no consumer of this project is
248
+ # exposed to, and a scan that is permanently yellow is a scan everyone
249
+ # learns to ignore. Resolving pyproject.toml here keeps the result
250
+ # identical on every OS/python and dependent only on what we declare.
242
251
  data = rtoml.load(Path("pyproject.toml"))
252
+
253
+ # Not every project declares dev dependencies, and those that do may use
254
+ # either an extra or a PEP 735 group. Asking uv for one that does not
255
+ # exist is a hard error ("Requested extra not found"), which would leave
256
+ # the audit permanently broken here rather than merely noisy, so select
257
+ # the flag from what this project actually declares.
258
+ selector = []
259
+ if "dev" in data.get("project", {}).get("optional-dependencies", {}):
260
+ selector = ["--extra", "dev"]
261
+ elif "dev" in data.get("dependency-groups", {}):
262
+ selector = ["--group", "dev"]
263
+
264
+ tmp = Path(tempfile.mkdtemp())
265
+ req = tmp / "compiled-requirements.txt"
266
+ subprocess.check_call(
267
+ ["uv", "pip", "compile", *selector, "pyproject.toml", "-o", str(req), "--quiet"]
268
+ )
269
+
270
+ # Drop direct URL references ("pkg @ git+https://...") before auditing.
271
+ # pip-audit cannot audit those (they are not on PyPI) but, given a
272
+ # requirements file, first tries to `git clone` each one to resolve a
273
+ # version - a slow, credential-dependent fetch. No public project here
274
+ # uses one today, yet "allow-direct-references" is enabled, so this
275
+ # guards the day one appears and keeps this step identical to the
276
+ # default_cicd_private template's.
277
+ audit_req = tmp / "audit-requirements.txt"
278
+ kept, dropped = [], []
279
+ for line in req.read_text(encoding="utf-8").splitlines():
280
+ if " @ " in line and not line.lstrip().startswith("#"):
281
+ dropped.append(line.split(" @ ", 1)[0].strip())
282
+ else:
283
+ kept.append(line)
284
+ audit_req.write_text("\n".join(kept) + "\n", encoding="utf-8")
285
+ if dropped:
286
+ # Never drop coverage silently: name what is not being audited here.
287
+ print(f"::notice::pip-audit: not auditing {len(dropped)} direct-URL dependency/ies "
288
+ f"(audited in their own repo): {', '.join(sorted(dropped))}")
289
+
290
+ req = audit_req
243
291
  vulns = data.get("tool", {}).get("pip-audit", {}).get("ignore-vulns", [])
244
- cmd = ["pip-audit", "--skip-editable"]
292
+ # --no-deps: the compiled file is already a complete pinned resolution,
293
+ # so pip-audit must audit it verbatim rather than re-resolving it.
294
+ cmd = ["pip-audit", "--no-deps", "-r", str(req)]
245
295
  for v in vulns:
246
296
  cmd.extend(["--ignore-vuln", v])
247
297
  sys.exit(subprocess.call(cmd))
@@ -293,7 +343,7 @@ jobs:
293
343
 
294
344
  - name: Save ruff cache
295
345
  if: always() && runner.environment == 'github-hosted' && steps.ruff-cache.outputs.cache-hit != 'true'
296
- uses: actions/cache/save@v5
346
+ uses: actions/cache/save@v6
297
347
  with:
298
348
  path: .ruff_cache
299
349
  key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
@@ -302,7 +352,7 @@ jobs:
302
352
  name: pipx/uv verification (ubuntu)
303
353
  runs-on: ubuntu-latest
304
354
  steps:
305
- - uses: actions/checkout@v6
355
+ - uses: actions/checkout@v7
306
356
  - uses: actions/setup-python@v6
307
357
  with:
308
358
  python-version: "3.14"
@@ -341,7 +391,7 @@ jobs:
341
391
  name: Execute notebooks (ubuntu, Python 3.14)
342
392
  runs-on: ubuntu-latest
343
393
  steps:
344
- - uses: actions/checkout@v6
394
+ - uses: actions/checkout@v7
345
395
  - uses: actions/setup-python@v6
346
396
  with:
347
397
  python-version: "3.14"
@@ -16,7 +16,7 @@ jobs:
16
16
  env:
17
17
  UV_LINK_MODE: "copy"
18
18
  steps:
19
- - uses: actions/checkout@v6
19
+ - uses: actions/checkout@v7
20
20
  - uses: actions/setup-python@v6
21
21
  with:
22
22
  python-version: '3.14'
@@ -46,13 +46,21 @@ jobs:
46
46
  environment: pypi
47
47
  permissions:
48
48
  contents: read
49
+ # id-token is required for OIDC / PyPI Trusted Publisher. It is harmless when
50
+ # a PYPI_API_TOKEN secret is configured, in which case the token path runs.
51
+ id-token: write
49
52
  steps:
50
- - name: Verify PyPI token is set
53
+ - name: Select PyPI auth method
54
+ id: auth
55
+ env:
56
+ PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
51
57
  run: |
52
- if [ -z "${{ secrets.PYPI_API_TOKEN }}" ]; then
53
- echo "::error title=Missing PyPI token::Set the PYPI_API_TOKEN secret in GitHub repository settings to enable PyPI publishing on releases."
54
- echo "See: https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions"
55
- exit 1
58
+ if [ -n "$PYPI_API_TOKEN" ]; then
59
+ echo "PYPI_API_TOKEN is set; publishing with the API token."
60
+ echo "method=token" >> "$GITHUB_OUTPUT"
61
+ else
62
+ echo "No PYPI_API_TOKEN; publishing via OIDC Trusted Publisher."
63
+ echo "method=oidc" >> "$GITHUB_OUTPUT"
56
64
  fi
57
65
 
58
66
  - name: Download artifacts
@@ -61,9 +69,17 @@ jobs:
61
69
  name: dist
62
70
  path: dist/
63
71
 
64
- - name: Publish to PyPI
72
+ - name: Publish to PyPI (API token)
73
+ if: steps.auth.outputs.method == 'token'
65
74
  uses: pypa/gh-action-pypi-publish@release/v1
66
75
  with:
67
76
  password: ${{ secrets.PYPI_API_TOKEN }}
68
77
  attestations: false
69
78
  skip-existing: true
79
+
80
+ - name: Publish to PyPI (Trusted Publisher / OIDC)
81
+ if: steps.auth.outputs.method == 'oidc'
82
+ uses: pypa/gh-action-pypi-publish@release/v1
83
+ with:
84
+ attestations: true
85
+ skip-existing: true
@@ -186,3 +186,18 @@ uv.lock
186
186
  CLAUDE.md
187
187
  prompts.md
188
188
  .grimp_cache/
189
+
190
+ # Local, per-developer settings (Claude Code etc.) - never commit
191
+ *.local.json
192
+ # bitranox curated memory (local; CLAUDE.local.md @imports it)
193
+ CLAUDE.local.md
194
+
195
+ # Python virtual environments (managed by bmk)
196
+ .venv-win/
197
+ .venv-bmk/
198
+
199
+ # Python virtual environments (managed by bmk)
200
+ .venv*/
201
+
202
+ # Autonomous-execution decision log (never committed)
203
+ EXECUTION-USER-REVIEW.md
@@ -6,6 +6,34 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.6.1] - 2026-07-20
10
+
11
+ Re-release of 1.6.0. PyPI rejected the 1.6.0 upload because that filename was previously used by a file that has since been deleted, and PyPI never permits filename reuse. Package contents are identical to 1.6.0.
12
+
13
+ ### Added
14
+ - `send_email()` and `send_notification()` accept an optional `transport` argument, exposing btx_lib_mail's `Transport` port so a delivery double can be injected at a real seam instead of monkeypatching `smtplib`.
15
+
16
+ ### Fixed
17
+ - Mail tests failed against btx_lib_mail 1.5.0, which streams the message over the wire protocol directly (MAIL/RCPT plus DATA or BDAT) instead of calling `smtp.sendmail()`. The suite now injects a recording transport and asserts delivery intent (envelope sender, recipients, host failover order, credentials, payload), so it no longer depends on the library's internals.
18
+ - `ConfMail.smtp_password` is a `SecretStr` as of btx_lib_mail 1.5.0 and is now unwrapped before comparison.
19
+ - `config-deploy` emits an ASCII marker so it cannot crash on a cp1252 Windows console.
20
+ - `validate_profile` normalised to raise `ValueError`.
21
+ - DEVELOPMENT.md referenced a `make test-slow` target that no longer exists (4 places); the target is `make testintegration`. It also documented the SMTP username environment variable as `EMAIL__SMTP_USER`, but the field is `smtp_username`, so the correct name is `EMAIL__SMTP_USERNAME`.
22
+
23
+ ### Documentation
24
+ - DEVELOPMENT.md documents the SMTP test seam: inject a `transport` double instead of patching `smtplib`, and leave `transport` off the application ports.
25
+
26
+ ### Changed
27
+ - Raised the `btx_lib_mail` floor to `>=1.5.0`, which the injected transport seam requires. Other dependency floors refreshed and the current bmk Makefile adopted.
28
+ - CI: configurable pytest marker exclusion, pip-audit scoped to the project dependency tree with direct-URL dependencies filtered out, `actions/checkout` v7 and `actions/cache` v6.
29
+ - `codecov-cli` disabled (it capped `click<8.3.0` and held click on CVE-2026-7246); the redundant PYSEC-2026-2132 ignore dropped.
30
+ - LF line endings pinned via `.gitattributes`; file modes normalised so the executable bit is set only on shebang scripts.
31
+ - Quickstart notebook installs the package with uv.
32
+
33
+ ## [1.6.0] - 2026-07-20 [NOT PUBLISHED]
34
+
35
+ Tagged and released on GitHub, but never published to PyPI: the upload was rejected because the `1.6.0` filename had been used by a previously deleted file. Superseded by 1.6.1, which carries the same changes.
36
+
9
37
  ## [1.5.4] 2026-06-14
10
38
 
11
39
  ### Changed
@@ -84,13 +112,13 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
84
112
  ## [1.3.1] - 2026-02-13
85
113
 
86
114
  ### Fixed
87
- - `tests/test_metadata_sync.py`: replaced `importlib.metadata` lookups with direct `pyproject.toml` reads tests no longer fail when the package is not installed in the test environment (uvx)
115
+ - `tests/test_metadata_sync.py`: replaced `importlib.metadata` lookups with direct `pyproject.toml` reads - tests no longer fail when the package is not installed in the test environment (uvx)
88
116
  - Makefile `dev` target now correctly installs dev extras (`uv pip install -e ".[dev]"`)
89
117
 
90
118
  ### Changed
91
119
  - CLAUDE.md: updated project structure trees to reflect actual codebase (added `entry.py`, `domain/errors.py`, `adapters/memory/`, `adapters/config/permissions.py`, `adapters/email/validation.py`; removed deleted `traceback.py`)
92
120
  - CLAUDE.md: rewrote Make targets table to match new `bmk`-based Makefile (added aliases, new targets; removed obsolete `menu`, `test-slow`)
93
- - CLAUDE.md: corrected versioning documentation runtime metadata is served from `__init__conf__.py` constants, not `importlib.metadata`
121
+ - CLAUDE.md: corrected versioning documentation - runtime metadata is served from `__init__conf__.py` constants, not `importlib.metadata`
94
122
  - CLAUDE.md: replaced stale `scripts/` instrumentation section with `bmk` delegation note
95
123
  - CLAUDE.md: updated `make test-slow` references to `make testintegration`
96
124
 
@@ -122,17 +150,17 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
122
150
  - Profile name requirements documentation in CONFIG.md and README.md
123
151
 
124
152
  ### Removed
125
- - Custom `_PROFILE_PATTERN` regex replaced by lib_layered_config's built-in validation
153
+ - Custom `_PROFILE_PATTERN` regex - replaced by lib_layered_config's built-in validation
126
154
 
127
155
  ## [1.2.0] - 2026-01-30
128
156
 
129
157
  ### Added
130
158
  - **Attachment security settings** for email configuration (`[email.attachments]` section in `50-mail.toml`)
131
- - `allowed_extensions` / `blocked_extensions` whitelist/blacklist file extensions
132
- - `allowed_directories` / `blocked_directories` whitelist/blacklist attachment source directories
133
- - `max_size_bytes` maximum attachment file size (default 25 MiB, 0 to disable)
134
- - `allow_symlinks` whether symbolic links are permitted (default false)
135
- - `raise_on_security_violation` raise or skip on violations (default true)
159
+ - `allowed_extensions` / `blocked_extensions` - whitelist/blacklist file extensions
160
+ - `allowed_directories` / `blocked_directories` - whitelist/blacklist attachment source directories
161
+ - `max_size_bytes` - maximum attachment file size (default 25 MiB, 0 to disable)
162
+ - `allow_symlinks` - whether symbolic links are permitted (default false)
163
+ - `raise_on_security_violation` - raise or skip on violations (default true)
136
164
  - New `EmailConfig` fields for attachment security with Pydantic validators
137
165
  - `load_email_config_from_dict()` now flattens nested `[email.attachments]` section
138
166
 
@@ -145,17 +173,17 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
145
173
  - Coverage SQLite "database is locked" errors on Python 3.14 free-threaded builds and network mounts (SMB/NFS)
146
174
  - Removed bogus `COVERAGE_NO_SQL=1` environment variable from `scripts/test.py` (not a real coverage.py setting)
147
175
  - CI workflow now sets `COVERAGE_FILE` to `runner.temp` so coverage always writes to local disk
148
- - **Import-linter was a silent no-op** in `make test` / `make push` `python -m importlinter.cli lint` silently exits 0 without checking; replaced with `lint-imports` (the working console entry point)
176
+ - **Import-linter was a silent no-op** in `make test` / `make push` - `python -m importlinter.cli lint` silently exits 0 without checking; replaced with `lint-imports` (the working console entry point)
149
177
  - CI/local parameter mismatches: ruff now targets `.` (not hardcoded `src tests notebooks`), pytest uses `python -m pytest` with `--cov=src/$PACKAGE_MODULE`, `--cov-fail-under=90`, and `-vv` matching local runs
150
178
  - `scripts/test.py` bandit source path now reads `src-path` from `[tool.scripts.test]` instead of hardcoding `Path("src")`
151
179
  - `scripts/test.py` module-level `_default_env` now rebuilt with configured `src_path` before running checks
152
180
  - `run_slow_tests()` now reads pytest verbosity from `[tool.scripts.test].pytest-verbosity` instead of hardcoding `"-vv"`
153
181
 
154
182
  ### Changed
155
- - **pyproject.toml as single source of truth**: CI workflow extracts all tool configuration (src-path, pytest-verbosity, coverage-report-file, fail_under, bandit skips) from `pyproject.toml` via metadata step workflow is portable across projects without editing
183
+ - **pyproject.toml as single source of truth**: CI workflow extracts all tool configuration (src-path, pytest-verbosity, coverage-report-file, fail_under, bandit skips) from `pyproject.toml` via metadata step - workflow is portable across projects without editing
156
184
  - `scripts/test.py` removed module-level `PACKAGE_SRC` constant; bandit source path computed from `config.src_path` inside the functions that need it
157
185
  - `make push` now accepts an unquoted message as trailing words (e.g. `make push fix typo in readme`); commit message format is `<version> - <message>`, defaulting to `<version> - chores` when no message is given
158
- - Removed interactive commit-message prompt from `push.py` message is either provided via CLI args / `COMMIT_MESSAGE` env var, or defaults to `"chores"`
186
+ - Removed interactive commit-message prompt from `push.py` - message is either provided via CLI args / `COMMIT_MESSAGE` env var, or defaults to `"chores"`
159
187
 
160
188
  ### Added
161
189
  - `pytest_configure` hook in `tests/conftest.py` that redirects coverage data to `tempfile.gettempdir()` and purges stale SQLite journal files before each run
@@ -165,8 +193,8 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
165
193
  ### Fixed
166
194
  - CLAUDE.md: replaced stale package name `bitranox_template_cli_app_config_log_mail` with `bitranox_template_py_cli` throughout
167
195
  - Brittle SMTP mock assertions in `test_cli.py` now use structured `call_args` attributes instead of `str()` coercion
168
- - Stale docstring in `__init__conf__.py` claiming "adapters/platform layer" corrected to "Package-level metadata module"
169
- - Weak OR assertion in `test_cli.py` for SMTP host display replaced with two independent assertions
196
+ - Stale docstring in `__init__conf__.py` claiming "adapters/platform layer" - corrected to "Package-level metadata module"
197
+ - Weak OR assertion in `test_cli.py` for SMTP host display - replaced with two independent assertions
170
198
  - Removed stale `# type: ignore[reportUnknownVariableType]` from `sender.py` (`btx_lib_mail.ConfMail` now has proper type annotations)
171
199
  - Late function-body imports in `adapters/cli/commands/config.py` moved to module-level for consistency
172
200
 
@@ -219,7 +247,7 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
219
247
  - Precompile all regex patterns in `scripts/` as module-level constants for consistent compilation
220
248
  - **LIBRARIES**: Replace custom redaction/validation with `lib_layered_config` redaction API and `btx_lib_mail` validators; bump both libraries
221
249
  - **LIBRARIES**: Replace stdlib `json` with `orjson`; replace `urllib` with `httpx` in scripts
222
- - **ARCHITECTURE**: Purified domain layer `emit_greeting()` renamed to `build_greeting()` (returns `str`, no I/O); decoupled `display.py` from Click
250
+ - **ARCHITECTURE**: Purified domain layer - `emit_greeting()` renamed to `build_greeting()` (returns `str`, no I/O); decoupled `display.py` from Click
223
251
  - **DATA ARCHITECTURE**: Consolidated `EmailConfig` into single Pydantic `BaseModel` (eliminated dataclass conversion chain)
224
252
 
225
253
  ## [1.0.0] - 2026-01-15
@@ -2,26 +2,26 @@
2
2
 
3
3
  ## Make Targets
4
4
 
5
- | Target | Description |
6
- |-------------------|--------------------------------------------------------------------------------------------|
7
- | `help` | Show help |
8
- | `install` | Install package editable |
9
- | `dev` | Install package with dev extras |
10
- | `test` | Lint, type-check, run tests with coverage, upload to Codecov |
11
- | `run` | Run module CLI (requires dev install or src on PYTHONPATH) |
12
- | `version-current` | Print current version from pyproject.toml |
13
- | `bump` | Bump version (updates pyproject.toml and CHANGELOG.md) |
14
- | `bump-patch` | Bump patch version (X.Y.Z -> X.Y.(Z+1)) |
15
- | `bump-minor` | Bump minor version (X.Y.Z -> X.(Y+1).0) |
16
- | `bump-major` | Bump major version ((X+1).0.0) |
17
- | `clean` | Remove caches, build artifacts, and coverage |
18
- | `push` | Run tests, prompt for/accept a commit message, create (allow-empty) commit, push to remote |
19
- | `build` | Build wheel/sdist artifacts via `python -m build` |
20
- | `coverage` | Generate coverage reports |
21
- | `test-slow` | Run slow integration tests (SMTP, external resources) |
22
- | `dependencies` | Check and list project dependencies |
23
- | `dependencies-update` | Update dependencies to latest versions |
24
- | `menu` | Interactive TUI to run targets and edit parameters (requires dev dep: textual) |
5
+ | Target | Description |
6
+ |-----------------------|--------------------------------------------------------------------------------------------|
7
+ | `help` | Show help |
8
+ | `install` | Install package editable |
9
+ | `dev` | Install package with dev extras |
10
+ | `test` | Lint, type-check, run tests with coverage, upload to Codecov |
11
+ | `run` | Run module CLI (requires dev install or src on PYTHONPATH) |
12
+ | `version-current` | Print current version from pyproject.toml |
13
+ | `bump` | Bump version (updates pyproject.toml and CHANGELOG.md) |
14
+ | `bump-patch` | Bump patch version (X.Y.Z -> X.Y.(Z+1)) |
15
+ | `bump-minor` | Bump minor version (X.Y.Z -> X.(Y+1).0) |
16
+ | `bump-major` | Bump major version ((X+1).0.0) |
17
+ | `clean` | Remove caches, build artifacts, and coverage |
18
+ | `push` | Run tests, prompt for/accept a commit message, create (allow-empty) commit, push to remote |
19
+ | `build` | Build wheel/sdist artifacts via `python -m build` |
20
+ | `coverage` | Generate coverage reports |
21
+ | `testintegration` | Run integration tests only (SMTP, external resources) [aliases: `testi`, `ti`] |
22
+ | `dependencies` | Check and list project dependencies |
23
+ | `dependencies-update` | Update dependencies to latest versions |
24
+ | `menu` | Interactive TUI to run targets and edit parameters (requires dev dep: textual) |
25
25
 
26
26
  ### Target Parameters (env vars)
27
27
 
@@ -99,11 +99,11 @@ Some tests require external resources (SMTP servers, databases) and are excluded
99
99
 
100
100
  ### Quick Reference
101
101
 
102
- | Command | What it runs |
103
- |---------|--------------|
104
- | `make test` | All tests EXCEPT `local_only` (default for CI) |
105
- | `make test-slow` | ONLY `local_only` integration tests |
106
- | `pytest tests/` | ALL tests (no marker filter) |
102
+ | Command | What it runs |
103
+ |------------------------|------------------------------------------------|
104
+ | `make test` | All tests EXCEPT `local_only` (default for CI) |
105
+ | `make testintegration` | ONLY `local_only` integration tests |
106
+ | `pytest tests/` | ALL tests (no marker filter) |
107
107
 
108
108
  ### Email Integration Tests
109
109
 
@@ -116,7 +116,7 @@ To run email tests that actually send messages:
116
116
  EMAIL__SMTP_HOSTS=smtp.example.com:587
117
117
  EMAIL__FROM_ADDRESS=sender@example.com
118
118
  EMAIL__RECIPIENTS=recipient@example.com
119
- EMAIL__SMTP_USER=your_username
119
+ EMAIL__SMTP_USERNAME=your_username
120
120
  EMAIL__SMTP_PASSWORD=your_password
121
121
  ```
122
122
 
@@ -129,7 +129,7 @@ bitranox-template-py-cli --env-file /path/to/my/.env send-notification --subject
129
129
  2. **Run the integration tests**:
130
130
 
131
131
  ```bash
132
- make test-slow
132
+ make testintegration
133
133
  ```
134
134
 
135
135
  3. **Or run specific email tests**:
@@ -150,7 +150,23 @@ def test_real_external_service(...):
150
150
  ...
151
151
  ```
152
152
 
153
- These tests will be skipped in CI but run with `make test-slow`.
153
+ These tests will be skipped in CI but run with `make testintegration`.
154
+
155
+ ### SMTP Test Seam
156
+
157
+ `send_email()` and `send_notification()` accept an optional `transport` argument, forwarded
158
+ to `btx_lib_mail`'s `Transport` port. Unit tests inject a double (`RecordingTransport` in
159
+ `tests/test_mail.py`) rather than patching `smtplib.SMTP`, and assert on delivery intent:
160
+ envelope sender, recipients, host failover order, credentials, and the composed payload.
161
+
162
+ Do not go back to monkeypatching `smtplib`. btx_lib_mail streams the message over the wire
163
+ protocol itself (MAIL/RCPT plus DATA or BDAT), so a `MagicMock` does not honour the contract
164
+ and breaks whenever the library changes delivery strategy.
165
+
166
+ The `SendEmail` / `SendNotification` ports in `application/ports.py` deliberately omit
167
+ `transport`: a delivery mechanism is an adapter concern, not something the application layer
168
+ should see. The adapter still satisfies the Protocol because an extra keyword parameter with
169
+ a default is structurally compatible. Do not add `transport` to the port.
154
170
 
155
171
  ## Development Workflow
156
172