patchshuttle 0.1.0a3__tar.gz → 0.1.0a4__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 (119) hide show
  1. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/CHANGELOG.md +67 -0
  2. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/PKG-INFO +281 -50
  3. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/README.md +279 -49
  4. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/SPEC_V0_1.md +246 -43
  5. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/docs/RELEASE.md +38 -10
  6. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/pyproject.toml +1 -0
  7. patchshuttle-0.1.0a4/src/patchshuttle/_ai_log.py +437 -0
  8. patchshuttle-0.1.0a4/src/patchshuttle/_architecture.py +334 -0
  9. patchshuttle-0.1.0a4/src/patchshuttle/_python_discovery.py +236 -0
  10. patchshuttle-0.1.0a4/src/patchshuttle/_python_structure.py +220 -0
  11. patchshuttle-0.1.0a4/src/patchshuttle/_python_symbols.py +58 -0
  12. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/_version.py +1 -1
  13. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/actions/__init__.py +8 -0
  14. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/actions/constructors.py +75 -0
  15. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/audit.py +190 -0
  16. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/checks/__init__.py +2 -0
  17. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/checks/constructors.py +5 -0
  18. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/checks/runner.py +90 -16
  19. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/cli.py +111 -4
  20. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/config.py +102 -7
  21. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/context.py +22 -8
  22. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/errors.py +5 -0
  23. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/logging.py +232 -14
  24. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/models.py +82 -0
  25. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/planner.py +214 -8
  26. patchshuttle-0.1.0a4/src/patchshuttle/project_python.py +79 -0
  27. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/resources/AI_GUIDE.md +102 -4
  28. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/resources/PATCHSHUTTLE_PROTOCOL.md +105 -5
  29. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/runner.py +1 -0
  30. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/selfdoc.py +48 -1
  31. patchshuttle-0.1.0a4/src/patchshuttle/warning_baseline.py +271 -0
  32. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/workspace.py +12 -0
  33. patchshuttle-0.1.0a4/tests/unit/test_a4_coverage_regressions.py +568 -0
  34. patchshuttle-0.1.0a4/tests/unit/test_ai_log.py +273 -0
  35. patchshuttle-0.1.0a4/tests/unit/test_architecture.py +350 -0
  36. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_attempt_logging.py +6 -0
  37. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_audit_verify_execution.py +64 -1
  38. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_checks.py +80 -0
  39. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_cli.py +111 -3
  40. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_config.py +5 -0
  41. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_context_and_constructors.py +92 -0
  42. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_logging.py +83 -0
  43. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_models.py +65 -0
  44. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_operational_execution.py +39 -0
  45. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_package.py +1 -1
  46. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_planner.py +200 -0
  47. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_policy.py +30 -0
  48. patchshuttle-0.1.0a4/tests/unit/test_project_python.py +219 -0
  49. patchshuttle-0.1.0a4/tests/unit/test_python_discovery.py +307 -0
  50. patchshuttle-0.1.0a4/tests/unit/test_python_structure.py +194 -0
  51. patchshuttle-0.1.0a4/tests/unit/test_python_structure_action.py +151 -0
  52. patchshuttle-0.1.0a4/tests/unit/test_python_symbols.py +72 -0
  53. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_release_configuration.py +1 -1
  54. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_selfdoc.py +11 -1
  55. patchshuttle-0.1.0a4/tests/unit/test_warning_baseline.py +126 -0
  56. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_workspace.py +2 -0
  57. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/.github/workflows/ci.yml +0 -0
  58. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/.github/workflows/release.yml +0 -0
  59. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/.github/workflows/testpypi.yml +0 -0
  60. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/.gitignore +0 -0
  61. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/LICENSE +0 -0
  62. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/SECURITY.md +0 -0
  63. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/__init__.py +0 -0
  64. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/_diff.py +0 -0
  65. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/_html_lint.py +0 -0
  66. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/_line_ranges.py +0 -0
  67. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/_process.py +0 -0
  68. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/actions/create.py +0 -0
  69. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/actions/modify.py +0 -0
  70. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/backup.py +0 -0
  71. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/execution.py +0 -0
  72. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/formatter_policy.py +0 -0
  73. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/formatters/__init__.py +0 -0
  74. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/formatters/runner.py +0 -0
  75. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/identifiers.py +0 -0
  76. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/inventory.py +0 -0
  77. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/linters/__init__.py +0 -0
  78. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/linters/runner.py +0 -0
  79. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/operations.py +0 -0
  80. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/parser.py +0 -0
  81. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/policy.py +0 -0
  82. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/preflight.py +0 -0
  83. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/py.typed +0 -0
  84. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/registry.py +0 -0
  85. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/resources/AUDIT-EXAMPLE.psh.yaml +0 -0
  86. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/resources/PATCH-EXAMPLE.psh.yaml +0 -0
  87. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/resources/__init__.py +0 -0
  88. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/rollback.py +0 -0
  89. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/runtime_cache.py +0 -0
  90. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/src/patchshuttle/verification.py +0 -0
  91. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/e2e/test_cli_project_cycle.py +0 -0
  92. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/integration/test_workflows.py +0 -0
  93. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_actions.py +0 -0
  94. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_backup.py +0 -0
  95. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_change_transaction.py +0 -0
  96. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_check_transaction.py +0 -0
  97. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_diff.py +0 -0
  98. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_execution.py +0 -0
  99. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_formatters.py +0 -0
  100. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_formatting_transaction.py +0 -0
  101. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_guarded_ranges.py +0 -0
  102. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_html_lint.py +0 -0
  103. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_identifiers.py +0 -0
  104. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_inventory.py +0 -0
  105. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_inventory_transaction.py +0 -0
  106. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_manual_rollback.py +0 -0
  107. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_modify_actions.py +0 -0
  108. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_parser.py +0 -0
  109. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_phase19_cli.py +0 -0
  110. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_planner_ux.py +0 -0
  111. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_preflight.py +0 -0
  112. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_registry.py +0 -0
  113. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_rollback.py +0 -0
  114. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_runner.py +0 -0
  115. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tests/unit/test_runtime_cache.py +0 -0
  116. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tools/check_tag.py +0 -0
  117. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tools/index_smoke.py +0 -0
  118. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tools/release_checks.py +0 -0
  119. {patchshuttle-0.1.0a3 → patchshuttle-0.1.0a4}/tools/wheel_smoke.py +0 -0
@@ -5,6 +5,73 @@ uses semantic versioning, including Python-compatible pre-release versions.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.1.0a4 - 2026-08-27
9
+
10
+ Third alpha release, focused on bounded Python source discovery, compact
11
+ structure output, and owner-controlled architecture ratchets.
12
+
13
+ ### Added
14
+ - Owner-controlled Python architecture ratchets evaluate the planner's virtual
15
+ file state before execution. Defaults warn at 500 lines per module, 15 direct
16
+ Python files per package, five new Python files, or one new package; hard
17
+ limits are 1000, 25, 10, and three respectively. Stable bounded `ARCH` findings
18
+ appear in plans and logs, while unchanged or improving legacy violations remain
19
+ allowed.
20
+
21
+ - Read-only `python_structure` uses the standard-library AST to report a
22
+ bounded declaration map for one Python file or directory, including
23
+ imports, classes, functions, methods, parameters, decorators, bases,
24
+ source ranges, parse errors, and explicit file and symbol limit signals,
25
+ without importing or executing project code. Its opt-in `compact: true`
26
+ mode retains navigational symbol identities and ranges while omitting
27
+ detailed import and declaration metadata; full output remains the default.
28
+
29
+ - Read-only `search_context` returns bounded physical-line context around
30
+ literal matches, and `read_symbol` returns one exactly resolved
31
+ decorator-aware Python symbol with its physical range and canonical
32
+ LF/UTF-8 SHA-256.
33
+ - Guarded `replace_symbol` replaces one exactly resolved Python class,
34
+ function, method, or nested symbol using the hash returned by
35
+ `read_symbol` and the planner's sequential simulated file state.
36
+ - The built-in `ruff: {}` check runs fixed Ruff `F` rules without fixes
37
+ over only the Python files changed by the current patch.
38
+ - `logs --last --ai` and `logs --last --ai-json` render bounded
39
+ deterministic text and JSON views of the newest stored log without
40
+ changing it or executing project code.
41
+ - Run and early-failure logs include evidence-only
42
+ `PYTHON_DISCOVERY_EVALUATION` telemetry for the current job. It records
43
+ explicit Python paths, audit action/output volume, targeting styles, and
44
+ relevant failure signals without estimating tokens or deciding whether a
45
+ project symbol index should be implemented.
46
+ - Python discovery telemetry also aggregates reported file and search
47
+ matches, result-limit signals, and durations already present in bounded
48
+ audit results. Newly generated workspace policy covers conventional
49
+ `.venv`, `venv`, and `node_modules` trees at any depth; backup directories
50
+ remain owner-configured exclusions.
51
+ - Best-effort whole-log redaction recognizes common sensitive Python
52
+ assignment identifiers, including prefixed passwords, API and access keys,
53
+ secret and private keys, tokens, and one-line `SECRET_KEY_FALLBACKS`
54
+ collections. Safe annotations, same-name references, and loader calls remain
55
+ visible; the redaction guarantee remains `NONE`.
56
+ - Explicit project-local Django warning baselines classify known and new
57
+ W-class system-check warnings without changing the check exit status.
58
+ - Optional owner-controlled `[execution].python_executable` selects the
59
+ interpreter for Python-based project checks and `{python}` local
60
+ profiles. Jobs cannot set it, and omitted configuration retains the
61
+ existing interpreter behavior.
62
+
63
+ ### Changed
64
+
65
+ - AI handoffs use a deterministic capability hash, generated handoff trees
66
+ omit legacy backup and archive artifacts, and source audit output avoids
67
+ the confirmed token-name redaction false positive.
68
+
69
+ ### Fixed
70
+
71
+ - `replace_symbol` execution now enters the same guarded file-change
72
+ transaction as other planned text modifications, including revalidation,
73
+ backups, checks, rollback, logging, and workspace comparison.
74
+
8
75
  ## 0.1.0a3 - 2026-08-23
9
76
 
10
77
  Second alpha release, focused on AI-facing planning diagnostics, legacy-project
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchshuttle
3
- Version: 0.1.0a3
3
+ Version: 0.1.0a4
4
4
  Summary: Local, auditable patch workflows for AI-assisted software development
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -23,6 +23,7 @@ Requires-Dist: filelock<4,>=3.30
23
23
  Requires-Dist: isort<7,>=5.13
24
24
  Requires-Dist: pydantic<3,>=2.6
25
25
  Requires-Dist: pyyaml<7,>=6
26
+ Requires-Dist: ruff<1,>=0.16
26
27
  Requires-Dist: tomli<3,>=2; python_version < '3.11'
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: build<2,>=1.2; extra == 'dev'
@@ -42,7 +43,7 @@ describes a small job, the user reviews and runs it locally, and PatchShuttle
42
43
  records the result for the next iteration.
43
44
 
44
45
  > [!IMPORTANT]
45
- > PatchShuttle is alpha software. Version `0.1.0a3` implements the complete
46
+ > PatchShuttle is alpha software. Version `0.1.0a4` implements the complete
46
47
  > local v0.1 workflow. It executes bounded read-only
47
48
  > audits, approved patch transactions, and approved one-pass verification jobs
48
49
  > under a project lock. Patch jobs retain backups, run controlled checks, apply
@@ -53,8 +54,9 @@ records the result for the next iteration.
53
54
  > Trusted Publishing workflows are implemented. Every versioned release must
54
55
  > pass its own local qualification, GitHub-hosted Windows/Ubuntu matrix,
55
56
  > TestPyPI installation, production publication, and post-release smoke gates.
56
- > The immutable `0.1.0a2` evidence and documented ChatGPT end-to-end workflow
57
- > are retained in [docs/RELEASE.md](docs/RELEASE.md).
57
+ > The immutable `0.1.0a2` and `0.1.0a3` qualification evidence, plus the
58
+ > separately scoped `0.1.0a2` ChatGPT end-to-end workflow, are retained in
59
+ > [docs/RELEASE.md](docs/RELEASE.md).
58
60
 
59
61
  > [!NOTE]
60
62
  > Compared with `0.1.0a2`, version `0.1.0a3` adds AI-facing planner diagnostics,
@@ -64,6 +66,16 @@ records the result for the next iteration.
64
66
  > cleanup, and guarded physical-line range actions with a canonical read-only
65
67
  > range hash.
66
68
 
69
+ > [!NOTE]
70
+ > The current development branch also contains unreleased work completed after
71
+ > `0.1.0a3`: `search_context`, `read_symbol`, guarded `replace_symbol`, a fixed
72
+ > Ruff F-only check, compact text and JSON AI log views, explicit Django warning
73
+ > baselines, owner-controlled project-check Python selection, evidence-only
74
+ > Python discovery telemetry, expanded Python secret redaction, nested
75
+ > dependency-environment exclusions, and bounded declaration-only
76
+ > `python_structure` discovery. These features are available from the source
77
+ > branch and are not part of the immutable `0.1.0a3` PyPI artifacts.
78
+
67
79
  ## Design goals
68
80
 
69
81
  - Keep every change local and explicitly initiated by the user.
@@ -82,7 +94,7 @@ After publication, install the exact alpha release from PyPI:
82
94
  ```bash
83
95
  python -m venv .venv
84
96
  python -m pip install --upgrade pip
85
- python -m pip install "patchshuttle==0.1.0a3"
97
+ python -m pip install "patchshuttle==0.1.0a4"
86
98
  ```
87
99
 
88
100
  For development from a local checkout:
@@ -95,7 +107,7 @@ To enable opt-in HTML template linting in an installed release, install the
95
107
  `html` extra:
96
108
 
97
109
  ```bash
98
- python -m pip install "patchshuttle[html]==0.1.0a3"
110
+ python -m pip install "patchshuttle[html]==0.1.0a4"
99
111
  ```
100
112
 
101
113
  For a local development checkout, install both development and HTML extras:
@@ -158,6 +170,7 @@ initializing a workspace or reading protected generated files:
158
170
  patchshuttle capabilities
159
171
  patchshuttle schema
160
172
  patchshuttle explain replace_exact
173
+ patchshuttle explain replace_symbol
161
174
  patchshuttle explain replace_range
162
175
  patchshuttle explain hash_range
163
176
  patchshuttle explain apply_diff
@@ -299,9 +312,14 @@ checks:
299
312
  modules: [email_client.views, email_client.urls]
300
313
  ```
301
314
 
302
- PatchShuttle runs the current interpreter with `manage.py shell -c` and
303
- internally generated import code. The YAML accepts only bounded dotted module
304
- identifiers, not an arbitrary expression or shell command.
315
+ PatchShuttle runs the owner-selected project interpreter with
316
+ `manage.py shell -c` and internally generated import code. The optional local
317
+ `[execution] python_executable` value in `patches/patchshuttle.toml` may be an
318
+ absolute path or a path relative to the workspace root. When it is omitted,
319
+ PatchShuttle preserves the current `sys.executable` behavior. Jobs cannot set
320
+ or override the interpreter, and PatchShuttle does not auto-detect a virtual
321
+ environment. The YAML accepts only bounded dotted module identifiers, not an
322
+ arbitrary expression or shell command.
305
323
 
306
324
  Load a job from disk with the same validation contract:
307
325
 
@@ -329,10 +347,11 @@ patchshuttle plan patches/examples/PATCH-EXAMPLE.psh.yaml
329
347
  patchshuttle plan patches/examples/PATCH-EXAMPLE.psh.yaml --diff
330
348
  ```
331
349
 
332
- The command displays the normalized job hash, sequential action dispositions,
333
- files and directories that would be created or modified, requested checks,
334
- Python formatting scope, a per-file isort/Black decision matrix, optional HTML
335
- lint scope, successful quality preflight records, protected-path result, backup
350
+ The command displays the normalized job hash, effective project Python
351
+ interpreter when one is used, sequential action dispositions, files and
352
+ directories that would be created or modified, requested checks, Python
353
+ formatting scope, a per-file isort/Black decision matrix, optional HTML lint
354
+ scope, successful quality preflight records, protected-path result, backup
336
355
  destination template, rollback policy, and whether later execution will
337
356
  require confirmation.
338
357
  `--diff` also prints a bounded unified preview of the final resolved bytes
@@ -450,12 +469,18 @@ locations, the archived job copy, initial check results, formatter results,
450
469
  retained formatted-file states, final check results, audit observations, and a
451
470
  workspace comparison where applicable.
452
471
 
453
- An audit executes `tree`, `read`, literal `search`, `find_files`, `file_info`,
454
- SHA-256 `hash`, canonical `hash_range`, `git_status`, and `environment`.
455
- Traversal, file reads, match counts, and recorded output are bounded by local
456
- policy. Protected and ignored paths are skipped, source content appears only
457
- when explicitly requested by a `read` or `search` action, and a before/after
458
- inventory verifies that the audit did not modify the workspace.
472
+ An audit executes `tree`, `read`, literal `search`, bounded `search_context`,
473
+ Python `read_symbol`, `find_files`, `file_info`, SHA-256 `hash`, canonical
474
+ `hash_range`, `git_status`, and `environment`. Traversal, file reads, match
475
+ counts, and recorded output are bounded by local policy. Protected and ignored
476
+ paths are skipped, source content appears only when explicitly requested by a
477
+ content-reading audit action, and a before/after inventory verifies that the
478
+ audit did not modify the workspace.
479
+
480
+ `search_context` reports bounded physical-line windows around literal matches.
481
+ `read_symbol` parses Python source without importing it, resolves exactly one
482
+ class, function, method, or nested symbol, includes its decorators, and reports
483
+ the canonical source range and SHA-256 used by `replace_symbol`.
459
484
 
460
485
  A verify job runs its controlled checks once, does not create a backup or run
461
486
  formatters, and compares the workspace before and after. A successful check
@@ -464,9 +489,9 @@ project checks can have external effects, PatchShuttle reports but does not
464
489
  automatically undo those changes.
465
490
 
466
491
  A patch plan may contain `create_directory`, `create_file`, `replace_exact`,
467
- `insert_before`, `insert_after`, `delete_exact`, `replace_range`,
468
- `delete_range`, `insert_at_line`, or `apply_diff` actions and applies the final
469
- bytes already computed by the planner.
492
+ `replace_symbol`, `insert_before`, `insert_after`, `delete_exact`,
493
+ `replace_range`, `delete_range`, `insert_at_line`, or `apply_diff` actions and
494
+ applies the final bytes already computed by the planner.
470
495
 
471
496
  The optional line-range actions are strict guarded operations for cases where
472
497
  a current audit already established an exact physical range. Lines are 1-based
@@ -477,6 +502,13 @@ and SHA-256 over UTF-8 bytes. PatchShuttle never fuzzes, relocates, partially
477
502
  applies, or automatically shifts a stale range. Use an audit `hash_range` when
478
503
  a digest is more compact than repeating a large old block.
479
504
 
505
+ A `replace_symbol` action names a dotted Python symbol, supplies the canonical
506
+ SHA-256 returned by `read_symbol`, and provides complete replacement source.
507
+ Planning resolves the symbol against the current sequential simulated file,
508
+ rejects missing, duplicate, syntactically invalid, or stale targets, and does
509
+ not use fuzzy relocation or automatic line shifting. Exact desired symbol
510
+ content is an idempotent `NO_CHANGE`.
511
+
480
512
  For a supported plan, the implemented sequence is:
481
513
 
482
514
  1. validate the workspace lock file and acquire a non-blocking cross-platform
@@ -532,18 +564,21 @@ overwritten. Rollback validates retained originals before restoring them,
532
564
  refuses to follow symbolic links or remove foreign non-empty directories, and
533
565
  does not claim success when a tracked path cannot be restored.
534
566
 
535
- The internal check runner supports `compileall`, `pytest`, `unittest`, Django
536
- checks and tests, plain validated module imports, Django-aware validated module
537
- imports through `manage.py shell -c`, and locally configured profiles. It
538
- inherits the current process environment because project checks execute project
539
- code and PatchShuttle is not an operating-system sandbox. PatchShuttle overrides
540
- `PYTHONPYCACHEPREFIX` with a fresh temporary directory for each check and
541
- removes that directory afterward, keeping ordinary generated Python bytecode
542
- outside the workspace. A defensive cache ledger also handles `.pyc` files
543
- created inside newly added packages despite that override. Formatter order is
544
- fixed to isort then Black for protocol 1; local exact-path exclusions are
545
- resolved separately for each tool. Non-Python jobs skip formatting and do not
546
- repeat checks. Optional djLint uses a locally selected template profile,
567
+ The internal check runner supports the fixed `ruff: {}` F-only check,
568
+ `compileall`, `pytest`, `unittest`, Django checks and tests, plain validated
569
+ module imports, Django-aware validated module imports through
570
+ `manage.py shell -c`, and locally configured profiles. Ruff receives only the
571
+ immutable ordered Python scope derived from current non-no-change patch actions;
572
+ a job cannot supply paths, rules, fixes, or arbitrary Ruff arguments.
573
+ Project checks inherit the current process environment because they execute
574
+ project code and PatchShuttle is not an operating-system sandbox. PatchShuttle
575
+ overrides `PYTHONPYCACHEPREFIX` with a fresh temporary directory for each check
576
+ and removes that directory afterward, keeping ordinary generated Python
577
+ bytecode outside the workspace. A defensive cache ledger also handles `.pyc`
578
+ files created inside newly added packages despite that override. Formatter
579
+ order is fixed to isort then Black for protocol 1; local exact-path exclusions
580
+ are resolved separately for each tool. Non-Python jobs skip formatting and do
581
+ not repeat checks. Optional djLint uses a locally selected template profile,
547
582
  targets only changed `.html` files, never reformats them, and triggers the same
548
583
  rollback path on failure. It reads content through stdin from an isolated
549
584
  temporary configuration root, so project djLint configuration cannot weaken
@@ -592,9 +627,12 @@ Every job run log contains all standard sections in a fixed order, including
592
627
  `LINT_HTML`, using `NOT_APPLICABLE` where a stage did not run, and ends with
593
628
  `PATCHSHUTTLE_AI_HANDOFF`. Check and formatter output is bounded by local
594
629
  policy, as is HTML linter output. Common password, token, API-key,
595
- authorization-header, and private-key shapes are masked when redaction is
596
- enabled. Redaction is best-effort and is not a guarantee that a log contains no
597
- secrets; review a log before uploading it to any AI service.
630
+ authorization-header, private-key, and sensitive Python assignment shapes are
631
+ masked when redaction is enabled. Python assignment matching includes prefixed
632
+ passwords, API and access keys, secret and private keys, tokens, and one-line
633
+ `SECRET_KEY_FALLBACKS` collections while preserving loader calls such as
634
+ `config("SECRET_KEY")`. Redaction is best-effort and is not a guarantee that a
635
+ log contains no secrets; review a log before uploading it to any AI service.
598
636
 
599
637
  Early `VALIDATION_FAILED` and `PLAN_FAILED` logs use a smaller fixed attempt
600
638
  format with `SUMMARY` and `PATCHSHUTTLE_AI_HANDOFF` sections. They record no
@@ -602,16 +640,162 @@ project changes, backup, job archive, or registry update. An explicitly
602
640
  declined reviewed plan continues to use the full job log and exact failed-job
603
641
  archive with result `USER_DECLINED`.
604
642
 
605
- Find the latest upload-friendly log:
643
+ Django system-check warnings can use an explicit project-local baseline:
644
+
645
+ ```bash
646
+ patchshuttle warnings
647
+ patchshuttle warnings --add urls.W005
648
+ patchshuttle warnings --remove urls.W005
649
+ ```
650
+
651
+ Only explicit W-class IDs are accepted. A `django_check` retains its normal
652
+ exit code and full log output while adding `known_warnings`, `new_warnings`,
653
+ and complete new-warning details. If captured output is truncated,
654
+ classification is reported as incomplete. Existing workspaces without the
655
+ protected baseline file start with an empty baseline; `patchshuttle init`
656
+ creates the missing scaffold file without overwriting existing state.
657
+ Find or compactly render the latest upload-friendly log:
606
658
 
607
659
  ```bash
608
660
  patchshuttle logs --last
661
+ patchshuttle logs --last --ai
662
+ patchshuttle logs --last --ai-json
609
663
  ```
610
664
 
611
- This selects the newest recorded run, snapshot, handoff, or failure-attempt
612
- log. Commands that intentionally produce no artifact, including successful
613
- `validate`, successful `plan`, `version`, `capabilities`, `schema`, and
614
- `explain`, do not replace it.
665
+ The default prints the path. `--ai` prints a bounded deterministic text view,
666
+ while `--ai-json` prints the same selected data as compact JSON. These modes
667
+ read the already stored log, preserve it unchanged, and never execute project
668
+ code. They are mutually exclusive and retain useful audit outputs and failed
669
+ check diagnostics within explicit bounds.
670
+
671
+ Run and early validation/planning failure logs also expose
672
+ `PYTHON_DISCOVERY_EVALUATION`. The section records only current-job evidence:
673
+ explicit Python paths, executed audit action counts and bounded output size
674
+ before whole-log redaction,
675
+ declared text/symbol/line targeting, and relevant failure signals.
676
+ `index_assessment: NOT_EVALUATED` is intentional: the report neither estimates
677
+ AI token savings nor decides whether a project symbol index is needed.
678
+
679
+ ### Discover Python structure before targeted reads
680
+
681
+ The read-only `python_structure` audit action collects a bounded declaration
682
+ map before targeted reads. Start with the narrowest useful directory or one
683
+ `.py` file because a broad package can produce a large audit result.
684
+
685
+ A complete YAML audit job looks like this:
686
+
687
+ ```yaml
688
+ protocol: 1
689
+ project_id: PSH-0123456789ABCDEF
690
+ id: AUDIT-PYTHON-STRUCTURE-001
691
+ kind: audit
692
+ actions:
693
+ - python_structure:
694
+ path: src/example
695
+ max_files: 50
696
+ max_symbols: 400
697
+ compact: true
698
+ checks: []
699
+ ```
700
+
701
+ `path` defaults to `.`, `max_files` defaults to `300`, `max_symbols`
702
+ defaults to `2000`, and `compact` defaults to `false`. A file target must end
703
+ in `.py`. Directory traversal respects the workspace's protected and ignored
704
+ paths. The standard-library AST is used without importing or executing project
705
+ code. Use `compact: true` for a navigational overview and omit it when detailed
706
+ imports, parameters, decorators, and bases are required.
707
+
708
+ Compact output begins with the same collection counts, parse errors, and limit
709
+ signals as full output, followed by per-file records containing only counts and
710
+ navigational symbol metadata:
711
+
712
+ ```text
713
+ schema: patchshuttle.python_structure_collection.compact.v1
714
+ path: src/example
715
+ glob: *.py
716
+ files_considered: 1
717
+ files_parsed: 1
718
+ parse_errors: 0
719
+ imports_reported: 3
720
+ symbols_available: 2
721
+ symbols_reported: 2
722
+ file_limit_reached: false
723
+ symbol_limit_reached: false
724
+
725
+ schema: patchshuttle.python_structure.compact.v1
726
+ file: "src/example/service.py"
727
+ imports: 3
728
+ symbols: 2
729
+ symbol: {"kind":"class","lines":[10,42],"qualified_name":"Service"}
730
+ symbol: {"kind":"method","lines":[14,31],"qualified_name":"Service.run"}
731
+ ```
732
+
733
+ With `compact: false`, the existing
734
+ `patchshuttle.python_structure_collection.v1` and
735
+ `patchshuttle.python_structure.v1` schemas remain unchanged and additionally
736
+ include top-level import records, parameters, decorators, and bases.
737
+
738
+ Use the reported qualified name for a smaller follow-up audit:
739
+
740
+ ```yaml
741
+ actions:
742
+ - read_symbol:
743
+ path: src/example/service.py
744
+ symbol: Service.run
745
+ ```
746
+
747
+ If a change is needed, copy the canonical SHA-256 returned by `read_symbol`
748
+ into a guarded patch action:
749
+
750
+ ```yaml
751
+ actions:
752
+ - replace_symbol:
753
+ path: src/example/service.py
754
+ symbol: Service.run
755
+ expected_sha256: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
756
+ new_content: |
757
+ def run(self, value):
758
+ return value
759
+ ```
760
+
761
+ The same audit can be constructed through the public Python adapter:
762
+
763
+ ```python
764
+ from patchshuttle import Job
765
+ from patchshuttle.actions import python_structure
766
+
767
+ job = Job(
768
+ protocol=1,
769
+ project_id="PSH-0123456789ABCDEF",
770
+ id="AUDIT-PYTHON-STRUCTURE-001",
771
+ kind="audit",
772
+ actions=[
773
+ python_structure(
774
+ "src/example", max_files=50, max_symbols=400, compact=True
775
+ )
776
+ ],
777
+ )
778
+ ```
779
+
780
+ Full `python_structure` output reports declarations, top-level imports,
781
+ parameters, decorators, bases, source ranges, parse errors, and explicit limit
782
+ signals. Compact output retains file and symbol counts, symbol kinds, qualified
783
+ names, ranges, parse errors, and limit signals. Both modes exclude source values,
784
+ calls, references, inferred types, caching, persistence, and automatic patch
785
+ scope.
786
+
787
+ The same section reports aggregate Python file/search matches, whether file
788
+ discovery or searches reached their declared result bounds, and total duration
789
+ of executed Python-targeted audit actions. Newly initialized workspaces ignore
790
+ and protect conventional `.venv`, `venv`, and `node_modules` trees at any
791
+ depth. Existing `patches/patchshuttle.toml` files are never rewritten by
792
+ `init`; owners must add nested environment patterns and project-specific
793
+ backup paths to `project.ignored_paths` when needed.
794
+
795
+ The command selects the newest recorded run, snapshot, handoff, or
796
+ failure-attempt log. Commands that intentionally produce no artifact,
797
+ including successful `validate`, successful `plan`, `version`,
798
+ `capabilities`, `schema`, and `explain`, do not replace it.
615
799
 
616
800
  Inspect all registered jobs or one job ID:
617
801
 
@@ -687,17 +871,64 @@ python -m coverage report --fail-under=100
687
871
  python -m build
688
872
  python -m twine check dist/*
689
873
  python tools/release_checks.py dist
690
- python tools/wheel_smoke.py dist/patchshuttle-0.1.0a3-py3-none-any.whl --version 0.1.0a3
874
+ python tools/wheel_smoke.py dist/patchshuttle-0.1.0a4-py3-none-any.whl --version 0.1.0a4
691
875
  ```
692
876
 
693
- The `0.1.0a2` alpha release passed the required Ubuntu and Windows
694
- compatibility matrix, TestPyPI qualification, production PyPI Trusted
695
- Publishing, and a clean post-release installation. The exact release, workflow
696
- links, artifact hashes, and ChatGPT end-to-end evidence are recorded in
697
- [docs/RELEASE.md](docs/RELEASE.md). Follow that guide in order for future
698
- releases, including `0.1.0a3`, and append separate immutable evidence after
699
- every gate completes. Rerun CI after every release-candidate change.
877
+ The `0.1.0a2` and `0.1.0a3` alpha releases each passed the required Ubuntu and
878
+ Windows compatibility matrix, TestPyPI qualification, production PyPI Trusted
879
+ Publishing, and a clean post-release installation. Their exact release and
880
+ workflow links and artifact hashes, together with the separately scoped
881
+ `0.1.0a2` ChatGPT end-to-end evidence, are recorded in
882
+ [docs/RELEASE.md](docs/RELEASE.md). Follow that guide in order for every future
883
+ release and append separate immutable evidence after each gate completes.
884
+ Rerun CI after every release-candidate change.
885
+
886
+ ## Python architecture policy
887
+
888
+ PatchShuttle can stop an AI-generated patch from making an already crowded Python
889
+ codebase worse. The policy is local owner configuration, not a job option, and is
890
+ enabled by default for the fixed `modular-monolith` / `package-by-feature`
891
+ profile in `ratchet` mode.
892
+
893
+ ```toml
894
+ [architecture]
895
+ enabled = true
896
+ profile = "modular-monolith"
897
+ organization = "package-by-feature"
898
+ mode = "ratchet"
899
+ exclude = ["**/migrations/**", "**/generated/**"]
900
+ max_report_items = 50
901
+
902
+ [architecture.module]
903
+ warning_lines = 500
904
+ max_lines = 1000
905
+
906
+ [architecture.package]
907
+ warning_python_files = 15
908
+ max_python_files = 25
909
+
910
+ [architecture.patch]
911
+ warning_new_python_files = 5
912
+ max_new_python_files = 10
913
+ warning_new_packages = 1
914
+ max_new_packages = 3
915
+ ```
700
916
 
917
+ The planner evaluates its in-memory result before touching the workspace. A file
918
+ already above a limit may stay the same size or shrink; making it larger is a new
919
+ regression and is blocked at the hard limit. The same rule applies to direct
920
+ Python-file counts in touched directories. Patch-wide file and package budgets
921
+ catch the kind of broad root-level file growth that is difficult to review.
922
+
923
+ Plans and stored logs expose the profile, status, measured counts, bounded
924
+ findings, and whether the report was truncated. `ARCH001`/`ARCH002` cover module
925
+ size, `ARCH010`/`ARCH011` package size, and `ARCH020`/`ARCH021` patch growth.
926
+ Warnings remain visible but do not block execution; errors fail planning before
927
+ confirmation, backup, or project checks.
928
+
929
+ This is deliberately a structural guard. It does not infer feature boundaries,
930
+ build dependency or call graphs, detect cycles, judge semantic responsibility,
931
+ reorganize files, or change the patch scope selected by the AI.
701
932
  ## Manual workflow
702
933
 
703
934
  The implemented local cycle is: