Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

PatchShuttle

PatchShuttle is intended to provide local, auditable patch workflows for step-by-step software development with ChatGPT and other AI services. An AI describes a small job, the user reviews and runs it locally, and PatchShuttle records the result for the next iteration.

Design goals

  • Keep every change local and explicitly initiated by the user.
  • Make each run reviewable, reproducible, and easy to return to an AI as a log.
  • Centralize recurring safety checks, backups, tests, isort, and Black.
  • Support both existing repositories and projects built from an empty folder.
  • Use the same typed job model from YAML, the CLI, and the Python API.

PatchShuttle is not a security sandbox. Project tests and AI-generated project code can execute arbitrary behavior with the current user's permissions.

Install

After publication, install the exact alpha release from PyPI:

python -m venv .venv
python -m pip install --upgrade pip
python -m pip install "patchshuttle==0.1.0a6"

For development from a local checkout:

python -m pip install -e ".[dev]"

To enable opt-in HTML template linting in an installed release, install the html extra:

python -m pip install "patchshuttle[html]==0.1.0a6"

For a local development checkout, install both development and HTML extras:

python -m pip install -e ".[dev,html]"

The extra installs djLint. HTML linting still remains disabled until the local workspace configuration explicitly enables it.

Formatter exclusions are explicit user-owned local policy. New workspaces contain empty lists in [formatting]; an older workspace may add the keys to its existing section:

[formatting]
enabled = true
order = ["isort", "black"]
scope = "changed_python_files"
rerun_checks = true
isort_exclude = []
black_exclude = ["email_client/views.py"]

Each entry must be one exact normalized workspace-relative .py path. An excluded file still receives the requested project checks. The exclusion only skips the named formatter, is shown in the plan and log, and cannot be supplied or changed by an AI job. Strict formatting remains the default.

For a newly initialized workspace, edit the generated block in patches/patchshuttle.toml. For an older workspace that does not yet contain the block, append it:

[linting.html]
enabled = true
tool = "djlint"
profile = "django"
scope = "changed_html_files"
ignore = []

Choose the profile that matches the project: html, django, jinja, nunjucks, handlebars, liquid, golang, angular, tera, or askama. The ignore list accepts explicit djLint rule codes such as H006. These values are user-owned local policy and cannot be supplied by a job.

Verify the installed CLI:

patchshuttle version
patchshuttle --help

In version 0.1.0a3, an AI or user can inspect the installed contract without initializing a workspace or reading protected generated files:

patchshuttle capabilities
patchshuttle schema
patchshuttle explain replace_exact
patchshuttle explain replace_symbol
patchshuttle explain replace_range
patchshuttle explain hash_range
patchshuttle explain apply_diff

capabilities prints the finite protocol surface and safety boundaries, schema prints the exact deterministic JSON Schema produced by the installed job model, and explain describes supported high-friction actions and workflow topics. Run patchshuttle explain --help for the finite topic list.

Initialize a workspace

Run this inside an existing project:

patchshuttle init

For a new project, start in an empty directory:

patchshuttle init --new-project

Every workspace-aware command can also receive an exact root before the command name:

patchshuttle --workspace path/to/project init
patchshuttle --workspace path/to/project handoff

Without this option, PatchShuttle searches the current directory and its parents. If no workspace is found, the CLI performs a bounded scan of direct child directories and may print candidate names and a rerun hint. It does not select or execute against a candidate automatically. JOB_FILE arguments remain relative to the process current directory; the workspace option changes workspace selection, not normal path-argument rules.

--new-project also accepts a real .git directory and the recognized regular metadata files .DS_Store, Thumbs.db, desktop.ini, and AppleDouble ._* files. Other entries are rejected. Managed paths that are symbolic links or have an unexpected file type are also rejected.

Initialization creates a stable random project ID and the local patches/ workspace containing:

  • inbox/, applied/, failed/, logs/, and backups/;
  • state/registry.json and state/run.lock;
  • patchshuttle.toml with local policy and limits;
  • AI_GUIDE.md, PATCHSHUTTLE_PROTOCOL.md, and the JSON Schema;
  • valid audit and patch examples in examples/.

Running init again preserves every existing file. If a generated entry is missing, only that missing entry is created.

The same operation is available from Python:

from patchshuttle import init_workspace

result = init_workspace(".", new_project=False)
print(result.status.value, result.workspace.project_id)

Validate a job

Validate the generated audit example or an AI job placed in patches/inbox/:

patchshuttle validate patches/examples/AUDIT-EXAMPLE.psh.yaml
patchshuttle validate patches/inbox/AUDIT-001.psh.yaml

A valid job produces a compact summary and exits with code 0:

VALID
job_id: AUDIT-001
kind: audit
protocol: 1
project_id: PSH-8F41C2A73D905E61
actions: 1
checks: 0

An invalid job is reported to standard error with a stable error code, field path, and line and column when available:

INVALID [JOB_SCHEMA_INVALID] $.protocol: Input should be 1

YAML and schema errors exit with code 2. Missing workspace, invalid local configuration, and project ID mismatch errors exit with code 3.

The current validate command reads the selected workspace configuration, applies its max_job_bytes limit, safely loads the YAML, validates the typed model, and compares the job project ID. It does not plan or execute actions or modify project source files. A successful validation does not create an operational artifact. In version 0.1.0a3, a validation failure after the workspace is resolved writes a timestamped VALIDATION_FAILED log containing the stable error, summary, and AI handoff. It does not archive invalid source as a job or update the registry. A workspace-discovery failure has no resolved workspace in which to write a log.

The public constructors validate declarative Python data without reading or modifying project files:

from patchshuttle import Job
from patchshuttle.actions import create_file
from patchshuttle.checks import compileall

job = Job(
    protocol=1,
    project_id="PSH-8F41C2A73D905E61",
    id="PATCH-001",
    kind="patch",
    actions=[
        create_file(path="src/example.py", content="VALUE = 1\n")
    ],
    checks=[compileall(paths=["src"])],
)

schema = Job.model_json_schema()

The models reject unknown fields, enforce protocol and identifier formats, and keep validated data immutable. They do not execute the requested operations.

For modules that require Django settings or app-registry initialization, use the controlled Django-aware check instead of the plain import_check:

checks:
  - django_import_check:
      manage_py: manage.py
      modules: [email_client.views, email_client.urls]

PatchShuttle runs the owner-selected project interpreter with manage.py shell -c and internally generated import code. The optional local [execution] python_executable value in patches/patchshuttle.toml may be an absolute path or a path relative to the workspace root. When it is omitted, PatchShuttle preserves the current sys.executable behavior. Jobs cannot set or override the interpreter, and PatchShuttle does not auto-detect a virtual environment. The YAML accepts only bounded dotted module identifiers, not an arbitrary expression or shell command.

Load a job from disk with the same validation contract:

from patchshuttle import JobError, load_job

try:
    job = load_job("patches/inbox/PATCH-001.psh.yaml")
except JobError as error:
    print(error.code.value, error.field_path, error.line, error.column)

The loader accepts the exact .psh.yaml extension and UTF-8 input, checks its max_bytes limit before parsing, uses PyYAML's safe loader, and rejects duplicate mapping keys, custom tags, anchors, aliases, unknown fields, and invalid typed-model data. Errors expose a stable code and a YAML field path; lexical errors also include a line and column when available.

Plan a job

Plan a validated audit, patch, or verify job without changing the workspace:

patchshuttle plan patches/examples/PATCH-EXAMPLE.psh.yaml
patchshuttle plan patches/examples/PATCH-EXAMPLE.psh.yaml --diff

The command displays the normalized job hash, effective project Python interpreter when one is used, sequential action dispositions, files and directories that would be created or modified, requested checks, Python formatting scope, a per-file isort/Black decision matrix, optional HTML lint scope, successful quality preflight records, protected-path result, backup destination template, rollback policy, and whether later execution will require confirmation. --diff also prints a bounded unified preview of the final resolved bytes without writing them.

Planning performs the complete implemented read-only preflight:

  • applies local action-count, check, size, ignored-path, and protected-path policy;
  • simulates sequential changes in memory, including changes to a file created earlier in the same job;
  • checks exact occurrence counts and idempotent NO_CHANGE states;
  • reports exact-match line numbers or up to three bounded, similarity-ranked nearby snippets when an occurrence count does not match;
  • parses and dry-runs text-only unified diffs without a shell or external patch command, with hunk count and first-context-mismatch diagnostics;
  • rejects binary content, unsupported target encodings, mixed newline styles, symbolic links, special files, and file-size violations;
  • validates check paths, the conservative pytest argument allowlist, dotted Django labels, local profiles, and required Python modules;
  • detects Python source encodings with the PEP 263 mechanism and records separate baseline and final-planned compatibility for isort and Black on every non-excluded changed-Python file;
  • reports FORMATTER_BASELINE_INCOMPATIBLE when both the existing and planned file remain incompatible, FORMATTER_PATCH_INCOMPATIBLE when the planned change introduces incompatibility, and allows a plan that repairs an incompatible baseline;
  • when locally enabled, passes every final planned changed .html file to djLint through stdin from an isolated temporary configuration root before confirmation;
  • records target fingerprints and computes final bytes and SHA-256 hashes for the internal transactional runner.

Policy blocks exit with code 4, planning failures with code 5, and missing check profiles or dependencies with code 9. A successful plan exits with code 0. In version 0.1.0a3, a failed planning attempt after workspace resolution writes a timestamped PLAN_FAILED log. The terminal result prints its path, and patchshuttle logs --last returns that failure log until a newer recorded artifact is written.

The same planner is available from Python:

from patchshuttle import discover_workspace, load_job, plan_job, render_plan_diff

workspace = discover_workspace(".")
job = load_job(
    "patches/inbox/PATCH-001.psh.yaml",
    max_bytes=workspace.config.execution.max_job_bytes,
)
plan = plan_job(job, workspace)

print(plan.job_hash)
print(plan.files_to_create)
print(plan.files_to_modify)
print(render_plan_diff(plan).text)

Plan, its actions, checks, fingerprints, and final file changes are immutable. Planning does not create target directories or files, execute audits or checks, write formatter or linter output, create backups, or alter registry state. A successful plan does not create a log. A failed plan may create only its managed failure log. Planning may invoke formatter libraries and the optional HTML linter against in-memory final content for compatibility preflight. A successful plan therefore does not mean that the job was applied.

Execute jobs

run is the universal executor for audit, patch, and verify jobs:

patchshuttle run patches/inbox/PATCH-001.psh.yaml

Audit jobs are read-only and do not prompt. Patch and verify jobs print the complete plan and local-code warning before asking Apply this job? [y/N]. Explicit automation can use:

patchshuttle run patches/inbox/PATCH-001.psh.yaml --yes

For a patch only, the user may deliberately retain partial declared changes after a failure:

patchshuttle run patches/inbox/PATCH-001.psh.yaml --keep-changes

This mode requires a second deny-by-default confirmation unless combined with --yes. It is rejected when local policy sets allow_keep_changes = false. The log and registry distinguish SKIPPED_CHANGES_KEPT from a skipped rollback where no declared change was published. Tests and profiles can still create external side effects outside PatchShuttle's transaction.

Kind-specific CLI entry points are also available:

patchshuttle audit patches/inbox/AUDIT-001.psh.yaml
patchshuttle verify patches/inbox/VERIFY-001.psh.yaml
patchshuttle verify patches/inbox/VERIFY-001.psh.yaml --yes

From Python, approval is required for patch and verify plans but not audits:

from patchshuttle import execute_plan

result = execute_plan(plan, approved=True)
print(result.status.value, result.backup_path, result.log_path)

RunResult is immutable and reports created and modified paths, backup and log locations, the archived job copy, initial check results, formatter results, retained formatted-file states, final check results, audit observations, and a workspace comparison where applicable.

An audit executes tree, read, literal search, bounded search_context, Python read_symbol, read_imports, python_structure, python_dependencies, python_references, find_files, file_info, SHA-256 hash, canonical hash_range, git_status, and environment. Traversal, file reads, match counts, and recorded output are bounded by local policy. Protected and ignored paths are skipped, source content appears only when explicitly requested by a content-reading audit action, and a before/after inventory verifies that the audit did not modify the workspace.

search_context reports bounded physical-line windows around literal matches. read_symbol parses Python source without importing it, resolves exactly one class, function, method, or nested symbol, includes its decorators, and reports the canonical source range and SHA-256 used by replace_symbol.

A verify job runs its controlled checks once, does not create a backup or run formatters, and compares the workspace before and after. A successful check that changes a non-ignored path returns UNEXPECTED_WORKSPACE_CHANGE; because project checks can have external effects, PatchShuttle reports but does not automatically undo those changes.

A patch plan may contain create_directory, create_file, replace_exact, replace_symbol, insert_before, insert_after, delete_exact, replace_range, delete_range, insert_at_line, apply_diff, ensure_import, remove_import, edit_imports, move_file, move_range, or move_symbol actions and applies the final bytes already computed by the planner.

The optional line-range actions are strict guarded operations for cases where a current audit already established an exact physical range. Lines are 1-based and ranges are inclusive. expected_content, expected_sha256, or both prove the target; line numbers only locate it. If both guards are supplied, both must pass against the sequential in-memory plan. Canonical guards use LF newlines and SHA-256 over UTF-8 bytes. PatchShuttle never fuzzes, relocates, partially applies, or automatically shifts a stale range. Use an audit hash_range when a digest is more compact than repeating a large old block.

A replace_symbol action names a dotted Python symbol, supplies the canonical SHA-256 returned by read_symbol, and provides complete replacement source. Planning resolves the symbol against the current sequential simulated file, rejects missing, duplicate, syntactically invalid, or stale targets, and does not use fuzzy relocation or automatic line shifting. Exact desired symbol content is an idempotent NO_CHANGE.

The optional expected_ast_sha256 guard uses the structural hash from read_symbol to tolerate formatting changes. It ignores ordinary comments, includes docstrings and decorators, and is scoped to the reported Python minor version. Supply the text hash too when comments must be guarded. AST-guarded replacements retain the name and kind and undergo compile and resolution validation. See next-release work for current scope.

For a supported plan, the implemented sequence is:

  1. validate the workspace lock file and acquire a non-blocking cross-platform lock;
  2. run the complete planner again under the lock and compare the resulting immutable plan with the approved plan;
  3. capture a deterministic inventory of non-ignored workspace entries before the first project write, hashing regular files within the configured entry and total-byte limits;
  4. copy every existing target into patches/backups/<JOB_ID>/<RUN_TIMESTAMP>/originals/ before the first project write and create a manifest containing PRESENT or ABSENT entries, SHA-256 hashes, sizes, modes, encoding, and newline metadata;
  5. create planned directories and files without accepting an existing-path race;
  6. stage modified-file bytes beside the target, flush them, recheck the approved original fingerprint, atomically replace the target, preserve its mode, and verify exact final bytes and hash;
  7. when HTML linting is locally enabled, run djLint in lint-only mode on the exact changed .html scope and require it not to modify transaction files;
  8. build fixed argument arrays for every requested check and run them in order from the workspace root with shell=False, per-check timeout, separate stdout and stderr capture, local output truncation limits, and Python bytecode caches redirected to an isolated temporary directory;
  9. stop on the first failed, timed-out, or unstartable initial check and verify that the checks did not change any declared transaction file;
  10. resolve the per-file local formatter policy, then run isort followed by Black only on each tool's exact non-excluded relative paths using the current interpreter in isolated mode, fixed argument arrays, shell=False, and the same timeout and bounded output controls;
  11. stop on formatter failure, reject changes to declared non-Python files, reject oversized or non-regular formatter targets, and retain the formatted bytes, SHA-256 hashes, sizes, and modes;
  12. when configured, repeat the same checks and require every formatted and non-formatted transaction file to retain its exact approved post-state;
  13. remove only new regular .pyc files and newly empty __pycache__ directories within the bounded changed-Python scope, preserving every pre-existing or foreign entry and treating unresolved cache paths as a transaction failure;
  14. capture the final bounded inventory and classify added, removed, modified, and type-changed paths as declared or unexpected;
  15. mark the manifest COMPLETED, or restore modified originals and remove only paths created by this attempt before recording ROLLED_BACK or ROLLBACK_FAILED; an explicitly accepted --keep-changes run instead records CHANGES_KEPT when a failed job published declared changes;
  16. archive the exact CLI source job, write a fixed-section UTF-8 log with a compact AI handoff block, and atomically commit registry state before releasing the workspace lock.

A no-change plan is revalidated under the lock and returns without creating a backup or launching checks. A race detected before replacement is not overwritten. Rollback validates retained originals before restoring them, refuses to follow symbolic links or remove foreign non-empty directories, and does not claim success when a tracked path cannot be restored.

The internal check runner supports the fixed ruff: {} F-only check, compileall, pytest, unittest, Django checks and tests, plain validated module imports, Django-aware validated module imports through manage.py shell -c, and locally configured profiles. Ruff receives only the immutable ordered Python scope derived from current non-no-change patch actions; a job cannot supply paths, rules, fixes, or arbitrary Ruff arguments. Project checks inherit the current process environment because they execute project code and PatchShuttle is not an operating-system sandbox. PatchShuttle overrides PYTHONPYCACHEPREFIX with a fresh temporary directory for each check and removes that directory afterward, keeping ordinary generated Python bytecode outside the workspace. A defensive cache ledger also handles .pyc files created inside newly added packages despite that override. Formatter order is fixed to isort then Black for protocol 1; local exact-path exclusions are resolved separately for each tool. Non-Python jobs skip formatting and do not repeat checks. Optional djLint uses a locally selected template profile, targets only changed .html files, never reformats them, and triggers the same rollback path on failure. It reads content through stdin from an isolated temporary configuration root, so project djLint configuration cannot weaken the local PatchShuttle lint policy. Transaction files are revalidated after each executable stage and unrelated final workspace changes are recorded. Default ignored paths include VCS metadata, virtual environments, dependency trees, PatchShuttle runtime state, and common Python caches.

The command maps approval, workspace locking, job-ID conflicts, actions, checks, formatting, and rollback failures to the documented process exit-code groups. A successful check that leaves an undeclared path change returns UNEXPECTED_WORKSPACE_CHANGE; PatchShuttle rolls back its declared files but does not delete or restore that external side effect. RunResult and execution errors expose the final WorkspaceComparison, log path, and archived job path.

Manual rollback

Roll back a completed patch interactively or with explicit automation:

patchshuttle rollback PATCH-001
patchshuttle rollback PATCH-001 --yes

The Python equivalent is rollback_job(workspace, "PATCH-001", approved=True). Manual rollback reloads the retained manifest and original copies, validates their identity and integrity, and compares every tracked path with the exact completed-job state. It refuses to overwrite a later user edit or remove a created directory containing undeclared entries. On success, it restores original files, removes only paths created by that job, marks the manifest ROLLED_BACK, records a fixed-section log, updates the registry, and allows the same job ID and hash to be applied again. A failure preserves the backup and reports unresolved paths without claiming restoration succeeded.

Recorded runs and AI handoff

For CLI execution, PatchShuttle preserves the input .psh.yaml byte-for-byte in patches/applied/ or patches/failed/. A Python API call without source_path= archives a deterministic YAML rendering of the immutable job model. Archive and log filenames include the configured-timezone timestamp, job ID, and, for archives, a short normalized hash. Numeric suffixes prevent same-second collisions.

Every job run log contains all standard sections in a fixed order, including LINT_HTML, using NOT_APPLICABLE where a stage did not run, and ends with PATCHSHUTTLE_AI_HANDOFF. Check and formatter output is bounded by local policy, as is HTML linter output. Common password, token, API-key, authorization-header, private-key, and sensitive Python assignment shapes are masked when redaction is enabled. Python assignment matching includes prefixed passwords, API and access keys, secret and private keys, tokens, and one-line SECRET_KEY_FALLBACKS collections while preserving loader calls such as config("SECRET_KEY"). Redaction is best-effort and is not a guarantee that a log contains no secrets; review a log before uploading it to any AI service.

Early VALIDATION_FAILED and PLAN_FAILED logs use a smaller fixed attempt format with SUMMARY and PATCHSHUTTLE_AI_HANDOFF sections. They record no project changes, backup, job archive, or registry update. An explicitly declined reviewed plan continues to use the full job log and exact failed-job archive with result USER_DECLINED.

Django system-check warnings can use an explicit project-local baseline:

patchshuttle warnings
patchshuttle warnings --add urls.W005
patchshuttle warnings --remove urls.W005

Only explicit W-class IDs are accepted. A django_check retains its normal exit code and full log output while adding known_warnings, new_warnings, and complete new-warning details. If captured output is truncated, classification is reported as incomplete. Existing workspaces without the protected baseline file start with an empty baseline; patchshuttle init creates the missing scaffold file without overwriting existing state. Find or compactly render the latest upload-friendly log:

patchshuttle logs --last
patchshuttle logs --last --ai
patchshuttle logs --last --ai-json

The default prints the path. --ai prints a bounded deterministic text view, while --ai-json prints the same selected data as compact JSON. These modes read the already stored log, preserve it unchanged, and never execute project code. They are mutually exclusive and retain useful audit outputs and failed check diagnostics within explicit bounds.

Run and early validation/planning failure logs also expose PYTHON_DISCOVERY_EVALUATION. The section records only current-job evidence: explicit Python paths, executed audit action counts and bounded output size before whole-log redaction, declared text/symbol/line targeting, and relevant failure signals. index_assessment: NOT_EVALUATED is intentional: the report neither estimates AI token savings nor decides whether a project symbol index is needed.

Discover Python structure before targeted reads

The read-only python_structure audit action collects a bounded declaration map before targeted reads. Start with the narrowest useful directory or one .py file because a broad package can produce a large audit result.

A complete YAML audit job looks like this:

protocol: 1
project_id: PSH-0123456789ABCDEF
id: AUDIT-PYTHON-STRUCTURE-001
kind: audit
actions:
  - python_structure:
      path: src/example
      max_files: 50
      max_symbols: 400
      compact: true
checks: []

path defaults to ., max_files defaults to 300, max_symbols defaults to 2000, and compact defaults to false. A file target must end in .py. Directory traversal respects the workspace's protected and ignored paths. The standard-library AST is used without importing or executing project code. Use compact: true for a navigational overview and omit it when detailed imports, parameters, decorators, and bases are required.

Compact output begins with the same collection counts, parse errors, and limit signals as full output, followed by per-file records containing only counts and navigational symbol metadata:

schema: patchshuttle.python_structure_collection.compact.v1
path: src/example
glob: *.py
files_considered: 1
files_parsed: 1
parse_errors: 0
imports_reported: 3
symbols_available: 2
symbols_reported: 2
file_limit_reached: false
symbol_limit_reached: false

schema: patchshuttle.python_structure.compact.v1
file: "src/example/service.py"
imports: 3
symbols: 2
symbol: {"kind":"class","lines":[10,42],"qualified_name":"Service"}
symbol: {"kind":"method","lines":[14,31],"qualified_name":"Service.run"}

With compact: false, the existing patchshuttle.python_structure_collection.v1 and patchshuttle.python_structure.v1 schemas remain unchanged and additionally include top-level import records, parameters, decorators, and bases.

Use the reported qualified name for a smaller follow-up audit:

actions:
  - read_symbol:
      path: src/example/service.py
      symbol: Service.run

If a change is needed, use the audit-derived draft or copy ast_sha256 from read_symbol on the same Python minor version. Add its text sha256 too when comments must be guarded. The action below is illustrative:

actions:
  - replace_symbol:
      path: src/example/service.py
      symbol: Service.run
      expected_ast_sha256: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
      new_content: |2
            def run(self, value):
                return value

The same audit can be constructed through the public Python adapter:

from patchshuttle import Job
from patchshuttle.actions import python_structure

job = Job(
    protocol=1,
    project_id="PSH-0123456789ABCDEF",
    id="AUDIT-PYTHON-STRUCTURE-001",
    kind="audit",
    actions=[
        python_structure(
            "src/example", max_files=50, max_symbols=400, compact=True
        )
    ],
)

Full python_structure output reports declarations, top-level imports, parameters, decorators, bases, source ranges, parse errors, and explicit limit signals. Compact output retains file and symbol counts, symbol kinds, qualified names, ranges, parse errors, and limit signals. Both modes exclude source values, calls, references, inferred types, caching, persistence, and automatic patch scope.

The same section reports aggregate Python file/search matches, whether file discovery or searches reached their declared result bounds, and total duration of executed Python-targeted audit actions. Newly initialized workspaces ignore and protect conventional .venv, venv, and node_modules trees at any depth. Existing patches/patchshuttle.toml files are never rewritten by init; owners must add nested environment patterns and project-specific backup paths to project.ignored_paths when needed.

The command selects the newest recorded run, snapshot, handoff, or failure-attempt log. Commands that intentionally produce no artifact, including successful validate, successful plan, version, capabilities, schema, and explain, do not replace it.

Inspect all registered jobs or one job ID:

patchshuttle status
patchshuttle status PATCH-001

The registry enforces stable identity. A completed ID with the same normalized hash returns ALREADY_APPLIED without rerunning actions, checks, or formatters. The same ID with different normalized content returns PATCH_ID_CONFLICT before mutable planning. Failed, rolled-back, and declined jobs may be retried with the same ID and hash. Registry writes and the definitive identity check share the same workspace lock as the project transaction.

Create a metadata-only project snapshot or a compact AI handoff:

patchshuttle snapshot
patchshuttle handoff

Both commands write timestamped .log files under patches/logs/. A snapshot contains versions, a bounded tree, sizes and SHA-256 hashes, Git status when available, recent jobs, capabilities, and policy summaries. It does not dump source-file contents. A handoff adds a provider-neutral AI instruction, latest run summary and PATCHSHUTTLE_AI_HANDOFF block, bounded tree, recent history, and the explicit requirement to return one .psh.yaml file.

Inspect local path policy

The planner uses the same public read-only policy API. It normalizes an AI-supplied path, applies the local protected-path rules, inspects every existing component without following symbolic links, and confirms that the resolved result remains in the workspace:

from patchshuttle import Policy, PolicyError, discover_workspace

policy = Policy(discover_workspace("."))

try:
    target = policy.resolve("src/example.py", allow_missing=True)
except PolicyError as error:
    print(error.code.value, error.path)
else:
    print(target.relative, target.kind.value)

Absolute and drive-qualified paths, URLs, parent traversal, protected paths, symbolic-link targets or parents, sockets, devices, and named pipes are rejected with stable error codes. Both / and \ are treated as separators so a job cannot use platform-specific spelling to bypass a rule.

Protected and ignored glob patterns come only from patches/patchshuttle.toml. ** matches zero or more complete path segments, protected-path exceptions override configured protected globs, and matching uses platform-appropriate case behavior. The workspace root requires explicit allow_root=True access and patches/ remains a hard block for job targets. Policy.is_ignored() is used by bounded inventory, audit traversal, snapshot, and handoff generation.

Calling Policy directly performs no action planning or file writes.

Run the scaffold checks:

python -m isort --check-only src tests tools
python -m black --check src tests tools
python -m coverage erase
python -m coverage run -m pytest -q
python -m coverage report --fail-under=100
python -m build
python -m twine check dist/*
python tools/release_checks.py dist
python tools/wheel_smoke.py dist/patchshuttle-0.1.0a7-py3-none-any.whl --version 0.1.0a7

The 0.1.0a2, 0.1.0a3, and 0.1.0a4 alpha releases each passed the required Ubuntu and Windows compatibility matrix, TestPyPI qualification, production PyPI Trusted Publishing, and a clean post-release installation. Their exact release and workflow links and artifact hashes, together with the separately scoped 0.1.0a2 ChatGPT end-to-end evidence, are recorded in docs/RELEASE.md. Follow that guide in order for every future release and append separate immutable evidence after each gate completes. Rerun CI after every release-candidate change.

Python architecture policy

PatchShuttle can stop an AI-generated patch from making an already crowded Python codebase worse. The policy is local owner configuration, not a job option, and is enabled by default for the fixed modular-monolith / package-by-feature profile in ratchet mode.

[architecture]
enabled = true
profile = "modular-monolith"
organization = "package-by-feature"
mode = "ratchet"
exclude = ["**/migrations/**", "**/generated/**"]
max_report_items = 50

[architecture.module]
warning_lines = 500
max_lines = 1000

[architecture.package]
warning_python_files = 15
max_python_files = 25

[architecture.patch]
warning_new_python_files = 5
max_new_python_files = 10
warning_new_packages = 1
max_new_packages = 3

The planner evaluates its in-memory result before touching the workspace. A file already above a limit may stay the same size or shrink; making it larger is a new regression and is blocked at the hard limit. The same rule applies to direct Python-file counts in touched directories. Patch-wide file and package budgets catch the kind of broad root-level file growth that is difficult to review.

Plans and stored logs expose the profile, status, measured counts, bounded findings, and whether the report was truncated. ARCH001/ARCH002 cover module size, ARCH010/ARCH011 package size, and ARCH020/ARCH021 patch growth. Warnings remain visible but do not block execution; errors fail planning before confirmation, backup, or project checks.

This is deliberately a structural guard. It does not infer feature boundaries, build dependency or call graphs, detect cycles, judge semantic responsibility, reorganize files, or change the patch scope selected by the AI.

Structured project history

PatchShuttle writes one compact JSON record for each recorded execution attempt to patches/history/<job-id>/<record-id>.json. These records are intended for long-term external ingestion and AI retrieval. They describe PatchShuttle's own operations only; requirements, conversations, product decisions, stages, browser tests, and semantic project memory belong to an external orchestrator.

History has a different purpose from the other outputs:

  • execution logs contain detailed diagnostic evidence and command output;
  • AI logs are bounded deterministic views derived from execution logs;
  • history records contain compact schema-versioned facts plus references to the detailed log and its derived AI-log view.

job.title and bounded, redacted job.description are stored below declared as operator or AI-supplied intent. They are never presented as observed facts. observed contains only trusted execution state: status, files, checks, failures, rollback, warnings, and reliably targeted symbols. PatchShuttle does not infer workarounds, semantic meaning, relationships, or affected symbols it cannot prove.

Read records without parsing human-readable logs:

patchshuttle history list --limit 20
patchshuttle history latest
patchshuttle history latest PATCH-EXAMPLE-001
patchshuttle history show PATCH-EXAMPLE-001/2026_08_27_120000_000001_abcdef01

latest and show return the full canonical JSON record. list returns bounded JSON summaries, one per line. Python integrations can call list_history_records, latest_history_record, and read_history_record from patchshuttle.history. External systems may also ingest the per-attempt JSON files directly.

History writing occurs after the required detailed log and registry update. It is best-effort: a history storage failure is exposed through the Python result's history_warning, but does not turn a completed job into a failure or trigger rollback. Existing protocol-1 jobs require no changes. Existing workspaces may add patches/history/** to project.ignored_paths; new default configurations already exclude it from workspace inventories.

The v1 layout is append-only and uses exclusive file creation. It deliberately has no database, JSONL index, semantic search, vector store, relationship graph, or Farm-specific behavior.

Manual workflow

The implemented local cycle is:

  1. Give an AI the latest PatchShuttle handoff or log.
  2. Receive one declarative .psh.yaml job.
  3. Review the local execution plan.
  4. Execute an audit, approved patch, or approved verification job.
  5. For a patch, run optional changed-HTML linting, controlled checks, the locally resolved isort/Black scopes, and final checks.
  6. Receive a timestamped log or generate a fresh handoff.
  7. Retrieve configured MCP history or provide the log through an authorized channel. Current MCP history returns summaries/references, not full logs. Review the observed result before continuing with the next job.

See SPEC_V0_1.md for the approved product contract and CHANGELOG.md for release notes. PatchShuttle is designed for ChatGPT and other AI services. Version 0.1.0a2 is Tested with ChatGPT for a recorded audit, patch, formatting, repeated-check, and independent verification workflow on Windows 11 with Python 3.14.2. This statement applies to that documented workflow, not to every supported action, failure path, AI model, or project.

License

PatchShuttle is licensed under the MIT License.

Guarded source transfers

  • move_range: path, start_line, end_line, source expected_content and/or expected_sha256, destination, destination_line and expected_destination_sha256.
  • move_symbol: path, symbol, expected_ast_sha256, destination, expected_destination_sha256 and optional destination_class.

Destination hashes cover simulated raw bytes; source range hashes cover canonical LF/UTF-8 text. Destinations must exist or be created earlier in the same job. Range insertion is before the 1-based destination line; EOF+1 appends. Symbol insertion appends at module scope or to the named class. Both files are validated and participate in the same backup/check/rollback transaction.

Same-file transfers, case-only path differences, duplicate destination names, closure extraction, class-bound super/private-name moves and multiline-string reindentation are rejected. Imports and callers are not automatically rewritten; include explicit edits and focused project checks. These actions move source content and leave the source file present. Whole-file moves are separate work.

Whole-file transfers

move_file moves an existing supported text file to an absent destination. Supply path, destination, and expected_sha256 of the original raw bytes. The transaction preserves original bytes and permissions, creates missing parent directories, and reports the source in deleted_files. Formatters and checks target the surviving destination. Add explicit import and caller edits when moving Python modules; imports are not automatically rewritten.

The source and destination must not have been touched earlier in the job. Subsequent edits to the destination are supported. Case-only moves, chains, swaps, binary files and reuse of the vacated path are currently rejected. A failed check restores the source and removes the destination. Manual rollback also verifies the applied state and refuses to overwrite a recreated source.

Optional integrations

  • patchshuttle[mcp]: authenticated, read-only history retrieval for ChatGPT. See setup and deployment limits.
  • patchshuttle[django]: reviewed migration plans, explicit execution/reversal, and a separate recovery journal. See Django workflow.

Structural editing and optional adapters in 0.1.0a6

Use read_symbol AST hashes for guarded class/method replacements; use read_imports, ensure_import, and remove_import for structural imports. move_file, move_range, and move_symbol support explicit refactoring plans with shared checks and rollback. Imports and callers are not inferred. See scope and examples.

Optional ChatGPT history retrieval uses an owner-configured authenticated MCP server (patchshuttle[mcp]). Optional Django migrations provide reviewed plans, locks and recovery journals (patchshuttle[django]). A live ChatGPT deployment requires the owner's HTTPS endpoint and OAuth configuration.

AI workflow in a7 development

Start with the installed capabilities/schema and the workspace handoff. The AI guide contains the operation decision table, guard sources, refactoring checklist and recovery boundaries. Existing Python declaration edits should normally use read_symbol then replace_symbol; request include_template: true for a guarded draft. Use edit_imports for a sequence of import edits. Keep audits separate from patches and review actual execution outcomes. The explicit |2 in the method example preserves Python indentation after YAML parsing. Documentation examples use illustrative bodies and hashes; replace these with observed source and guards before submitting.

This section describes the development branch, not a published a7 release.

Metadata

Release files for patchshuttle 0.1.0a7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for patchshuttle 0.1.0a7
File Size Uploaded Provenance
patchshuttle-0.1.0a7.tar.gz 394.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for patchshuttle 0.1.0a7
File Interpreter ABI Platform
patchshuttle-0.1.0a7-py3-none-any.whl Python 3 none any Details

Total release size: 619.0 kB

Release files / patchshuttle-0.1.0a7.tar.gz

Download URL patchshuttle-0.1.0a7.tar.gz
Size 394.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b0486a5d6c39cad2285f872825e4d5f0a3017042677ea211cfcd817e6faa9bf9
BLAKE2b-256 checksum
How to use checksums
d1f34f02b66a83838a41607b3588b4e7072286c2b0317f950f6bda65a931e31d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / patchshuttle-0.1.0a7-py3-none-any.whl

Download URL patchshuttle-0.1.0a7-py3-none-any.whl
Size 224.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
55a4b6ca2a3499ea63e4a493f72d86b4fcb7fd0dbb8e29fb20b4fe3d9a368814
BLAKE2b-256 checksum
How to use checksums
17418625c8f8d582dec31769d9226d0b91653a918b8306de6f2d092fb6061d16
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for patchshuttle 0.1.0a7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for patchshuttle-0.1.0a7.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page