patchshuttle 0.1.0a5__tar.gz → 0.1.0a6__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 (148) hide show
  1. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/.github/workflows/ci.yml +37 -5
  2. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/CHANGELOG.md +24 -1
  3. patchshuttle-0.1.0a5/README.md → patchshuttle-0.1.0a6/PKG-INFO +113 -4
  4. patchshuttle-0.1.0a5/PKG-INFO → patchshuttle-0.1.0a6/README.md +69 -41
  5. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/SPEC_V0_1.md +85 -1
  6. patchshuttle-0.1.0a6/docs/CHATGPT_INTEGRATION.md +54 -0
  7. patchshuttle-0.1.0a6/docs/DJANGO_MIGRATIONS.md +83 -0
  8. patchshuttle-0.1.0a6/docs/NEXT_RELEASE.md +170 -0
  9. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/docs/RELEASE.md +14 -0
  10. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/pyproject.toml +5 -0
  11. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_architecture.py +3 -1
  12. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_python_symbols.py +13 -1
  13. patchshuttle-0.1.0a6/src/patchshuttle/_refactor.py +147 -0
  14. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_version.py +1 -1
  15. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/actions/__init__.py +14 -0
  16. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/actions/constructors.py +108 -2
  17. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/actions/create.py +6 -0
  18. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/actions/modify.py +12 -0
  19. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/audit.py +34 -0
  20. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/backup.py +28 -2
  21. patchshuttle-0.1.0a6/src/patchshuttle/django_app/__init__.py +1 -0
  22. patchshuttle-0.1.0a6/src/patchshuttle/django_app/management/__init__.py +1 -0
  23. patchshuttle-0.1.0a6/src/patchshuttle/django_app/management/commands/__init__.py +1 -0
  24. patchshuttle-0.1.0a6/src/patchshuttle/django_app/management/commands/patchshuttle_migrate.py +50 -0
  25. patchshuttle-0.1.0a6/src/patchshuttle/django_app/workflow.py +306 -0
  26. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/execution.py +2 -0
  27. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/formatters/runner.py +5 -1
  28. patchshuttle-0.1.0a6/src/patchshuttle/integrations/__init__.py +1 -0
  29. patchshuttle-0.1.0a6/src/patchshuttle/integrations/mcp_server.py +182 -0
  30. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/linters/runner.py +1 -1
  31. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/logging.py +1 -0
  32. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/models.py +114 -4
  33. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/planner.py +248 -187
  34. patchshuttle-0.1.0a6/src/patchshuttle/planning/__init__.py +1 -0
  35. patchshuttle-0.1.0a6/src/patchshuttle/planning/imports.py +184 -0
  36. patchshuttle-0.1.0a6/src/patchshuttle/planning/types.py +194 -0
  37. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/preflight.py +2 -0
  38. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/resources/AI_GUIDE.md +83 -0
  39. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/resources/PATCHSHUTTLE_PROTOCOL.md +79 -2
  40. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/rollback.py +6 -0
  41. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/runner.py +18 -18
  42. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/selfdoc.py +33 -3
  43. patchshuttle-0.1.0a6/tests/integrations/test_django_migrations.py +356 -0
  44. patchshuttle-0.1.0a6/tests/integrations/test_imports.py +186 -0
  45. patchshuttle-0.1.0a6/tests/integrations/test_mcp_server.py +242 -0
  46. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_cli.py +2 -2
  47. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_formatters.py +4 -0
  48. patchshuttle-0.1.0a6/tests/unit/test_move_file.py +217 -0
  49. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_package.py +1 -1
  50. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_preflight.py +2 -0
  51. patchshuttle-0.1.0a6/tests/unit/test_refactor.py +330 -0
  52. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_release_configuration.py +23 -3
  53. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_selfdoc.py +1 -1
  54. patchshuttle-0.1.0a6/tests/unit/test_structural_guards.py +251 -0
  55. patchshuttle-0.1.0a6/tools/django_postgres_smoke.py +88 -0
  56. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/.github/workflows/release.yml +0 -0
  57. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/.github/workflows/testpypi.yml +0 -0
  58. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/.gitignore +0 -0
  59. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/LICENSE +0 -0
  60. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/SECURITY.md +0 -0
  61. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/__init__.py +0 -0
  62. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_ai_log.py +0 -0
  63. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_diff.py +0 -0
  64. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_html_lint.py +0 -0
  65. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_line_ranges.py +0 -0
  66. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_process.py +0 -0
  67. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_python_discovery.py +0 -0
  68. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/_python_structure.py +0 -0
  69. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/checks/__init__.py +0 -0
  70. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/checks/constructors.py +0 -0
  71. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/checks/runner.py +0 -0
  72. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/cli.py +0 -0
  73. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/config.py +0 -0
  74. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/context.py +0 -0
  75. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/errors.py +0 -0
  76. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/formatter_policy.py +0 -0
  77. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/formatters/__init__.py +0 -0
  78. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/history/__init__.py +0 -0
  79. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/history/cli.py +0 -0
  80. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/history/models.py +0 -0
  81. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/history/records.py +0 -0
  82. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/history/storage.py +0 -0
  83. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/identifiers.py +0 -0
  84. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/inventory.py +0 -0
  85. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/linters/__init__.py +0 -0
  86. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/operations.py +0 -0
  87. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/parser.py +0 -0
  88. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/policy.py +0 -0
  89. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/project_python.py +0 -0
  90. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/py.typed +0 -0
  91. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/registry.py +0 -0
  92. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/resources/AUDIT-EXAMPLE.psh.yaml +0 -0
  93. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/resources/PATCH-EXAMPLE.psh.yaml +0 -0
  94. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/resources/__init__.py +0 -0
  95. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/runtime_cache.py +0 -0
  96. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/verification.py +0 -0
  97. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/warning_baseline.py +0 -0
  98. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/src/patchshuttle/workspace.py +0 -0
  99. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/e2e/test_cli_project_cycle.py +0 -0
  100. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/integration/test_workflows.py +0 -0
  101. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/history/test_history.py +0 -0
  102. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/history/test_history_integration.py +0 -0
  103. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/history/test_history_storage_edges.py +0 -0
  104. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_a4_coverage_regressions.py +0 -0
  105. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_actions.py +0 -0
  106. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_ai_log.py +0 -0
  107. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_architecture.py +0 -0
  108. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_attempt_logging.py +0 -0
  109. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_audit_verify_execution.py +0 -0
  110. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_backup.py +0 -0
  111. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_change_transaction.py +0 -0
  112. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_check_transaction.py +0 -0
  113. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_checks.py +0 -0
  114. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_config.py +0 -0
  115. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_context_and_constructors.py +0 -0
  116. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_diff.py +0 -0
  117. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_execution.py +0 -0
  118. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_formatting_transaction.py +0 -0
  119. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_guarded_ranges.py +0 -0
  120. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_html_lint.py +0 -0
  121. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_identifiers.py +0 -0
  122. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_inventory.py +0 -0
  123. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_inventory_transaction.py +0 -0
  124. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_logging.py +0 -0
  125. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_manual_rollback.py +0 -0
  126. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_models.py +0 -0
  127. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_modify_actions.py +0 -0
  128. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_operational_execution.py +0 -0
  129. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_parser.py +0 -0
  130. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_phase19_cli.py +0 -0
  131. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_planner.py +0 -0
  132. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_planner_ux.py +0 -0
  133. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_policy.py +0 -0
  134. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_project_python.py +0 -0
  135. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_python_discovery.py +0 -0
  136. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_python_structure.py +0 -0
  137. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_python_structure_action.py +0 -0
  138. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_python_symbols.py +0 -0
  139. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_registry.py +0 -0
  140. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_rollback.py +0 -0
  141. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_runner.py +0 -0
  142. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_runtime_cache.py +0 -0
  143. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_warning_baseline.py +0 -0
  144. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tests/unit/test_workspace.py +0 -0
  145. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tools/check_tag.py +0 -0
  146. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tools/index_smoke.py +0 -0
  147. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tools/release_checks.py +0 -0
  148. {patchshuttle-0.1.0a5 → patchshuttle-0.1.0a6}/tools/wheel_smoke.py +0 -0
@@ -47,14 +47,46 @@ jobs:
47
47
  - name: Check formatting
48
48
  run: python -m black --check src tests tools
49
49
  - name: Run tests with branch coverage
50
- run: |
51
- python -m coverage erase
52
- python -m coverage run -m pytest -q
53
- python -m coverage report --fail-under=100
50
+ run: python -m coverage run -m pytest -q
51
+ - name: Require full branch coverage
52
+ run: python -m coverage report --fail-under=100
53
+
54
+ postgres:
55
+ name: Django / PostgreSQL 16
56
+ runs-on: ubuntu-24.04
57
+ timeout-minutes: 10
58
+ services:
59
+ postgres:
60
+ image: postgres:16
61
+ env:
62
+ POSTGRES_DB: patchshuttle_test
63
+ POSTGRES_USER: postgres
64
+ POSTGRES_PASSWORD: disposable-ci-password
65
+ ports:
66
+ - 5432:5432
67
+ options: >-
68
+ --health-cmd pg_isready
69
+ --health-interval 10s
70
+ --health-timeout 5s
71
+ --health-retries 5
72
+ steps:
73
+ - uses: actions/checkout@v7.0.1
74
+ - uses: actions/setup-python@v7.0.0
75
+ with:
76
+ python-version: "3.12"
77
+ - name: Install Django adapter and PostgreSQL driver
78
+ run: python -m pip install -e ".[django]" "psycopg[binary]>=3.2,<4"
79
+ - name: Check migration lifecycle and cross-connection lock
80
+ env:
81
+ PSH_PG_DATABASE: patchshuttle_test
82
+ PSH_PG_USER: postgres
83
+ PSH_PG_PASSWORD: disposable-ci-password
84
+ PSH_PG_PORT: "5432"
85
+ run: python tools/django_postgres_smoke.py
54
86
 
55
87
  package:
56
88
  name: Build and smoke-test distributions
57
- needs: test
89
+ needs: [test, postgres]
58
90
  runs-on: ubuntu-24.04
59
91
  timeout-minutes: 20
60
92
  steps:
@@ -3,7 +3,30 @@
3
3
  All notable changes to PatchShuttle are recorded in this file. The project
4
4
  uses semantic versioning, including Python-compatible pre-release versions.
5
5
 
6
- ## Unreleased
6
+ ## 0.1.0a6 - 2026-10-21
7
+
8
+ ### Added
9
+
10
+ - Guarded `read_imports`, `ensure_import` and `remove_import` with alias-aware AST matching, comment preservation and binding checks.
11
+
12
+ - Optional authenticated MCP history server for ChatGPT, scoped to one workspace and OAuth subject.
13
+ - Optional Django migration command with hashed plans, database locks, pre/post checks, explicit reversals and separate recovery journals.
14
+ - Guarded `move_range` and `move_symbol` source transfers between files,
15
+ including destination fingerprints, structural validation, Python constructors,
16
+ and shared transaction rollback.
17
+ - Guarded `move_file` with destination absence checks, byte/mode preservation,
18
+ explicit deleted-file reporting, and automatic and manual rollback.
19
+
20
+ - Optional AST guards for `replace_symbol`, with hashes and Python version
21
+ reported by `read_symbol`, replacement declaration validation and compile
22
+ validation. Existing text-only job serialization remains compatible.
23
+
24
+ ### Changed
25
+
26
+ - Formatter child interpreters and Black preflight explicitly enable Python
27
+ UTF-8 mode, including under isolated execution.
28
+
29
+ See `docs/NEXT_RELEASE.md` for implemented scope and remaining release work.
7
30
 
8
31
  ## 0.1.0a5 - 2026-08-28
9
32
 
@@ -1,3 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: patchshuttle
3
+ Version: 0.1.0a6
4
+ Summary: Local, auditable patch workflows for AI-assisted software development
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: ai,automation,cli,development-tools,patch
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: black<27,>=24
21
+ Requires-Dist: click<9,>=8.1
22
+ Requires-Dist: filelock<4,>=3.30
23
+ Requires-Dist: isort<7,>=5.13
24
+ Requires-Dist: pydantic<3,>=2.6
25
+ Requires-Dist: pyyaml<7,>=6
26
+ Requires-Dist: ruff<1,>=0.16
27
+ Requires-Dist: tomli<3,>=2; python_version < '3.11'
28
+ Provides-Extra: dev
29
+ Requires-Dist: build<2,>=1.2; extra == 'dev'
30
+ Requires-Dist: django<7,>=5.2; extra == 'dev'
31
+ Requires-Dist: mcp<2,>=1.30; extra == 'dev'
32
+ Requires-Dist: pyjwt[crypto]<3,>=2.10; extra == 'dev'
33
+ Requires-Dist: pytest-cov<8,>=5; extra == 'dev'
34
+ Requires-Dist: pytest<10,>=8; extra == 'dev'
35
+ Requires-Dist: twine<7,>=6.1; extra == 'dev'
36
+ Provides-Extra: django
37
+ Requires-Dist: django<7,>=5.2; extra == 'django'
38
+ Provides-Extra: html
39
+ Requires-Dist: djlint<2,>=1.44; extra == 'html'
40
+ Provides-Extra: mcp
41
+ Requires-Dist: mcp<2,>=1.30; extra == 'mcp'
42
+ Requires-Dist: pyjwt[crypto]<3,>=2.10; extra == 'mcp'
43
+ Description-Content-Type: text/markdown
44
+
1
45
  # PatchShuttle
2
46
 
3
47
  PatchShuttle is intended to provide local, auditable patch workflows for
@@ -6,7 +50,7 @@ describes a small job, the user reviews and runs it locally, and PatchShuttle
6
50
  records the result for the next iteration.
7
51
 
8
52
  > [!IMPORTANT]
9
- > PatchShuttle is alpha software. Version `0.1.0a5` implements the complete
53
+ > PatchShuttle is alpha software. Version `0.1.0a6` implements the complete
10
54
  > local v0.1 workflow. It executes bounded read-only
11
55
  > audits, approved patch transactions, and approved one-pass verification jobs
12
56
  > under a project lock. Patch jobs retain backups, run controlled checks, apply
@@ -63,7 +107,7 @@ After publication, install the exact alpha release from PyPI:
63
107
  ```bash
64
108
  python -m venv .venv
65
109
  python -m pip install --upgrade pip
66
- python -m pip install "patchshuttle==0.1.0a5"
110
+ python -m pip install "patchshuttle==0.1.0a6"
67
111
  ```
68
112
 
69
113
  For development from a local checkout:
@@ -76,7 +120,7 @@ To enable opt-in HTML template linting in an installed release, install the
76
120
  `html` extra:
77
121
 
78
122
  ```bash
79
- python -m pip install "patchshuttle[html]==0.1.0a5"
123
+ python -m pip install "patchshuttle[html]==0.1.0a6"
80
124
  ```
81
125
 
82
126
  For a local development checkout, install both development and HTML extras:
@@ -478,6 +522,13 @@ rejects missing, duplicate, syntactically invalid, or stale targets, and does
478
522
  not use fuzzy relocation or automatic line shifting. Exact desired symbol
479
523
  content is an idempotent `NO_CHANGE`.
480
524
 
525
+ The optional `expected_ast_sha256` guard uses the structural hash from
526
+ `read_symbol` to tolerate formatting changes. It ignores ordinary comments,
527
+ includes docstrings and decorators, and is scoped to the reported Python minor
528
+ version. Supply the text hash too when comments must be guarded. AST-guarded
529
+ replacements retain the name and kind and undergo compile and resolution
530
+ validation. See [next-release work](docs/NEXT_RELEASE.md) for current scope.
531
+
481
532
  For a supported plan, the implemented sequence is:
482
533
 
483
534
  1. validate the workspace lock file and acquire a non-blocking cross-platform
@@ -840,7 +891,7 @@ python -m coverage report --fail-under=100
840
891
  python -m build
841
892
  python -m twine check dist/*
842
893
  python tools/release_checks.py dist
843
- python tools/wheel_smoke.py dist/patchshuttle-0.1.0a5-py3-none-any.whl --version 0.1.0a5
894
+ python tools/wheel_smoke.py dist/patchshuttle-0.1.0a6-py3-none-any.whl --version 0.1.0a6
844
895
  ```
845
896
 
846
897
  The `0.1.0a2`, `0.1.0a3`, and `0.1.0a4` alpha releases each passed the
@@ -971,3 +1022,61 @@ model, or project.
971
1022
  ## License
972
1023
 
973
1024
  PatchShuttle is licensed under the MIT License.
1025
+
1026
+
1027
+ ## Guarded source transfers
1028
+
1029
+ - `move_range`: `path`, `start_line`, `end_line`, source `expected_content`
1030
+ and/or `expected_sha256`, `destination`, `destination_line` and
1031
+ `expected_destination_sha256`.
1032
+ - `move_symbol`: `path`, `symbol`, `expected_ast_sha256`, `destination`,
1033
+ `expected_destination_sha256` and optional `destination_class`.
1034
+
1035
+ Destination hashes cover simulated raw bytes; source range hashes cover
1036
+ canonical LF/UTF-8 text. Destinations must exist or be created earlier in the
1037
+ same job. Range insertion is before the 1-based destination line; EOF+1 appends.
1038
+ Symbol insertion appends at module scope or to the named class. Both files are
1039
+ validated and participate in the same backup/check/rollback transaction.
1040
+
1041
+ Same-file transfers, case-only path differences, duplicate destination names,
1042
+ closure extraction, class-bound super/private-name moves and multiline-string
1043
+ reindentation are rejected. Imports and callers are not automatically rewritten;
1044
+ include explicit edits and focused project checks. These actions move source
1045
+ content and leave the source file present. Whole-file moves are separate work.
1046
+
1047
+
1048
+ ## Whole-file transfers
1049
+
1050
+ `move_file` moves an existing supported text file to an absent destination.
1051
+ Supply `path`, `destination`, and `expected_sha256` of the original raw bytes.
1052
+ The transaction preserves original bytes and permissions, creates missing
1053
+ parent directories, and reports the source in `deleted_files`. Formatters and
1054
+ checks target the surviving destination. Add explicit import and caller edits
1055
+ when moving Python modules; imports are not automatically rewritten.
1056
+
1057
+ The source and destination must not have been touched earlier in the job.
1058
+ Subsequent edits to the destination are supported. Case-only moves, chains,
1059
+ swaps, binary files and reuse of the vacated path are currently rejected.
1060
+ A failed check restores the source and removes the destination. Manual rollback
1061
+ also verifies the applied state and refuses to overwrite a recreated source.
1062
+
1063
+
1064
+ ## Optional integrations
1065
+
1066
+ - `patchshuttle[mcp]`: authenticated, read-only history retrieval for ChatGPT. See [setup and deployment limits](docs/CHATGPT_INTEGRATION.md).
1067
+ - `patchshuttle[django]`: reviewed migration plans, explicit execution/reversal, and a separate recovery journal. See [Django workflow](docs/DJANGO_MIGRATIONS.md).
1068
+
1069
+
1070
+ ## Structural editing and optional adapters in 0.1.0a6
1071
+
1072
+ Use `read_symbol` AST hashes for guarded class/method replacements; use
1073
+ `read_imports`, `ensure_import`, and `remove_import` for structural imports.
1074
+ `move_file`, `move_range`, and `move_symbol` support explicit refactoring plans
1075
+ with shared checks and rollback. Imports and callers are not inferred.
1076
+ See [scope and examples](docs/NEXT_RELEASE.md).
1077
+
1078
+ Optional [ChatGPT history retrieval](docs/CHATGPT_INTEGRATION.md) uses an
1079
+ owner-configured authenticated MCP server (`patchshuttle[mcp]`).
1080
+ Optional [Django migrations](docs/DJANGO_MIGRATIONS.md) provide reviewed plans,
1081
+ locks and recovery journals (`patchshuttle[django]`). A live ChatGPT deployment
1082
+ requires the owner's HTTPS endpoint and OAuth configuration.
@@ -1,40 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: patchshuttle
3
- Version: 0.1.0a5
4
- Summary: Local, auditable patch workflows for AI-assisted software development
5
- License-Expression: MIT
6
- License-File: LICENSE
7
- Keywords: ai,automation,cli,development-tools,patch
8
- Classifier: Development Status :: 3 - Alpha
9
- Classifier: Environment :: Console
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3 :: Only
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Programming Language :: Python :: 3.13
17
- Classifier: Programming Language :: Python :: 3.14
18
- Classifier: Typing :: Typed
19
- Requires-Python: >=3.10
20
- Requires-Dist: black<27,>=24
21
- Requires-Dist: click<9,>=8.1
22
- Requires-Dist: filelock<4,>=3.30
23
- Requires-Dist: isort<7,>=5.13
24
- Requires-Dist: pydantic<3,>=2.6
25
- Requires-Dist: pyyaml<7,>=6
26
- Requires-Dist: ruff<1,>=0.16
27
- Requires-Dist: tomli<3,>=2; python_version < '3.11'
28
- Provides-Extra: dev
29
- Requires-Dist: build<2,>=1.2; extra == 'dev'
30
- Requires-Dist: django<7,>=5.2; extra == 'dev'
31
- Requires-Dist: pytest-cov<8,>=5; extra == 'dev'
32
- Requires-Dist: pytest<10,>=8; extra == 'dev'
33
- Requires-Dist: twine<7,>=6.1; extra == 'dev'
34
- Provides-Extra: html
35
- Requires-Dist: djlint<2,>=1.44; extra == 'html'
36
- Description-Content-Type: text/markdown
37
-
38
1
  # PatchShuttle
39
2
 
40
3
  PatchShuttle is intended to provide local, auditable patch workflows for
@@ -43,7 +6,7 @@ describes a small job, the user reviews and runs it locally, and PatchShuttle
43
6
  records the result for the next iteration.
44
7
 
45
8
  > [!IMPORTANT]
46
- > PatchShuttle is alpha software. Version `0.1.0a5` implements the complete
9
+ > PatchShuttle is alpha software. Version `0.1.0a6` implements the complete
47
10
  > local v0.1 workflow. It executes bounded read-only
48
11
  > audits, approved patch transactions, and approved one-pass verification jobs
49
12
  > under a project lock. Patch jobs retain backups, run controlled checks, apply
@@ -100,7 +63,7 @@ After publication, install the exact alpha release from PyPI:
100
63
  ```bash
101
64
  python -m venv .venv
102
65
  python -m pip install --upgrade pip
103
- python -m pip install "patchshuttle==0.1.0a5"
66
+ python -m pip install "patchshuttle==0.1.0a6"
104
67
  ```
105
68
 
106
69
  For development from a local checkout:
@@ -113,7 +76,7 @@ To enable opt-in HTML template linting in an installed release, install the
113
76
  `html` extra:
114
77
 
115
78
  ```bash
116
- python -m pip install "patchshuttle[html]==0.1.0a5"
79
+ python -m pip install "patchshuttle[html]==0.1.0a6"
117
80
  ```
118
81
 
119
82
  For a local development checkout, install both development and HTML extras:
@@ -515,6 +478,13 @@ rejects missing, duplicate, syntactically invalid, or stale targets, and does
515
478
  not use fuzzy relocation or automatic line shifting. Exact desired symbol
516
479
  content is an idempotent `NO_CHANGE`.
517
480
 
481
+ The optional `expected_ast_sha256` guard uses the structural hash from
482
+ `read_symbol` to tolerate formatting changes. It ignores ordinary comments,
483
+ includes docstrings and decorators, and is scoped to the reported Python minor
484
+ version. Supply the text hash too when comments must be guarded. AST-guarded
485
+ replacements retain the name and kind and undergo compile and resolution
486
+ validation. See [next-release work](docs/NEXT_RELEASE.md) for current scope.
487
+
518
488
  For a supported plan, the implemented sequence is:
519
489
 
520
490
  1. validate the workspace lock file and acquire a non-blocking cross-platform
@@ -877,7 +847,7 @@ python -m coverage report --fail-under=100
877
847
  python -m build
878
848
  python -m twine check dist/*
879
849
  python tools/release_checks.py dist
880
- python tools/wheel_smoke.py dist/patchshuttle-0.1.0a5-py3-none-any.whl --version 0.1.0a5
850
+ python tools/wheel_smoke.py dist/patchshuttle-0.1.0a6-py3-none-any.whl --version 0.1.0a6
881
851
  ```
882
852
 
883
853
  The `0.1.0a2`, `0.1.0a3`, and `0.1.0a4` alpha releases each passed the
@@ -1008,3 +978,61 @@ model, or project.
1008
978
  ## License
1009
979
 
1010
980
  PatchShuttle is licensed under the MIT License.
981
+
982
+
983
+ ## Guarded source transfers
984
+
985
+ - `move_range`: `path`, `start_line`, `end_line`, source `expected_content`
986
+ and/or `expected_sha256`, `destination`, `destination_line` and
987
+ `expected_destination_sha256`.
988
+ - `move_symbol`: `path`, `symbol`, `expected_ast_sha256`, `destination`,
989
+ `expected_destination_sha256` and optional `destination_class`.
990
+
991
+ Destination hashes cover simulated raw bytes; source range hashes cover
992
+ canonical LF/UTF-8 text. Destinations must exist or be created earlier in the
993
+ same job. Range insertion is before the 1-based destination line; EOF+1 appends.
994
+ Symbol insertion appends at module scope or to the named class. Both files are
995
+ validated and participate in the same backup/check/rollback transaction.
996
+
997
+ Same-file transfers, case-only path differences, duplicate destination names,
998
+ closure extraction, class-bound super/private-name moves and multiline-string
999
+ reindentation are rejected. Imports and callers are not automatically rewritten;
1000
+ include explicit edits and focused project checks. These actions move source
1001
+ content and leave the source file present. Whole-file moves are separate work.
1002
+
1003
+
1004
+ ## Whole-file transfers
1005
+
1006
+ `move_file` moves an existing supported text file to an absent destination.
1007
+ Supply `path`, `destination`, and `expected_sha256` of the original raw bytes.
1008
+ The transaction preserves original bytes and permissions, creates missing
1009
+ parent directories, and reports the source in `deleted_files`. Formatters and
1010
+ checks target the surviving destination. Add explicit import and caller edits
1011
+ when moving Python modules; imports are not automatically rewritten.
1012
+
1013
+ The source and destination must not have been touched earlier in the job.
1014
+ Subsequent edits to the destination are supported. Case-only moves, chains,
1015
+ swaps, binary files and reuse of the vacated path are currently rejected.
1016
+ A failed check restores the source and removes the destination. Manual rollback
1017
+ also verifies the applied state and refuses to overwrite a recreated source.
1018
+
1019
+
1020
+ ## Optional integrations
1021
+
1022
+ - `patchshuttle[mcp]`: authenticated, read-only history retrieval for ChatGPT. See [setup and deployment limits](docs/CHATGPT_INTEGRATION.md).
1023
+ - `patchshuttle[django]`: reviewed migration plans, explicit execution/reversal, and a separate recovery journal. See [Django workflow](docs/DJANGO_MIGRATIONS.md).
1024
+
1025
+
1026
+ ## Structural editing and optional adapters in 0.1.0a6
1027
+
1028
+ Use `read_symbol` AST hashes for guarded class/method replacements; use
1029
+ `read_imports`, `ensure_import`, and `remove_import` for structural imports.
1030
+ `move_file`, `move_range`, and `move_symbol` support explicit refactoring plans
1031
+ with shared checks and rollback. Imports and callers are not inferred.
1032
+ See [scope and examples](docs/NEXT_RELEASE.md).
1033
+
1034
+ Optional [ChatGPT history retrieval](docs/CHATGPT_INTEGRATION.md) uses an
1035
+ owner-configured authenticated MCP server (`patchshuttle[mcp]`).
1036
+ Optional [Django migrations](docs/DJANGO_MIGRATIONS.md) provide reviewed plans,
1037
+ locks and recovery journals (`patchshuttle[django]`). A live ChatGPT deployment
1038
+ requires the owner's HTTPS endpoint and OAuth configuration.
@@ -863,7 +863,9 @@ Fields:
863
863
 
864
864
  - `path`: required existing Python text file;
865
865
  - `symbol`: required dotted class, function, method, or nested-symbol name;
866
- - `expected_sha256`: required canonical hash returned by `read_symbol`;
866
+ - `expected_sha256`: optional canonical text hash returned by `read_symbol`;
867
+ - `expected_ast_sha256`: optional structural hash returned by `read_symbol`;
868
+ - at least one hash is required; when both are supplied both must match;
867
869
  - `new_content`: required complete replacement source.
868
870
 
869
871
  Planning parses the current sequential simulated file without importing it,
@@ -874,6 +876,15 @@ desired symbol content returns `NO_CHANGE`. Execution publishes the planner's
874
876
  final file bytes through the normal guarded transaction, backup, check,
875
877
  workspace-comparison, and rollback lifecycle.
876
878
 
879
+ With `expected_ast_sha256`, whitespace and ordinary comment differences do not
880
+ invalidate the guard. Hashes include AST fields but exclude source positions,
881
+ and are scoped to the `ast_python` major/minor version reported by the audit.
882
+ Docstrings, decorators and annotations remain part of the AST. The replacement
883
+ must be one declaration of the same name and kind, the final module must compile
884
+ without execution, and the qualified target must still resolve uniquely.
885
+ Use the text guard as well when comment changes must invalidate the job.
886
+ Old text-guarded jobs retain their normalized serialization and registry hash.
887
+
877
888
  ### 13.5 `insert_before`
878
889
 
879
890
  Fields:
@@ -2085,3 +2096,76 @@ unless this specification is explicitly revised:
2085
2096
  - logs are plain UTF-8 text with timestamped names and an AI handoff block;
2086
2097
  - implementation begins with tests for the contract before completing the
2087
2098
  execution engine.
2099
+
2100
+
2101
+ ## Guarded source transfers
2102
+
2103
+ - `move_range`: `path`, `start_line`, `end_line`, source `expected_content`
2104
+ and/or `expected_sha256`, `destination`, `destination_line` and
2105
+ `expected_destination_sha256`.
2106
+ - `move_symbol`: `path`, `symbol`, `expected_ast_sha256`, `destination`,
2107
+ `expected_destination_sha256` and optional `destination_class`.
2108
+
2109
+ Destination hashes cover simulated raw bytes; source range hashes cover
2110
+ canonical LF/UTF-8 text. Destinations must exist or be created earlier in the
2111
+ same job. Range insertion is before the 1-based destination line; EOF+1 appends.
2112
+ Symbol insertion appends at module scope or to the named class. Both files are
2113
+ validated and participate in the same backup/check/rollback transaction.
2114
+
2115
+ Same-file transfers, case-only path differences, duplicate destination names,
2116
+ closure extraction, class-bound super/private-name moves and multiline-string
2117
+ reindentation are rejected. Imports and callers are not automatically rewritten;
2118
+ include explicit edits and focused project checks. These actions move source
2119
+ content and leave the source file present. Whole-file moves are separate work.
2120
+
2121
+
2122
+ ## Whole-file transfers
2123
+
2124
+ `move_file` moves an existing supported text file to an absent destination.
2125
+ Supply `path`, `destination`, and `expected_sha256` of the original raw bytes.
2126
+ The transaction preserves original bytes and permissions, creates missing
2127
+ parent directories, and reports the source in `deleted_files`. Formatters and
2128
+ checks target the surviving destination. Add explicit import and caller edits
2129
+ when moving Python modules; imports are not automatically rewritten.
2130
+
2131
+ The source and destination must not have been touched earlier in the job.
2132
+ Subsequent edits to the destination are supported. Case-only moves, chains,
2133
+ swaps, binary files and reuse of the vacated path are currently rejected.
2134
+ A failed check restores the source and removes the destination. Manual rollback
2135
+ also verifies the applied state and refuses to overwrite a recreated source.
2136
+
2137
+
2138
+ ## Structural import edits
2139
+
2140
+ `read_imports` accepts `path` and optional `max_bytes`. It reports top-level
2141
+ imports, `imports_ast_sha256`, and `ast_python`. The hash ignores formatting
2142
+ and comments, includes alias names and order, and is Python-minor-specific.
2143
+ Nested/conditional imports are not selected. Use the same interpreter for
2144
+ inspection and planning.
2145
+
2146
+ `ensure_import` and `remove_import` require `path`, `statement` and
2147
+ `expected_imports_ast_sha256`. The statement must be exactly one Python
2148
+ `import` or `from ... import ...` with one alias, optionally renamed. Relative
2149
+ imports are supported. Star requests and ambiguous duplicate matches fail.
2150
+ The guard is always checked, including for already-present/absent imports.
2151
+
2152
+ ```yaml
2153
+ - ensure_import:
2154
+ path: services/offers.py
2155
+ statement: from .pricing import calculate_price
2156
+ expected_imports_ast_sha256: "<64 hex digits from read_imports>"
2157
+ ```
2158
+
2159
+ An existing alias in a multi-alias import satisfies `ensure_import` without
2160
+ rewriting that line. Removal from a multi-alias import requires a separately
2161
+ reviewed rewrite. Removal retains comments, including comments inside a
2162
+ parenthesized import. Shared semicolon lines are rejected when editing them.
2163
+ Insertion preserves headers, the module docstring and future-import ordering.
2164
+ Existing bindings are checked conservatively across scopes; a conflicting
2165
+ local name may therefore require a separately reviewed edit too. The resulting
2166
+ module is compiled without importing or executing it.
2167
+
2168
+ Unrelated source remains intact before the normal scoped formatter phase.
2169
+ Neither action rewrites callers, proves runtime compatibility, nor infers
2170
+ imports when moving files or symbols. A sequence of import edits needs the
2171
+ hash of each simulated intermediate import set, or separate audited jobs.
@@ -0,0 +1,54 @@
1
+ # Read PatchShuttle results from ChatGPT
2
+
3
+ The optional MCP server exposes `history_list`, `history_latest` and
4
+ `history_show` for one explicitly selected workspace. It does not expose job
5
+ execution, database execution, raw log downloads or arbitrary filesystem reads.
6
+ Returned project text is untrusted data, not instructions.
7
+
8
+ Install `patchshuttle[mcp]`. Create an owner-controlled JSON configuration
9
+ outside job content, replacing every example value:
10
+
11
+ ```json
12
+ {
13
+ "issuer": "https://identity.example/",
14
+ "jwks_url": "https://identity.example/.well-known/jwks.json",
15
+ "resource_url": "https://history.example/mcp",
16
+ "subject": "your-exact-OAuth-subject",
17
+ "port": 8765
18
+ }
19
+ ```
20
+
21
+ Start with `patchshuttle-mcp --workspace /absolute/project --config /absolute/mcp.json`.
22
+ The process binds only `127.0.0.1`. Put it behind your HTTPS reverse proxy and
23
+ forward `/mcp` and `/.well-known/oauth-protected-resource/mcp` to port 8765.
24
+ Keep Authorization and Host headers intact. Do not expose an unauthenticated
25
+ alternate listener. The resource URL must end in the exact `/mcp` path.
26
+
27
+ Use an established OAuth authorization server with discovery, authorization-code
28
+ and PKCE support, and ChatGPT-compatible client registration (CIMD or DCR, or
29
+ configured static client credentials). Configure audience to the exact resource
30
+ URL and issue the `patchshuttle:history:read` scope to the configured subject.
31
+ The resource server accepts RS256/ES256 JWT access tokens only and validates
32
+ signature, expiry, issuer, audience, scope and the exact subject. It is not an
33
+ OAuth authorization server; opaque tokens/introspection are not implemented.
34
+ No access tokens or signing secrets belong in patch jobs or this configuration.
35
+
36
+ In ChatGPT's custom MCP connection settings, supply the HTTPS MCP URL and
37
+ configure OAuth. Install/select the resulting plugin and ask for the latest
38
+ PatchShuttle result. Availability and the actual linking flow depend on the
39
+ account/workspace. A live connection to the user's deployment is not established
40
+ by installing this Python package.
41
+
42
+ Local validation covers signed tokens, rejected claims/signatures, protected
43
+ resource discovery, HTTP authorization and history reads. It does not constitute
44
+ an end-to-end ChatGPT/OAuth-provider deployment test. The existing history
45
+ redaction remains best effort; authenticated access can reveal project details.
46
+
47
+ Official setup and authorization references:
48
+
49
+ - https://developers.openai.com/api/docs/guides/custom-mcp-server
50
+ - https://developers.openai.com/plugins/build/auth
51
+ - https://developers.openai.com/api/docs/guides/secure-mcp-tunnels
52
+
53
+ A Secure MCP Tunnel is another documented transport option, but this package's
54
+ initial configuration and local tests target an HTTPS reverse-proxy deployment.
@@ -0,0 +1,83 @@
1
+ # Controlled Django migrations
2
+
3
+ Install `patchshuttle[django]` in the project's interpreter and add
4
+ `"patchshuttle.django_app"` to the owner's `INSTALLED_APPS`. The adapter has no
5
+ models and adds no migration of its own. Initialize a PatchShuttle workspace
6
+ and run the management command from the Django project as usual.
7
+
8
+ Preview an exact target (prefix matching is deliberately unsupported):
9
+
10
+ ```bash
11
+ python manage.py patchshuttle_migrate orders 0007_add_status --workspace . --database default
12
+ ```
13
+
14
+ The JSON plan contains ordered operations, direction, atomicity, declared
15
+ reversibility, migration-file checksums, current applied migrations and a database
16
+ fingerprint. Credentials come from Django settings and are not written into the
17
+ plan. Planning loads trusted Django project/migration code and reads database
18
+ state; the adapter does not apply schema operations during preview. Application
19
+ startup hooks remain project code and may have their own effects.
20
+
21
+ Review the plan and prepare a tested database backup/recovery procedure. Apply
22
+ using the exact returned SHA-256 and a non-secret reference to that procedure:
23
+
24
+ ```bash
25
+ python manage.py patchshuttle_migrate orders 0007_add_status --workspace . --database default --apply --expected-plan <sha256> --recovery-reference <backup-or-recovery-record>
26
+ ```
27
+
28
+ The reference records the owner's recovery preparation; PatchShuttle does not
29
+ create or verify the backup. A workspace lock and database lock are held while
30
+ rechecking the plan, running Django checks, applying migrations and verifying
31
+ both the exact applied set and unchanged migration sources. System checks run
32
+ before and after migration. Add domain-specific checks to Django's check
33
+ framework; model checks alone do not validate business data.
34
+
35
+ PostgreSQL uses a session-level advisory lock shared by cooperating adapter
36
+ processes in that database. Persistent SQLite uses a companion file lock.
37
+ Ordinary `manage.py migrate`, other applications and manual SQL do not observe
38
+ these locks. Use one canonical workspace/journal for each database's migration
39
+ operations. PostgreSQL connection poolers must preserve the same server session
40
+ for the operation; transaction-pooling mode is not supported.
41
+
42
+ Initial limits: PostgreSQL and persistent SQLite, autocommit connections,
43
+ transactional DDL and atomic migrations. In-memory/URI SQLite, other backends,
44
+ non-atomic migrations, unresolved graph conflicts and squashed replacements
45
+ are rejected. Migration checksums cover migration source files, not imported
46
+ helper implementations or arbitrary application dependencies. Keep the deployed
47
+ code immutable during planning/execution.
48
+
49
+ ## Failure and recovery
50
+
51
+ Records are separate from filesystem patch backups, under
52
+ `patches/state/django-migrations`. An exclusive, flushed `*.start.json` precedes
53
+ migration execution; a separate `*.result.json` records success or failure.
54
+ An unfinished start record or `FAILED_REVIEW_REQUIRED` blocks another execution.
55
+ The adapter does not automatically retry an interrupted operation or reverse
56
+ already applied migrations. A multi-migration plan can partially succeed before
57
+ failure even if each individual migration is atomic. Python migration code may
58
+ also have external effects that database transactions cannot undo.
59
+
60
+ After independently inspecting/recovering the database, generate a fresh plan
61
+ and explicitly record owner reconciliation:
62
+
63
+ ```bash
64
+ python manage.py patchshuttle_migrate orders 0007_add_status --workspace . --database default --reconcile <attempt-id> --expected-plan <fresh-sha256> --recovery-reference <verified-recovery-record>
65
+ ```
66
+
67
+ This appends an acknowledgement. It does not repair, replay or prove the
68
+ correctness of database contents. Corrupt journals must be investigated;
69
+ do not delete them just to bypass an unresolved attempt.
70
+
71
+ ## Explicit database reversal
72
+
73
+ Choose the earlier target (or `zero`) and preview with `--reverse`. Apply that
74
+ reviewed reverse plan with the same `--reverse`, hash and recovery reference.
75
+ An irreversible operation is rejected. Django's reversible flag does not imply
76
+ that data lost by forward operations can be restored. Reversal can remove data
77
+ and requires separate review and backup planning.
78
+
79
+ Filesystem `rollback` never reverses the database. The MCP history adapter
80
+ currently retrieves filesystem-job history; this separate migration journal
81
+ is not yet exposed through MCP.
82
+
83
+ Django's migration behavior: https://docs.djangoproject.com/en/6.0/topics/migrations/