patchshuttle 0.1.0a2__py3-none-any.whl
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.
- patchshuttle/__init__.py +98 -0
- patchshuttle/_diff.py +317 -0
- patchshuttle/_process.py +198 -0
- patchshuttle/_version.py +3 -0
- patchshuttle/actions/__init__.py +80 -0
- patchshuttle/actions/constructors.py +211 -0
- patchshuttle/actions/create.py +155 -0
- patchshuttle/actions/modify.py +174 -0
- patchshuttle/audit.py +588 -0
- patchshuttle/backup.py +712 -0
- patchshuttle/checks/__init__.py +37 -0
- patchshuttle/checks/constructors.py +67 -0
- patchshuttle/checks/runner.py +233 -0
- patchshuttle/cli.py +766 -0
- patchshuttle/config.py +247 -0
- patchshuttle/context.py +370 -0
- patchshuttle/errors.py +291 -0
- patchshuttle/execution.py +651 -0
- patchshuttle/formatters/__init__.py +25 -0
- patchshuttle/formatters/runner.py +240 -0
- patchshuttle/identifiers.py +20 -0
- patchshuttle/inventory.py +331 -0
- patchshuttle/logging.py +741 -0
- patchshuttle/models.py +496 -0
- patchshuttle/operations.py +292 -0
- patchshuttle/parser.py +243 -0
- patchshuttle/planner.py +1144 -0
- patchshuttle/policy.py +377 -0
- patchshuttle/py.typed +1 -0
- patchshuttle/registry.py +275 -0
- patchshuttle/resources/AI_GUIDE.md +163 -0
- patchshuttle/resources/AUDIT-EXAMPLE.psh.yaml +10 -0
- patchshuttle/resources/PATCH-EXAMPLE.psh.yaml +17 -0
- patchshuttle/resources/PATCHSHUTTLE_PROTOCOL.md +109 -0
- patchshuttle/resources/__init__.py +1 -0
- patchshuttle/rollback.py +306 -0
- patchshuttle/runner.py +880 -0
- patchshuttle/verification.py +107 -0
- patchshuttle/workspace.py +382 -0
- patchshuttle-0.1.0a2.dist-info/METADATA +535 -0
- patchshuttle-0.1.0a2.dist-info/RECORD +44 -0
- patchshuttle-0.1.0a2.dist-info/WHEEL +4 -0
- patchshuttle-0.1.0a2.dist-info/entry_points.txt +2 -0
- patchshuttle-0.1.0a2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: patchshuttle
|
|
3
|
+
Version: 0.1.0a2
|
|
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: tomli<3,>=2; python_version < '3.11'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: django<7,>=5.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov<8,>=5; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest<10,>=8; extra == 'dev'
|
|
32
|
+
Requires-Dist: twine<7,>=6.1; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# PatchShuttle
|
|
36
|
+
|
|
37
|
+
PatchShuttle is intended to provide local, auditable patch workflows for
|
|
38
|
+
step-by-step software development with ChatGPT and other AI services. An AI
|
|
39
|
+
describes a small job, the user reviews and runs it locally, and PatchShuttle
|
|
40
|
+
records the result for the next iteration.
|
|
41
|
+
|
|
42
|
+
> [!IMPORTANT]
|
|
43
|
+
> PatchShuttle is alpha software. The current `0.1.0a2` release candidate
|
|
44
|
+
> implements the complete local v0.1 workflow. It executes bounded read-only audits,
|
|
45
|
+
> approved patch transactions, and approved one-pass verification jobs under a
|
|
46
|
+
> project lock. Patch jobs retain backups, run controlled checks, apply scoped
|
|
47
|
+
> isort then Black, repeat checks, and compare SHA-256 workspace inventories.
|
|
48
|
+
> Completed patches support guarded manual rollback. Timestamped fixed-section
|
|
49
|
+
> logs, exact job archives, registry idempotency, project snapshots, AI
|
|
50
|
+
> handoffs, declarative Python constructors, release checks, and Trusted
|
|
51
|
+
> Publishing workflows are implemented. Local qualification and the required
|
|
52
|
+
> GitHub-hosted Windows/Ubuntu matrix are complete. TestPyPI installation
|
|
53
|
+
> remains the next external release gate.
|
|
54
|
+
|
|
55
|
+
## Design goals
|
|
56
|
+
|
|
57
|
+
- Keep every change local and explicitly initiated by the user.
|
|
58
|
+
- Make each run reviewable, reproducible, and easy to return to an AI as a log.
|
|
59
|
+
- Centralize recurring safety checks, backups, tests, isort, and Black.
|
|
60
|
+
- Support both existing repositories and projects built from an empty folder.
|
|
61
|
+
- Use the same typed job model from YAML, the CLI, and the Python API.
|
|
62
|
+
|
|
63
|
+
PatchShuttle is not a security sandbox. Project tests and AI-generated project
|
|
64
|
+
code can execute arbitrary behavior with the current user's permissions.
|
|
65
|
+
|
|
66
|
+
## Install the development build
|
|
67
|
+
|
|
68
|
+
PatchShuttle has not been published to PyPI yet. From a local checkout:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python -m venv .venv
|
|
72
|
+
python -m pip install --upgrade pip
|
|
73
|
+
python -m pip install -e ".[dev]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Verify the installed CLI:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
patchshuttle version
|
|
80
|
+
patchshuttle --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Initialize a workspace
|
|
84
|
+
|
|
85
|
+
Run this inside an existing project:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
patchshuttle init
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For a new project, start in an empty directory:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
patchshuttle init --new-project
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`--new-project` also accepts a real `.git` directory and the recognized regular
|
|
98
|
+
metadata files `.DS_Store`, `Thumbs.db`, `desktop.ini`, and AppleDouble `._*`
|
|
99
|
+
files. Other entries are rejected. Managed paths that are symbolic links or
|
|
100
|
+
have an unexpected file type are also rejected.
|
|
101
|
+
|
|
102
|
+
Initialization creates a stable random project ID and the local `patches/`
|
|
103
|
+
workspace containing:
|
|
104
|
+
|
|
105
|
+
- `inbox/`, `applied/`, `failed/`, `logs/`, and `backups/`;
|
|
106
|
+
- `state/registry.json` and `state/run.lock`;
|
|
107
|
+
- `patchshuttle.toml` with local policy and limits;
|
|
108
|
+
- `AI_GUIDE.md`, `PATCHSHUTTLE_PROTOCOL.md`, and the JSON Schema;
|
|
109
|
+
- valid audit and patch examples in `examples/`.
|
|
110
|
+
|
|
111
|
+
Running `init` again preserves every existing file. If a generated entry is
|
|
112
|
+
missing, only that missing entry is created.
|
|
113
|
+
|
|
114
|
+
The same operation is available from Python:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from patchshuttle import init_workspace
|
|
118
|
+
|
|
119
|
+
result = init_workspace(".", new_project=False)
|
|
120
|
+
print(result.status.value, result.workspace.project_id)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Validate a job
|
|
124
|
+
|
|
125
|
+
Validate the generated audit example or an AI job placed in `patches/inbox/`:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
patchshuttle validate patches/examples/AUDIT-EXAMPLE.psh.yaml
|
|
129
|
+
patchshuttle validate patches/inbox/AUDIT-001.psh.yaml
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A valid job produces a compact summary and exits with code `0`:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
VALID
|
|
136
|
+
job_id: AUDIT-001
|
|
137
|
+
kind: audit
|
|
138
|
+
protocol: 1
|
|
139
|
+
project_id: PSH-8F41C2A73D905E61
|
|
140
|
+
actions: 1
|
|
141
|
+
checks: 0
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
An invalid job is reported to standard error with a stable error code, field
|
|
145
|
+
path, and line and column when available:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
INVALID [JOB_SCHEMA_INVALID] $.protocol: Input should be 1
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
YAML and schema errors exit with code `2`. Missing workspace, invalid local
|
|
152
|
+
configuration, and project ID mismatch errors exit with code `3`.
|
|
153
|
+
|
|
154
|
+
The current `validate` command reads the nearest workspace configuration,
|
|
155
|
+
applies its `max_job_bytes` limit, safely loads the YAML, validates the typed
|
|
156
|
+
model, and compares the job project ID. It does not plan or execute actions,
|
|
157
|
+
modify project files, or create logs.
|
|
158
|
+
|
|
159
|
+
The public constructors validate declarative Python data without reading or
|
|
160
|
+
modifying project files:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from patchshuttle import Job
|
|
164
|
+
from patchshuttle.actions import create_file
|
|
165
|
+
from patchshuttle.checks import compileall
|
|
166
|
+
|
|
167
|
+
job = Job(
|
|
168
|
+
protocol=1,
|
|
169
|
+
project_id="PSH-8F41C2A73D905E61",
|
|
170
|
+
id="PATCH-001",
|
|
171
|
+
kind="patch",
|
|
172
|
+
actions=[
|
|
173
|
+
create_file(path="src/example.py", content="VALUE = 1\n")
|
|
174
|
+
],
|
|
175
|
+
checks=[compileall(paths=["src"])],
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
schema = Job.model_json_schema()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The models reject unknown fields, enforce protocol and identifier formats, and
|
|
182
|
+
keep validated data immutable. They do not execute the requested operations.
|
|
183
|
+
|
|
184
|
+
Load a job from disk with the same validation contract:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from patchshuttle import JobError, load_job
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
job = load_job("patches/inbox/PATCH-001.psh.yaml")
|
|
191
|
+
except JobError as error:
|
|
192
|
+
print(error.code.value, error.field_path, error.line, error.column)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The loader accepts the exact `.psh.yaml` extension and UTF-8 input, checks its
|
|
196
|
+
`max_bytes` limit before parsing, uses PyYAML's safe loader, and rejects
|
|
197
|
+
duplicate mapping keys, custom tags, anchors, aliases, unknown fields, and
|
|
198
|
+
invalid typed-model data. Errors expose a stable code and a YAML field path;
|
|
199
|
+
lexical errors also include a line and column when available.
|
|
200
|
+
|
|
201
|
+
## Plan a job
|
|
202
|
+
|
|
203
|
+
Plan a validated audit, patch, or verify job without changing the workspace:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
patchshuttle plan patches/examples/PATCH-EXAMPLE.psh.yaml
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The command displays the normalized job hash, sequential action dispositions,
|
|
210
|
+
files and directories that would be created or modified, requested checks,
|
|
211
|
+
Python formatting scope, protected-path result, backup destination template,
|
|
212
|
+
rollback policy, and whether later execution will require confirmation.
|
|
213
|
+
|
|
214
|
+
Planning performs the complete implemented read-only preflight:
|
|
215
|
+
|
|
216
|
+
- applies local action-count, check, size, ignored-path, and protected-path
|
|
217
|
+
policy;
|
|
218
|
+
- simulates sequential changes in memory, including changes to a file created
|
|
219
|
+
earlier in the same job;
|
|
220
|
+
- checks exact occurrence counts and idempotent `NO_CHANGE` states;
|
|
221
|
+
- parses and dry-runs text-only unified diffs without a shell or external
|
|
222
|
+
`patch` command;
|
|
223
|
+
- rejects binary content, unsupported target encodings, mixed newline styles,
|
|
224
|
+
symbolic links, special files, and file-size violations;
|
|
225
|
+
- validates check paths, the conservative pytest argument allowlist, dotted
|
|
226
|
+
Django labels, local profiles, and required Python modules;
|
|
227
|
+
- records target fingerprints and computes final bytes and SHA-256 hashes for
|
|
228
|
+
the internal transactional runner.
|
|
229
|
+
|
|
230
|
+
Policy blocks exit with code `4`, planning failures with code `5`, and missing
|
|
231
|
+
check profiles or dependencies with code `9`. A successful plan exits with
|
|
232
|
+
code `0`.
|
|
233
|
+
|
|
234
|
+
The same planner is available from Python:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from patchshuttle import discover_workspace, load_job, plan_job
|
|
238
|
+
|
|
239
|
+
workspace = discover_workspace(".")
|
|
240
|
+
job = load_job(
|
|
241
|
+
"patches/inbox/PATCH-001.psh.yaml",
|
|
242
|
+
max_bytes=workspace.config.execution.max_job_bytes,
|
|
243
|
+
)
|
|
244
|
+
plan = plan_job(job, workspace)
|
|
245
|
+
|
|
246
|
+
print(plan.job_hash)
|
|
247
|
+
print(plan.files_to_create)
|
|
248
|
+
print(plan.files_to_modify)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`Plan`, its actions, checks, fingerprints, and final file changes are immutable.
|
|
252
|
+
Planning does not create target directories or files, execute audits or checks,
|
|
253
|
+
run formatters, create backups or logs, or alter registry state. A successful
|
|
254
|
+
plan therefore does not mean that the job was applied.
|
|
255
|
+
|
|
256
|
+
## Execute jobs
|
|
257
|
+
|
|
258
|
+
`run` is the universal executor for `audit`, `patch`, and `verify` jobs:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
patchshuttle run patches/inbox/PATCH-001.psh.yaml
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Audit jobs are read-only and do not prompt. Patch and verify jobs print the
|
|
265
|
+
complete plan and local-code warning before asking `Apply this job? [y/N]`.
|
|
266
|
+
Explicit automation can use:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
patchshuttle run patches/inbox/PATCH-001.psh.yaml --yes
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
For a patch only, the user may deliberately retain partial declared changes
|
|
273
|
+
after a failure:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
patchshuttle run patches/inbox/PATCH-001.psh.yaml --keep-changes
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This mode requires a second deny-by-default confirmation unless combined with
|
|
280
|
+
`--yes`. It is rejected when local policy sets `allow_keep_changes = false`.
|
|
281
|
+
The log and registry distinguish `SKIPPED_CHANGES_KEPT` from a skipped rollback
|
|
282
|
+
where no declared change was published. Tests and profiles can still create
|
|
283
|
+
external side effects outside PatchShuttle's transaction.
|
|
284
|
+
|
|
285
|
+
Kind-specific CLI entry points are also available:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
patchshuttle audit patches/inbox/AUDIT-001.psh.yaml
|
|
289
|
+
patchshuttle verify patches/inbox/VERIFY-001.psh.yaml
|
|
290
|
+
patchshuttle verify patches/inbox/VERIFY-001.psh.yaml --yes
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
From Python, approval is required for patch and verify plans but not audits:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
from patchshuttle import execute_plan
|
|
297
|
+
|
|
298
|
+
result = execute_plan(plan, approved=True)
|
|
299
|
+
print(result.status.value, result.backup_path, result.log_path)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`RunResult` is immutable and reports created and modified paths, backup and log
|
|
303
|
+
locations, the archived job copy, initial check results, formatter results,
|
|
304
|
+
retained formatted-file states, final check results, audit observations, and a
|
|
305
|
+
workspace comparison where applicable.
|
|
306
|
+
|
|
307
|
+
An audit executes `tree`, `read`, literal `search`, `find_files`, `file_info`,
|
|
308
|
+
SHA-256 `hash`, `git_status`, and `environment`. Traversal, file reads, match
|
|
309
|
+
counts, and recorded output are bounded by local policy. Protected and ignored
|
|
310
|
+
paths are skipped, source content appears only when explicitly requested by a
|
|
311
|
+
`read` or `search` action, and a before/after inventory verifies that the audit
|
|
312
|
+
did not modify the workspace.
|
|
313
|
+
|
|
314
|
+
A verify job runs its controlled checks once, does not create a backup or run
|
|
315
|
+
formatters, and compares the workspace before and after. A successful check
|
|
316
|
+
that changes a non-ignored path returns `UNEXPECTED_WORKSPACE_CHANGE`; because
|
|
317
|
+
project checks can have external effects, PatchShuttle reports but does not
|
|
318
|
+
automatically undo those changes.
|
|
319
|
+
|
|
320
|
+
A patch plan may contain `create_directory`, `create_file`, `replace_exact`,
|
|
321
|
+
`insert_before`, `insert_after`, `delete_exact`, or `apply_diff` actions and
|
|
322
|
+
applies the final bytes already computed by the planner.
|
|
323
|
+
|
|
324
|
+
For a supported plan, the implemented sequence is:
|
|
325
|
+
|
|
326
|
+
1. validate the workspace lock file and acquire a non-blocking cross-platform
|
|
327
|
+
lock;
|
|
328
|
+
2. run the complete planner again under the lock and compare the resulting
|
|
329
|
+
immutable plan with the approved plan;
|
|
330
|
+
3. capture a deterministic inventory of non-ignored workspace entries before
|
|
331
|
+
the first project write, hashing regular files within the configured entry
|
|
332
|
+
and total-byte limits;
|
|
333
|
+
4. copy every existing target into
|
|
334
|
+
`patches/backups/<JOB_ID>/<RUN_TIMESTAMP>/originals/` before the first
|
|
335
|
+
project write and create a manifest containing `PRESENT` or `ABSENT`
|
|
336
|
+
entries, SHA-256 hashes, sizes, modes, encoding, and newline metadata;
|
|
337
|
+
5. create planned directories and files without accepting an existing-path
|
|
338
|
+
race;
|
|
339
|
+
6. stage modified-file bytes beside the target, flush them, recheck the
|
|
340
|
+
approved original fingerprint, atomically replace the target, preserve its
|
|
341
|
+
mode, and verify exact final bytes and hash;
|
|
342
|
+
7. build fixed argument arrays for every requested check and run them in order
|
|
343
|
+
from the workspace root with `shell=False`, per-check timeout, separate
|
|
344
|
+
stdout and stderr capture, and local output truncation limits;
|
|
345
|
+
8. stop on the first failed, timed-out, or unstartable initial check and verify
|
|
346
|
+
that the checks did not change any declared transaction file;
|
|
347
|
+
9. capture the approved changed-Python scope, then run isort followed by Black
|
|
348
|
+
only on those exact relative paths using the current interpreter in isolated
|
|
349
|
+
mode, fixed argument arrays, `shell=False`, and the same timeout and bounded
|
|
350
|
+
output controls;
|
|
351
|
+
10. stop on formatter failure, reject changes to declared non-Python files,
|
|
352
|
+
reject oversized or non-regular formatter targets, and retain the formatted
|
|
353
|
+
bytes, SHA-256 hashes, sizes, and modes;
|
|
354
|
+
11. when configured, repeat the same checks and require every formatted and
|
|
355
|
+
non-formatted transaction file to retain its exact approved post-state;
|
|
356
|
+
12. capture the final bounded inventory and classify added, removed, modified,
|
|
357
|
+
and type-changed paths as declared or unexpected;
|
|
358
|
+
13. mark the manifest `COMPLETED`, or restore modified originals and remove
|
|
359
|
+
only paths created by this attempt before recording `ROLLED_BACK` or
|
|
360
|
+
`ROLLBACK_FAILED`; an explicitly accepted `--keep-changes` run instead
|
|
361
|
+
records `CHANGES_KEPT` when a failed job published declared changes;
|
|
362
|
+
14. archive the exact CLI source job, write a fixed-section UTF-8 log with a
|
|
363
|
+
compact AI handoff block, and atomically commit registry state before
|
|
364
|
+
releasing the workspace lock.
|
|
365
|
+
|
|
366
|
+
A no-change plan is revalidated under the lock and returns without creating a
|
|
367
|
+
backup or launching checks. A race detected before replacement is not
|
|
368
|
+
overwritten. Rollback validates retained originals before restoring them,
|
|
369
|
+
refuses to follow symbolic links or remove foreign non-empty directories, and
|
|
370
|
+
does not claim success when a tracked path cannot be restored.
|
|
371
|
+
|
|
372
|
+
The internal check runner supports `compileall`, `pytest`, `unittest`, Django
|
|
373
|
+
checks and tests, validated module imports, and locally configured profiles. It
|
|
374
|
+
inherits the current process environment because project checks execute project
|
|
375
|
+
code and PatchShuttle is not an operating-system sandbox. Formatter order is
|
|
376
|
+
fixed to isort then Black for protocol 1; non-Python jobs skip formatting and
|
|
377
|
+
do not repeat checks. Phase 15 revalidates declared transaction files after
|
|
378
|
+
each executable stage and records unrelated final workspace changes. Default
|
|
379
|
+
ignored paths include VCS metadata, virtual environments, dependency trees,
|
|
380
|
+
PatchShuttle runtime state, and common Python caches.
|
|
381
|
+
|
|
382
|
+
The command maps approval, workspace locking, job-ID conflicts, actions,
|
|
383
|
+
checks, formatting, and rollback failures to the documented process exit-code
|
|
384
|
+
groups. A successful
|
|
385
|
+
check that leaves an undeclared path change returns
|
|
386
|
+
`UNEXPECTED_WORKSPACE_CHANGE`; PatchShuttle rolls back its declared files but
|
|
387
|
+
does not delete or restore that external side effect. `RunResult` and execution
|
|
388
|
+
errors expose the final `WorkspaceComparison`, log path, and archived job path.
|
|
389
|
+
|
|
390
|
+
## Manual rollback
|
|
391
|
+
|
|
392
|
+
Roll back a completed patch interactively or with explicit automation:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
patchshuttle rollback PATCH-001
|
|
396
|
+
patchshuttle rollback PATCH-001 --yes
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The Python equivalent is `rollback_job(workspace, "PATCH-001",
|
|
400
|
+
approved=True)`. Manual rollback reloads the retained manifest and original
|
|
401
|
+
copies, validates their identity and integrity, and compares every tracked
|
|
402
|
+
path with the exact completed-job state. It refuses to overwrite a later user
|
|
403
|
+
edit or remove a created directory containing undeclared entries. On success,
|
|
404
|
+
it restores original files, removes only paths created by that job, marks the
|
|
405
|
+
manifest `ROLLED_BACK`, records a fixed-section log, updates the registry, and
|
|
406
|
+
allows the same job ID and hash to be applied again. A failure preserves the
|
|
407
|
+
backup and reports unresolved paths without claiming restoration succeeded.
|
|
408
|
+
|
|
409
|
+
## Recorded runs and AI handoff
|
|
410
|
+
|
|
411
|
+
For CLI execution, PatchShuttle preserves the input `.psh.yaml` byte-for-byte
|
|
412
|
+
in `patches/applied/` or `patches/failed/`. A Python API call without
|
|
413
|
+
`source_path=` archives a deterministic YAML rendering of the immutable job
|
|
414
|
+
model. Archive and log filenames include the configured-timezone timestamp,
|
|
415
|
+
job ID, and, for archives, a short normalized hash. Numeric suffixes prevent
|
|
416
|
+
same-second collisions.
|
|
417
|
+
|
|
418
|
+
Every job run log contains all standard sections in a fixed order, using
|
|
419
|
+
`NOT_APPLICABLE` where a stage did not run, and ends with
|
|
420
|
+
`PATCHSHUTTLE_AI_HANDOFF`. Check and formatter output is bounded by local
|
|
421
|
+
policy. Common password, token, API-key, authorization-header, and private-key
|
|
422
|
+
shapes are masked when redaction is enabled. Redaction is best-effort and is
|
|
423
|
+
not a guarantee that a log contains no secrets; review a log before uploading
|
|
424
|
+
it to any AI service.
|
|
425
|
+
|
|
426
|
+
Find the latest upload-friendly log:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
patchshuttle logs --last
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Inspect all registered jobs or one job ID:
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
patchshuttle status
|
|
436
|
+
patchshuttle status PATCH-001
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The registry enforces stable identity. A completed ID with the same normalized
|
|
440
|
+
hash returns `ALREADY_APPLIED` without rerunning actions, checks, or formatters.
|
|
441
|
+
The same ID with different normalized content returns `PATCH_ID_CONFLICT`
|
|
442
|
+
before mutable planning. Failed, rolled-back, and declined jobs may be retried
|
|
443
|
+
with the same ID and hash. Registry writes and the definitive identity check
|
|
444
|
+
share the same workspace lock as the project transaction.
|
|
445
|
+
|
|
446
|
+
Create a metadata-only project snapshot or a compact AI handoff:
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
patchshuttle snapshot
|
|
450
|
+
patchshuttle handoff
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Both commands write timestamped `.log` files under `patches/logs/`. A snapshot
|
|
454
|
+
contains versions, a bounded tree, sizes and SHA-256 hashes, Git status when
|
|
455
|
+
available, recent jobs, capabilities, and policy summaries. It does not dump
|
|
456
|
+
source-file contents. A handoff adds a provider-neutral AI instruction, latest
|
|
457
|
+
run summary and `PATCHSHUTTLE_AI_HANDOFF` block, bounded tree, recent history,
|
|
458
|
+
and the explicit requirement to return one `.psh.yaml` file.
|
|
459
|
+
|
|
460
|
+
## Inspect local path policy
|
|
461
|
+
|
|
462
|
+
The planner uses the same public read-only policy API. It normalizes an
|
|
463
|
+
AI-supplied path, applies the local protected-path rules, inspects every
|
|
464
|
+
existing component without following symbolic links, and confirms that the
|
|
465
|
+
resolved result remains in the workspace:
|
|
466
|
+
|
|
467
|
+
```python
|
|
468
|
+
from patchshuttle import Policy, PolicyError, discover_workspace
|
|
469
|
+
|
|
470
|
+
policy = Policy(discover_workspace("."))
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
target = policy.resolve("src/example.py", allow_missing=True)
|
|
474
|
+
except PolicyError as error:
|
|
475
|
+
print(error.code.value, error.path)
|
|
476
|
+
else:
|
|
477
|
+
print(target.relative, target.kind.value)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Absolute and drive-qualified paths, URLs, parent traversal, protected paths,
|
|
481
|
+
symbolic-link targets or parents, sockets, devices, and named pipes are
|
|
482
|
+
rejected with stable error codes. Both `/` and `\` are treated as separators
|
|
483
|
+
so a job cannot use platform-specific spelling to bypass a rule.
|
|
484
|
+
|
|
485
|
+
Protected and ignored glob patterns come only from
|
|
486
|
+
`patches/patchshuttle.toml`. `**` matches zero or more complete path segments,
|
|
487
|
+
protected-path exceptions override configured protected globs, and matching
|
|
488
|
+
uses platform-appropriate case behavior. The workspace root requires explicit
|
|
489
|
+
`allow_root=True` access and `patches/` remains a hard block for job targets.
|
|
490
|
+
`Policy.is_ignored()` is used by bounded inventory, audit traversal, snapshot,
|
|
491
|
+
and handoff generation.
|
|
492
|
+
|
|
493
|
+
Calling `Policy` directly performs no action planning or file writes.
|
|
494
|
+
|
|
495
|
+
Run the scaffold checks:
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
python -m isort --check-only src tests tools
|
|
499
|
+
python -m black --check src tests tools
|
|
500
|
+
python -m coverage erase
|
|
501
|
+
python -m coverage run -m pytest -q
|
|
502
|
+
python -m coverage report --fail-under=100
|
|
503
|
+
python -m build
|
|
504
|
+
python -m twine check dist/*
|
|
505
|
+
python tools/release_checks.py dist
|
|
506
|
+
python tools/wheel_smoke.py dist/patchshuttle-0.1.0a2-py3-none-any.whl --version 0.1.0a2
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
The release candidate includes GitHub Actions for the required Ubuntu and
|
|
510
|
+
Windows compatibility matrix, TestPyPI qualification, and PyPI Trusted
|
|
511
|
+
Publishing. The required GitHub-hosted matrix first passed on 2026-08-16.
|
|
512
|
+
Follow [docs/RELEASE.md](docs/RELEASE.md) in order, rerun CI after every
|
|
513
|
+
release-candidate change, and do not treat CI as proof that either package
|
|
514
|
+
index passed.
|
|
515
|
+
|
|
516
|
+
## Manual workflow
|
|
517
|
+
|
|
518
|
+
The implemented local cycle is:
|
|
519
|
+
|
|
520
|
+
1. Give an AI the latest PatchShuttle handoff or log.
|
|
521
|
+
2. Receive one declarative `.psh.yaml` job.
|
|
522
|
+
3. Review the local execution plan.
|
|
523
|
+
4. Execute an audit, approved patch, or approved verification job.
|
|
524
|
+
5. For a patch, run controlled checks, isort, Black, and final checks.
|
|
525
|
+
6. Receive a timestamped log or generate a fresh handoff.
|
|
526
|
+
7. Review the log, upload it to the AI, and continue with the next job.
|
|
527
|
+
|
|
528
|
+
See [SPEC_V0_1.md](SPEC_V0_1.md) for the approved product contract and
|
|
529
|
+
[CHANGELOG.md](CHANGELOG.md) for release notes. PatchShuttle is designed for
|
|
530
|
+
ChatGPT and other AI services, but the phrase `Tested with ChatGPT` is reserved
|
|
531
|
+
for a documented end-to-end run using a published protocol build.
|
|
532
|
+
|
|
533
|
+
## License
|
|
534
|
+
|
|
535
|
+
PatchShuttle is licensed under the MIT License.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
patchshuttle/__init__.py,sha256=2EyCJMNw9wV4s4Aeh72kAgqZzgqysJt63vyVU-4zWbQ,2282
|
|
2
|
+
patchshuttle/_diff.py,sha256=TMl6ynvGnpDxN2Us8HAMeWFUzXJrGLy848NA76_8-6o,9689
|
|
3
|
+
patchshuttle/_process.py,sha256=t_tu_2LGj0fFgzuZuU7jTz0CYmwaHL3gqnZHi-A9Z1k,5329
|
|
4
|
+
patchshuttle/_version.py,sha256=bt05g23ozEUqv4EUIgwJCjYq9ohAmtBDVFqOBq7t29w,92
|
|
5
|
+
patchshuttle/audit.py,sha256=8fGmLoT65dku7HTUDijY_n7ztIiO6uhFTG_tF7lrShY,19919
|
|
6
|
+
patchshuttle/backup.py,sha256=cNlTAel7f_2pqaLJyZpnRx97bVUB2uLNcsPqWFi30jI,23489
|
|
7
|
+
patchshuttle/cli.py,sha256=FE1c7VA6X2-d1doiWSf556Q_2bo74JFYlkZG_HINUh4,24780
|
|
8
|
+
patchshuttle/config.py,sha256=FBxcV8NWI2Nj6ex0SoVBsmQHJGlMPm1sV7_8IBTbkik,7863
|
|
9
|
+
patchshuttle/context.py,sha256=jmgZ_m9mEt9u8bSIPc48nLAzYHIg2yhlx7yysV8JQqE,11940
|
|
10
|
+
patchshuttle/errors.py,sha256=uz6voPTtr9XCtjyoOwAjCn17UjfhnHSQU6ih5r1JF3o,9793
|
|
11
|
+
patchshuttle/execution.py,sha256=0DPV-o2EVl3hysAdsG4RTalxdTw2hnyfpbfs0C5pJcA,20063
|
|
12
|
+
patchshuttle/identifiers.py,sha256=1LVK1yO4icJF1sPCWZJpzQqGbzxpzGmHTinKCtsjTsQ,556
|
|
13
|
+
patchshuttle/inventory.py,sha256=vrz6LuTpVC9mBrJse-_HHtnH9FqFbUewI90eTEeGaE8,10134
|
|
14
|
+
patchshuttle/logging.py,sha256=C9c3VsHG8Z-iBoT9YNjA6dcHUoS5HAWUdg4Z0tDyL-c,24561
|
|
15
|
+
patchshuttle/models.py,sha256=Z3KYgV_TUSiiYAORpmva9WSv5tCrARPVhN72orxS39M,12605
|
|
16
|
+
patchshuttle/operations.py,sha256=X3KM5bUoecKYgswoGBKEyUUPiw5hbmSPY7i2Ldf5GWc,9331
|
|
17
|
+
patchshuttle/parser.py,sha256=hyIrptTxKjdcWINcTBGXeLEiC6_li4H8_CQ3WwFt-N4,7609
|
|
18
|
+
patchshuttle/planner.py,sha256=eKvbtnxd8VKZ8HAtybWJV999WheZ2WFLenRhXfe8NQo,39958
|
|
19
|
+
patchshuttle/policy.py,sha256=LS1Gem6PWVj1RjVxxGXKCuy8AfEuSLzMt5v4aJZGek0,12332
|
|
20
|
+
patchshuttle/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
21
|
+
patchshuttle/registry.py,sha256=Dhc8ulv_nVOK0sbMYddaMxT41XWV6sLUeZZYUaFzv9o,9018
|
|
22
|
+
patchshuttle/rollback.py,sha256=3sk9BR_Rda4jhZi_hp1WttxO6KYI8QJaI2Ylg30VTg4,10152
|
|
23
|
+
patchshuttle/runner.py,sha256=PCqxGGMP074kv_LU7akT0U6FO9D_ucREoMGQwHGpDCU,31373
|
|
24
|
+
patchshuttle/verification.py,sha256=dep5d8HkoJTkQEZHrHlc0cjhc8WatdWfb5L1QKIaTS8,3703
|
|
25
|
+
patchshuttle/workspace.py,sha256=hklwMrycDgJMUxOmtSglWGqO2LrLQWChDeGTyt5viZU,12394
|
|
26
|
+
patchshuttle/actions/__init__.py,sha256=Q2mvc2dQTxoTQul3BmHqlZdflRmxrvEnxu8XK--0D9E,1880
|
|
27
|
+
patchshuttle/actions/constructors.py,sha256=QXoak9jFBBbbyZiXcrlpeUZlzkmyDOrDXtvfPtRub88,4148
|
|
28
|
+
patchshuttle/actions/create.py,sha256=QBmZ0fIEiiudCl6Zdw_2i6lmZudwJ7C6r0O7SP2Sn30,4858
|
|
29
|
+
patchshuttle/actions/modify.py,sha256=mIUmHL1-cJqQFGiSGwxWnuZwT96w8-NxVxLae7JidZ0,5097
|
|
30
|
+
patchshuttle/checks/__init__.py,sha256=rmO9T5Q93NwyHOT2VyWU7lIOniD2ry6_T97YrriTBP4,699
|
|
31
|
+
patchshuttle/checks/constructors.py,sha256=qiLBD_XZur6Yxm49sZbdWHglh_bZGn7EB_wSbpl-Krw,1624
|
|
32
|
+
patchshuttle/checks/runner.py,sha256=ssrZUeAixnxI1g5B9c9QxIdvQrgL6yQ_6tZtO01Dkl0,6546
|
|
33
|
+
patchshuttle/formatters/__init__.py,sha256=Pg0aqlDPSIJZIFEVAATqViH9BBXfuCrzGbkRwoUMVHs,565
|
|
34
|
+
patchshuttle/formatters/runner.py,sha256=DbhhqjufsK-uXIAa6bZ9rLxJCCKDBgcVlpWi3YsQLm4,7443
|
|
35
|
+
patchshuttle/resources/AI_GUIDE.md,sha256=d3LQFqWWdyZ847kziBnGwjQkRS4RpLfEAZww_DIsL7k,5653
|
|
36
|
+
patchshuttle/resources/AUDIT-EXAMPLE.psh.yaml,sha256=pl32mVbSdDWI1ZybGEY-BH6xV-8VZ6Z7_iS4SAOGhsA,182
|
|
37
|
+
patchshuttle/resources/PATCH-EXAMPLE.psh.yaml,sha256=Q3tJbqlVcOuNX9S6R6i200F1mhyiSDH_8KUoct5-010,359
|
|
38
|
+
patchshuttle/resources/PATCHSHUTTLE_PROTOCOL.md,sha256=_B-pjo1KiVtCldR7MYv0mM3guikFw4pe_xvj5Hxh954,5220
|
|
39
|
+
patchshuttle/resources/__init__.py,sha256=ns3nT9-qUFk3ZO6CCU3yadbxd-owJFkjeiFBHtP3nX0,74
|
|
40
|
+
patchshuttle-0.1.0a2.dist-info/METADATA,sha256=zsOKBXIKkf2FypAFrEyNjaF_nqcA7Dblcpip0ZHyg_I,21566
|
|
41
|
+
patchshuttle-0.1.0a2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
42
|
+
patchshuttle-0.1.0a2.dist-info/entry_points.txt,sha256=EfXT4n6K9_vxZ-dLR6DCLCXHdTMrAGPU5ygI2w0-V-k,55
|
|
43
|
+
patchshuttle-0.1.0a2.dist-info/licenses/LICENSE,sha256=T0Ii8IhUiNiPInYs8G7CD6uh0AAiiL7JgH-pgNew7zE,1082
|
|
44
|
+
patchshuttle-0.1.0a2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PatchShuttle contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|