# Forked CLI
Forked CLI lets you treat your fork like a product: keep `trunk` fast‑forwarded to upstream, stack patch branches in a reproducible overlay, and guard releases with deterministic policy checks. This repository contains the editable CLI implementation, automation scripts, and the project handbook that tracks sprint and release work.
---
## Table of Contents
1. [Concepts](#concepts)
2. [Repository Layout](#repository-layout)
3. [Installation](#installation)
4. [Quick Start](#quick-start)
5. [Configuration (`forked.yml`)](#configuration-forkedyml)
6. [CLI Commands](#cli-commands)
7. [Guard Reports](#guard-reports)
8. [Logs & Generated Artifacts](#logs--generated-artifacts)
9. [Demo Repository](#demo-repository)
10. [Development Workflow](#development-workflow)
11. [Troubleshooting](#troubleshooting)
---
## Concepts
- **Upstream** – the canonical repository you track. `forked init` keeps a local branch named `trunk` fast‑forwarded to `upstream/<branch>`.
- **Patch branches** – lightweight topic branches (`patch/*`) stacked in `forked.yml.patches.order`. They are replayed during every build.
- **Overlay** – a disposable branch (`overlay/<id>`) that results from applying the ordered patch list on top of `trunk`. Optionally materialised via a Git worktree.
- **Guard** – policy checks (sentinels, both‑touched files, size caps) that validate the overlay and emit JSON reports for CI or local review.
Forked CLI coordinates those pieces so you can rebuild or guard your patch stack deterministically without mutating your day‑to‑day working tree.
---
## Repository Layout
```
.
├── src/ # CLI source modules (typer-based)
├── pyproject.toml # packaging metadata for editable install
├── scripts/setup-demo-repo.sh # helper to create a sandbox repo with patch branches
├── project-handbook/ # sprint/release handbook (Makefile-driven)
└── README.md # this document
```
Run-time artefacts are intentionally kept out of Git:
```
.forked/ # logs, guard reports, and overlay worktrees
```
The directory is gitignored by default.
---
## Installation
You can install the CLI in editable mode while iterating locally:
```bash
python -m pip install --upgrade pip
python -m pip install -e .
```
Once published, the recommended user path will be:
```bash
pipx install forked-cli
```
The CLI requires:
- Git ≥ 2.31 (Git ≥ 2.38 unlocks the `zdiff3` conflict style automatically)
- Python ≥ 3.10
---
## Quick Start
```bash
# optional: create a sandbox fork with patch branches
./scripts/setup-demo-repo.sh demo-forked
cd demo-forked
# 1. Initialise Forked CLI in the repo
forked init
# 2. Configure patch order and optional sentinels in forked.yml
sed -i 's/order: \[\]/order:\n - patch\/contract-update\n - patch\/service-logging/' forked.yml
# 3. Build an overlay (creates overlay/<id> + optional worktree)
forked build --id smoke --auto-continue
# 4. Guard the overlay (generates .forked/report.json)
forked guard --overlay overlay/smoke --mode block
# 5. Sync trunk & rebase patch branches against upstream
forked sync
```
## Feature-Sliced Overlays
1. **Define features and overlays** in `forked.yml` using the new `features` and `overlays` sections. Each feature lists the patch branches (slices) that compose it, and overlays map profile names to feature sets. Example:
```yaml
features:
payments_v2:
patches:
- patch/payments_v2/01-schema
- patch/payments_v2/02-api
overlays:
dev:
features: [payments_v2, branding]
```
2. **Scaffold feature slices** with the CLI:
```bash
forked feature create payments_v2 --slices 3
forked feature status # shows ahead/behind vs trunk
```
3. **Build overlays by profile or feature lists**:
```bash
# Profile-driven build (overlay/dev)
forked build --overlay dev --skip-upstream-equivalents
# Ad-hoc feature combination with include/exclude globs
forked build --features payments_v2,branding \
--include patch/branding/experimental \
--exclude patch/branding/old
```
The resolver preserves global patch order, surfaces unmatched patterns, and logs provenance (features, patches, and filters) to `.forked/logs/forked-build.log` and optional git notes on the overlay tip.
4. **Optimise repeat builds** with `--skip-upstream-equivalents` (filters commits that already exist on trunk via `git cherry`).
5. **Automate overlays safely** using the new selection metadata in git notes and build logs—these record the active feature set so guard/status tooling and downstream bots can reason about provenance.
The key artefacts after a build/guard cycle:
- `.forked/worktrees/<id>/` – the reuseable overlay worktree.
- `.forked/logs/forked-build.log` – append-only JSON telemetry describing each build.
- `.forked/report.json` – deterministic guard report used by CI and local review.
---
## Configuration (`forked.yml`)
`forked.yml` is committed to your repository and controls upstream, patch ordering, guards, and worktree behaviour.
```yaml
version: 1
upstream:
remote: upstream
branch: main
branches:
trunk: trunk
overlay_prefix: overlay/
patches:
order:
- patch/contract-update
- patch/service-logging
features:
contract_update:
patches:
- patch/contract-update
sentinels:
must_match_upstream:
- api/contracts/**
service_logging:
patches:
- patch/service-logging
overlays:
dev:
features: [contract_update, service_logging]
guards:
mode: warn # warn | block | require-override
both_touched: true
sentinels:
must_match_upstream:
- api/contracts/**
must_diverge_from_upstream:
- branding/**
size_caps:
max_loc: 0 # 0 disables the cap
max_files: 0
path_bias:
ours:
- config/forked/**
theirs:
- vendor/**
worktree:
enabled: true
root: ".forked/worktrees" # relative paths live under <repo>/.forked/worktrees/<id>
policy_overrides:
require_trailer: false
trailer_key: "Forked-Override"
```
Key behaviours:
- Relative `worktree.root` paths are relocated outside the Git repo to avoid nested worktrees.
- Setting `$FORKED_WORKTREES_DIR` overrides the root path. On POSIX platforms the CLI rejects Windows-style roots (`C:\…`) to prevent confusion.
- Sentinel sections determine whether specific paths must match or must diverge from upstream in the final overlay.
---
## CLI Commands
| Command | Purpose |
|---------|---------|
| [`forked init`](#forked-init) | Verify upstream remote, fast-forward `trunk`, scaffold config. |
| [`forked sync`](#forked-sync) | Fast-forward `trunk` to upstream and rebase every patch branch. |
| [`forked build`](#forked-build) | Rebuild an overlay branch (and optional worktree) from trunk + patches. |
| [`forked guard`](#forked-guard) | Evaluate policies against an overlay and write a JSON report. |
| [`forked status`](#forked-status) | Show trunk, patches, and the most recent overlays. |
| [`forked feature create`](#forked-feature-create) | Scaffold numbered patch slices for a feature. |
| [`forked feature status`](#forked-feature-status) | Display ahead/behind state for feature slices. |
| [`forked publish`](#forked-publish) | Tag and/or push an overlay branch. |
### `forked init`
```bash
forked init [--upstream-remote REMOTE] [--upstream-branch BRANCH]
```
Performs safety checks (clean working tree, remote exists), fetches from upstream, creates/updates the `trunk` branch, enables helpful Git settings (`rerere`, conflict style), and writes `forked.yml` if missing. On Git < 2.38 the CLI automatically falls back to `diff3` conflict style with a short notice.
### `forked sync`
```bash
forked sync
```
Fetches upstream, resets `trunk` to `upstream/<branch>`, then iterates through each patch listed in `forked.yml.patches.order` and rebases it onto trunk. If a rebase stops on conflicts, the command exits with code `4` and prints the branch to fix.
### `forked build`
```bash
forked build [--overlay PROFILE | --features NAME[,NAME...]] [--include PATTERN]...
[--exclude PATTERN]... [--id ID] [--skip-upstream-equivalents]
[--emit-conflicts] [--emit-conflicts-path PATH]
[--emit-conflict-blobs] [--conflict-blobs-dir DIR]
[--on-conflict MODE] [--on-conflict-exec COMMAND]
[--no-worktree] [--auto-continue] [--git-note/--no-git-note]
```
- `--overlay PROFILE` – select features defined in `forked.yml.overlays.<profile>.features`. When omitted, all patches in `patches.order` are applied. If `--id` is not provided, the profile name becomes the overlay id.
- `--features NAME[,NAME...]` – comma-separated list of feature keys to include. Mutually exclusive with `--overlay`.
- `--include` / `--exclude` – add or remove patch branches via exact names or glob patterns (applied after feature/overlay resolution).
- `--skip-upstream-equivalents` – skip cherry-picking commits already present on trunk (based on `git cherry -v`) and log the skipped counts per patch.
- `--id` – overlay identifier (default: overlay profile name or current date `YYYY-MM-DD`).
- `--no-worktree` – build directly in the current working tree instead of creating/reusing a worktree.
- `--emit-conflicts` – write a conflict bundle (`schema_version: 2`) when a cherry-pick halts, storing it at the default path `.forked/conflicts/<id>-<wave>.json`.
- `--emit-conflicts-path PATH` – write the conflict bundle to a custom location instead of the default path.
- `--emit-conflict-blobs` – export base/ours/theirs blobs for each conflicted path alongside the bundle (binary/large files always trigger blob export).
- `--conflict-blobs-dir DIR` – store exported blobs under a custom directory when `--emit-conflict-blobs` is used.
- `--on-conflict MODE` – conflict policy: `stop` (default, exit code 10), `bias` (apply recommended ours/theirs resolutions and continue), or `exec` (run an external command).
- `--on-conflict-exec COMMAND` – shell command invoked when `--on-conflict exec` is selected (use `{json}` as a placeholder for the bundle path).
- `--auto-continue` – legacy alias for `--on-conflict bias`.
- `--git-note/--no-git-note` – opt in/out of writing provenance notes to `refs/notes/forked-meta`.
Behaviour highlights:
- Worktree directories are reused between builds. If a stale directory blocks reuse, the CLI suffixes the path (e.g., `test-1`) and prints a reminder to run `git worktree prune`.
- Build summaries now display applied versus skipped commits (`--skip-upstream-equivalents`) and record the active feature set in `.forked/logs/forked-build.log` alongside resolver inputs.
- Conflict bundles capture multi-wave context, recommended resolutions, and blob locations. They are logged to `.forked/logs/forked-build.log` alongside exit metadata (status `conflict` for stop/exec, `success` when bias resolves conflicts).
- Optional git notes capture the selected features/patches to make overlay provenance discoverable with `git notes show`.
### `forked sync`
```bash
forked sync [--emit-conflicts] [--emit-conflicts-path PATH]
[--emit-conflict-blobs] [--conflict-blobs-dir DIR]
[--on-conflict MODE] [--on-conflict-exec COMMAND]
[--auto-continue]
```
- `--emit-conflicts` – emit a rebase conflict bundle when a patch fails to rebase, storing it at the default path `.forked/conflicts/sync-<branch>-<wave>.json`.
- `--emit-conflicts-path PATH` – place the sync conflict bundle at a custom location.
- `--emit-conflict-blobs` – export base/ours/theirs blobs for each conflicted file alongside the bundle.
- `--conflict-blobs-dir DIR` – store exported blobs under a custom directory when `--emit-conflict-blobs` is used.
- `--on-conflict MODE` – `stop` (default, exit code `10`), `bias` (apply recommended resolutions and continue the rebase), or `exec` (delegate to an external command).
- `--on-conflict-exec COMMAND` – command executed when `--on-conflict exec` is selected; `{json}` is replaced with the bundle path.
- `--auto-continue` – alias for `--on-conflict bias`.
Successful syncs return to the previously checked-out ref and log the run to `.forked/logs/forked-build.log` (`event: "forked.sync"`). When conflicts remain unresolved, the command exits with code `10` (or the external command’s status for `exec`).
### `forked guard`
```bash
forked guard --overlay OVERLAY [--output PATH] [--mode MODE] [--verbose]
```
- `--overlay` *(required)* – overlay branch/ref to analyse (e.g., `overlay/test`).
- `--output` – report destination (default `.forked/report.json`).
- `--mode` – overrides `guards.mode` (`warn`, `block`, or `require-override`).
- `--verbose` / `-v` – print sentinel matches and include extra debug data in the report/logs.
Policy overrides are configured in `forked.yml`:
```yaml
policy_overrides:
require_trailer: true
trailer_key: "Forked-Override"
allowed_values: ["sentinel","size","both_touched","all"]
```
When `guards.mode=require-override` (or `policy_overrides.require_trailer` is set), guard looks
for override trailers in this order: overlay tip commit → annotated tag message → git note
(`refs/notes/forked/override`). The first match wins; values can be comma- or space-delimited and
are normalised to lowercase (`sentinel`, `size`, `both_touched`, or `all`). Overrides must cover
every violation scope (or specify `all`) and respect `allowed_values`.
The v2 report schema contains:
- `both_touched` – files changed in both trunk and overlay since the merge base.
- `sentinels.must_match_upstream` / `.must_diverge_from_upstream` – validation results for sentinel globs.
- `size_caps` – diff size metrics via `git diff --numstat`.
- `violations` – subset of the above that failed policy.
- `override` – `{enabled, source, values, applied}` describing the override that was honoured (source `commit|tag|note|none`).
- `features` – provenance-sourced feature list for the overlay (`source` reflects provenance log, git note, or resolver fallback).
Example extract:
```json
{
"report_version": 2,
"violations": {"sentinels": {...}},
"override": {
"enabled": true,
"source": "commit",
"values": ["sentinel"],
"applied": true
},
"features": {
"source": "provenance-log",
"values": ["contract_update"],
"patches": ["patch/contract-update"]
}
}
```
Exit codes:
- `0` – pass (or violations in `warn` mode, or `require-override` when a valid override is applied).
- `2` – policy violations in `block`/`require-override` mode without a valid override.
- `3` – configuration missing/invalid.
- `4` – Git failure (dirty tree, missing remote, etc.).
### `forked status`
```bash
forked status [--latest N] [--json]
```
- Default output mirrors previous behaviour: upstream/trunk SHAs, patch branches in configured order, and the newest overlays with their build timestamps and both-touched counts. The overlay window defaults to the latest **5** entries; adjust with `--latest N`.
- `--json` emits a machine-readable payload (`status_version: 1`) suitable for dashboards or guard automation. Provenance is sourced from `.forked/logs/forked-build.log` / `refs/notes/forked-meta`, with automatic fallbacks when those entries are missing.
Example:
```json
{
"status_version": 1,
"upstream": {"remote": "upstream", "branch": "main", "sha": "c0ffee..."},
"trunk": {"name": "trunk", "sha": "b4d00d..."},
"patches": [
{"name": "patch/payments/01", "sha": "1234abcd...", "ahead": 2, "behind": 0}
],
"overlays": [
{
"name": "overlay/dev",
"sha": "feedf00d...",
"built_at": "2025-10-20T18:45:02Z",
"selection": {
"source": "provenance-log",
"features": ["payments"],
"patches": ["patch/payments/01"]
},
"both_touched_count": 1
}
]
}
```
Common `jq` flows:
```bash
forked status --json | jq '.overlays[].selection.features'
forked status --json --latest 1 | jq '.patches[] | {name, ahead, behind}'
```
When no overlays exist, the command returns an empty array and prints an informational message; consumers should treat `both_touched_count: null` as “guard data not yet collected”.
### `forked clean`
```bash
forked clean [--overlays FILTER] [--keep N] [--worktrees] [--conflicts]
[--conflicts-age DAYS] [--dry-run/--no-dry-run] [--confirm]
```
- Dry-run is the default: the command prints a grouped summary (overlays, worktrees, conflicts) with the exact Git/File operations that would occur. No changes are made until you pass both `--no-dry-run` **and** `--confirm`.
- `--overlays FILTER` – target overlay branches by age (`30d`) or glob (`overlay/tmp-*`). Repeat the flag to combine filters. Use `--keep N` to preserve the N newest overlays regardless of filters. Tagged overlays, active worktrees, and the current branch are always skipped.
- `--worktrees` – prune stale worktrees via `git worktree prune` and remove leftover directories under `.forked/worktrees/*` that no longer map to live overlays.
- `--conflicts` – delete conflict bundles under `.forked/conflicts` older than the retention window (default 14 days). The newest bundle per overlay id is retained; override the threshold with `--conflicts-age`.
- Every destructive run appends an entry to `.forked/logs/clean.log` so operators have an audit trail.
Examples:
```bash
# Preview overlays older than 30 days, keeping the 2 most recent
forked clean --dry-run --overlays 30d --keep 2
# Remove temporary overlays once reviewed
forked clean --overlays 'overlay/tmp-*' --no-dry-run --confirm
# Clear stale worktrees and conflict bundles in a single sweep
forked clean --worktrees --conflicts --no-dry-run --confirm
```
### `forked feature create`
```bash
forked feature create NAME [--slices N] [--slug TEXT]
```
- `NAME` – feature identifier (kebab/snake case recommended).
- `--slices` – number of patch slices to create (default `1`).
- `--slug` – optional suffix for each slice (e.g., `--slug initial` produces `patch/<name>/01-initial`).
The command enforces a clean working tree, creates patch branches based on the current `trunk` tip, appends them to `forked.yml.patches.order`, and writes a new `features.<name>` entry. Branch creation fails fast if the feature already exists or if any target branch name is present.
### `forked feature status`
```bash
forked feature status
```
Prints each feature from `forked.yml.features` with the SHA (first 12 characters) of every slice and its ahead/behind counts relative to `trunk`. Fully merged slices are marked accordingly, providing a quick glance at feature progress before building or publishing overlays.
### `forked publish`
```bash
forked publish --overlay OVERLAY [--tag TAG] [--push] [--remote REMOTE]
```
Creates (or force-updates) a tag pointing at the overlay and optionally pushes the tag and overlay branch to a remote (default `origin`). Useful once a guarded overlay is ready to share.
---
## Guard Reports
Default location: `.forked/report.json`
Example (trimmed):
```json
{
"report_version": 2,
"overlay": "overlay/dev",
"trunk": "trunk",
"base": "6c535ebe766748006eea7f5fc21d0eaa2bcf01a2",
"violations": {
"sentinels": {
"must_match_upstream": ["api/contracts/v1.yaml"],
"must_diverge_from_upstream": []
}
},
"both_touched": ["src/service.py"],
"size_caps": {
"files_changed": 3,
"loc": 42,
"violations": true
},
"override": {
"enabled": true,
"source": "commit",
"values": ["sentinel"],
"applied": true,
"allowed_values": ["sentinel", "size", "both_touched", "all"]
},
"features": {
"source": "provenance-log",
"values": ["contract_update"],
"patches": ["patch/contract-update"]
}
}
```
Guard checks in `mode=require-override` look for the configured trailer key (default `Forked-Override`) on the overlay tip commit, then annotated tags, then `refs/notes/forked/override`. The `override` block records which source supplied the escalation marker and whether it satisfied the active violations (or the special value `all`). The `features` block carries the provenance list harvested from build logs/notes so downstream tooling knows which slices were active.
Downstream tooling (CI, bots) can parse `violations` and `override` to fail builds or surface escalation guidance. The `report_version` field allows the format to evolve while preserving backwards compatibility.
---
## Conflict Bundles
When conflict bundling is enabled (`--emit-conflicts` or `--emit-conflicts-path`), `forked build` and `forked sync` record conflict bundles (`schema_version: 2`) under `.forked/conflicts/<id>-<wave>.json`:
```json
{
"schema_version": 2,
"wave": 1,
"context": {
"mode": "build",
"overlay": "overlay/dev",
"patch_branch": "patch/conflict",
"patch_commit": "44b1b20...",
"merge_base": "6d913cc...",
"feature": "conflict_feature"
},
"files": [
{
"path": "app.py",
"binary": false,
"size_bytes": 29,
"precedence": {
"sentinel": "must_match_upstream",
"path_bias": "none",
"recommended": "ours",
"rationale": "matched sentinel must_match_upstream"
},
"commands": {
"accept_ours": "git checkout --ours -- 'app.py' && git add 'app.py'",
"accept_theirs": "git checkout --theirs -- 'app.py' && git add 'app.py'",
"open_mergetool": "git mergetool -- 'app.py'"
}
}
],
"resume": {
"continue": "git cherry-pick --continue",
"abort": "git cherry-pick --abort",
"rebuild": "forked build --id dev --on-conflict stop"
},
"note": "Commands assume a POSIX-compatible shell (e.g. bash, git bash, WSL)."
}
```
- **Wave numbering** – repeated conflicts in a single invocation append `-2.json`, `-3.json`, etc., and every wave is logged to `.forked/logs/forked-build.log` (`event: "forked.build"` or `"forked.sync"`).
- **Binary & large files** – `binary: true` entries omit diffs, record `size_bytes`, and always write `base.txt`/`ours.txt`/`theirs.txt` into the configured blob directory.
- **Automation hooks** – `--on-conflict bias` records auto-applied actions; `--on-conflict exec` retains the conflicted worktree and exits with the delegated command’s status.
Exit codes: `10` for unresolved conflicts, external command status for exec mode, and raw Git exit codes for non-conflict failures.
---
## Logs & Generated Artifacts
| Path | Purpose |
|------|---------|
| `.forked/logs/forked-build.log` | Append-only JSON telemetry for each build (overlay id, resolver input/features, per-branch commit & skip summaries, reused path). |
| `.forked/logs/forked-guard.log` | Append-only JSON telemetry for guard runs (overlay, mode, violations, optional debug). |
| `.forked/report.json` | Latest guard report. |
| `.forked/worktrees/<overlay-id>/` | Reused worktree for the overlay (removed by `git worktree prune`). |
All of these paths are ignored via `.gitignore` so your repo stays clean.
---
## CI Example
Capture bundles in CI and highlight a deterministic failure when exit code `10` occurs:
```yaml
- name: Build overlay with conflict bundle
run: |
set -e
forked build --overlay dev \
--emit-conflicts-path .forked/conflicts/ci \
--on-conflict stop || status=$?
if [ "${status:-0}" = "10" ]; then
bundle=$(ls .forked/conflicts/ci-*.json)
echo "::error::Conflict bundle generated at ${bundle}"
exit 1
fi
```
Upload `.forked/conflicts/` as an artifact so reviewers (or downstream automation) can inspect the JSON and Blob exports when a rebase/build fails.
---
## Demo Repository
Need a sandbox with realistic branches? Use the helper script:
```bash
./scripts/setup-demo-repo.sh demo-forked
cd demo-forked
forked init
# forked.yml now lists upstream, trunk, and patch branches created by the script
```
The script provisions:
- `patch/contract-update` and `patch/service-logging`
- sentinel-friendly directories (`config/forked/**`, `branding/**`)
- both upstream and origin bare remotes for push/pull simulation
---
## Development Workflow
```bash
# install runtime + dev dependencies inside a Poetry virtualenv
poetry install --with dev
# runtime modules live directly under src/
# lint & format with Ruff
poetry run ruff check .
poetry run ruff format --check .
# apply formatting automatically when needed
poetry run ruff format .
# run mypy (configured via pyproject.toml)
poetry run mypy
# run project handbook automation (e.g., sprint dashboards)
poetry run make -C project-handbook help
```
> Tip: `poetry shell` drops you into the virtualenv; otherwise prefix commands
> with `poetry run` (for example `poetry run forked status --json`).
### Publishing to PyPI
```bash
# 1. Update version in pyproject.toml (PEP 440 format)
# 2. Verify packaging artefacts locally
poetry build
# 3. Publish using an API token (set POETRY_PYPI_TOKEN_PYPI beforehand)
poetry publish --build
```
The build step produces `dist/forked_cli-<version>.whl` and `.tar.gz`. Inspect the wheel (`unzip -l dist/*.whl`) if you need to confirm module contents before publishing.
#### GitHub Actions Release
- Push a tag matching `v*` (e.g., `v0.2.0`) to trigger `.github/workflows/publish.yml`.
- Store your PyPI token as `PYPI_API_TOKEN` in the repository secrets.
- The workflow runs Ruff, mypy, pytest, builds the artefacts via Poetry, and publishes with `pypa/gh-action-pypi-publish`.
Key handbook commands:
- `make task-list` – show current sprint tasks (`project-handbook/Makefile`).
- `make sprint-status` – current sprint health indicators.
- `make release-status` – release v1.0.0 progress overview.
When making CLI changes, regenerate the demo repo (script above), rerun `forked build` and `forked guard`, and inspect `.forked/logs/forked-build.log` to confirm logging.
---
## Troubleshooting
| Symptom | Resolution |
|---------|------------|
| `forked init` prints “Using diff3 merge conflict style…” | You are running Git < 2.38; the CLI falls back to a supported conflict style automatically. Upgrade Git if you want `zdiff3`. |
| Build warns about suffixing worktree directories | Run `git worktree prune` to remove stale entries, or delete the directory manually. |
| Guard exits with code `2` unexpectedly | Inspect `.forked/report.json` – look under `violations`. Run in `--mode warn` to explore without failing. |
| `forked build` applies no commits | Ensure `forked.yml.patches.order` lists your patch branches and that they diverge from trunk. |
| Guard report missing sentinel hits | Confirm the globs in `forked.yml.guards.sentinels` match actual file paths. |
---
Forked CLI is still evolving. If you have questions or ideas for the next iteration (better guard reporting, new commands, CI integrations), open an issue or capture it in the project handbook backlog. Happy overlaying! 🚀
Raw data
{
"_id": null,
"home_page": null,
"name": "forked-cli",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "git, cli, fork, rebasing, automation",
"author": "Spenquatch",
"author_email": "spmcconnell.totaltele@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/88/86/147b573b818587f6aa7d39536946787ad25e47f3dddd15cc37662f21fb1e/forked_cli-0.0.1.tar.gz",
"platform": null,
"description": "# Forked CLI\n\nForked CLI lets you treat your fork like a product: keep `trunk` fast\u2011forwarded to upstream, stack patch branches in a reproducible overlay, and guard releases with deterministic policy checks. This repository contains the editable CLI implementation, automation scripts, and the project handbook that tracks sprint and release work.\n\n---\n\n## Table of Contents\n\n1. [Concepts](#concepts)\n2. [Repository Layout](#repository-layout)\n3. [Installation](#installation)\n4. [Quick Start](#quick-start)\n5. [Configuration (`forked.yml`)](#configuration-forkedyml)\n6. [CLI Commands](#cli-commands)\n7. [Guard Reports](#guard-reports)\n8. [Logs & Generated Artifacts](#logs--generated-artifacts)\n9. [Demo Repository](#demo-repository)\n10. [Development Workflow](#development-workflow)\n11. [Troubleshooting](#troubleshooting)\n\n---\n\n## Concepts\n\n- **Upstream** \u2013 the canonical repository you track. `forked init` keeps a local branch named `trunk` fast\u2011forwarded to `upstream/<branch>`.\n- **Patch branches** \u2013 lightweight topic branches (`patch/*`) stacked in `forked.yml.patches.order`. They are replayed during every build.\n- **Overlay** \u2013 a disposable branch (`overlay/<id>`) that results from applying the ordered patch list on top of `trunk`. Optionally materialised via a Git worktree.\n- **Guard** \u2013 policy checks (sentinels, both\u2011touched files, size caps) that validate the overlay and emit JSON reports for CI or local review.\n\nForked CLI coordinates those pieces so you can rebuild or guard your patch stack deterministically without mutating your day\u2011to\u2011day working tree.\n\n---\n\n## Repository Layout\n\n```\n.\n\u251c\u2500\u2500 src/ # CLI source modules (typer-based)\n\u251c\u2500\u2500 pyproject.toml # packaging metadata for editable install\n\u251c\u2500\u2500 scripts/setup-demo-repo.sh # helper to create a sandbox repo with patch branches\n\u251c\u2500\u2500 project-handbook/ # sprint/release handbook (Makefile-driven)\n\u2514\u2500\u2500 README.md # this document\n```\n\nRun-time artefacts are intentionally kept out of Git:\n\n```\n.forked/ # logs, guard reports, and overlay worktrees\n```\n\nThe directory is gitignored by default.\n\n---\n\n## Installation\n\nYou can install the CLI in editable mode while iterating locally:\n\n```bash\npython -m pip install --upgrade pip\npython -m pip install -e .\n```\n\nOnce published, the recommended user path will be:\n\n```bash\npipx install forked-cli\n```\n\nThe CLI requires:\n\n- Git \u2265 2.31 (Git \u2265 2.38 unlocks the `zdiff3` conflict style automatically)\n- Python \u2265 3.10\n\n---\n\n## Quick Start\n\n```bash\n# optional: create a sandbox fork with patch branches\n./scripts/setup-demo-repo.sh demo-forked\ncd demo-forked\n\n# 1. Initialise Forked CLI in the repo\nforked init\n\n# 2. Configure patch order and optional sentinels in forked.yml\nsed -i 's/order: \\[\\]/order:\\n - patch\\/contract-update\\n - patch\\/service-logging/' forked.yml\n\n# 3. Build an overlay (creates overlay/<id> + optional worktree)\nforked build --id smoke --auto-continue\n\n# 4. Guard the overlay (generates .forked/report.json)\nforked guard --overlay overlay/smoke --mode block\n\n# 5. Sync trunk & rebase patch branches against upstream\nforked sync\n```\n\n## Feature-Sliced Overlays\n\n1. **Define features and overlays** in `forked.yml` using the new `features` and `overlays` sections. Each feature lists the patch branches (slices) that compose it, and overlays map profile names to feature sets. Example:\n\n ```yaml\n features:\n payments_v2:\n patches:\n - patch/payments_v2/01-schema\n - patch/payments_v2/02-api\n overlays:\n dev:\n features: [payments_v2, branding]\n ```\n\n2. **Scaffold feature slices** with the CLI:\n\n ```bash\n forked feature create payments_v2 --slices 3\n forked feature status # shows ahead/behind vs trunk\n ```\n\n3. **Build overlays by profile or feature lists**:\n\n ```bash\n # Profile-driven build (overlay/dev)\n forked build --overlay dev --skip-upstream-equivalents\n\n # Ad-hoc feature combination with include/exclude globs\n forked build --features payments_v2,branding \\\n --include patch/branding/experimental \\\n --exclude patch/branding/old\n ```\n\n The resolver preserves global patch order, surfaces unmatched patterns, and logs provenance (features, patches, and filters) to `.forked/logs/forked-build.log` and optional git notes on the overlay tip.\n\n4. **Optimise repeat builds** with `--skip-upstream-equivalents` (filters commits that already exist on trunk via `git cherry`).\n\n5. **Automate overlays safely** using the new selection metadata in git notes and build logs\u2014these record the active feature set so guard/status tooling and downstream bots can reason about provenance.\n\nThe key artefacts after a build/guard cycle:\n\n- `.forked/worktrees/<id>/` \u2013 the reuseable overlay worktree.\n- `.forked/logs/forked-build.log` \u2013 append-only JSON telemetry describing each build.\n- `.forked/report.json` \u2013 deterministic guard report used by CI and local review.\n\n---\n\n## Configuration (`forked.yml`)\n\n`forked.yml` is committed to your repository and controls upstream, patch ordering, guards, and worktree behaviour.\n\n```yaml\nversion: 1\nupstream:\n remote: upstream\n branch: main\nbranches:\n trunk: trunk\n overlay_prefix: overlay/\npatches:\n order:\n - patch/contract-update\n - patch/service-logging\nfeatures:\n contract_update:\n patches:\n - patch/contract-update\n sentinels:\n must_match_upstream:\n - api/contracts/**\n service_logging:\n patches:\n - patch/service-logging\noverlays:\n dev:\n features: [contract_update, service_logging]\nguards:\n mode: warn # warn | block | require-override\n both_touched: true\n sentinels:\n must_match_upstream:\n - api/contracts/**\n must_diverge_from_upstream:\n - branding/**\n size_caps:\n max_loc: 0 # 0 disables the cap\n max_files: 0\npath_bias:\n ours:\n - config/forked/**\n theirs:\n - vendor/**\nworktree:\n enabled: true\n root: \".forked/worktrees\" # relative paths live under <repo>/.forked/worktrees/<id>\npolicy_overrides:\n require_trailer: false\n trailer_key: \"Forked-Override\"\n```\n\nKey behaviours:\n\n- Relative `worktree.root` paths are relocated outside the Git repo to avoid nested worktrees.\n- Setting `$FORKED_WORKTREES_DIR` overrides the root path. On POSIX platforms the CLI rejects Windows-style roots (`C:\\\u2026`) to prevent confusion.\n- Sentinel sections determine whether specific paths must match or must diverge from upstream in the final overlay.\n\n---\n\n## CLI Commands\n\n| Command | Purpose |\n|---------|---------|\n| [`forked init`](#forked-init) | Verify upstream remote, fast-forward `trunk`, scaffold config. |\n| [`forked sync`](#forked-sync) | Fast-forward `trunk` to upstream and rebase every patch branch. |\n| [`forked build`](#forked-build) | Rebuild an overlay branch (and optional worktree) from trunk + patches. |\n| [`forked guard`](#forked-guard) | Evaluate policies against an overlay and write a JSON report. |\n| [`forked status`](#forked-status) | Show trunk, patches, and the most recent overlays. |\n| [`forked feature create`](#forked-feature-create) | Scaffold numbered patch slices for a feature. |\n| [`forked feature status`](#forked-feature-status) | Display ahead/behind state for feature slices. |\n| [`forked publish`](#forked-publish) | Tag and/or push an overlay branch. |\n\n### `forked init`\n\n```bash\nforked init [--upstream-remote REMOTE] [--upstream-branch BRANCH]\n```\n\nPerforms safety checks (clean working tree, remote exists), fetches from upstream, creates/updates the `trunk` branch, enables helpful Git settings (`rerere`, conflict style), and writes `forked.yml` if missing. On Git < 2.38 the CLI automatically falls back to `diff3` conflict style with a short notice.\n\n### `forked sync`\n\n```bash\nforked sync\n```\n\nFetches upstream, resets `trunk` to `upstream/<branch>`, then iterates through each patch listed in `forked.yml.patches.order` and rebases it onto trunk. If a rebase stops on conflicts, the command exits with code `4` and prints the branch to fix.\n\n### `forked build`\n\n```bash\nforked build [--overlay PROFILE | --features NAME[,NAME...]] [--include PATTERN]...\n [--exclude PATTERN]... [--id ID] [--skip-upstream-equivalents]\n [--emit-conflicts] [--emit-conflicts-path PATH]\n [--emit-conflict-blobs] [--conflict-blobs-dir DIR]\n [--on-conflict MODE] [--on-conflict-exec COMMAND]\n [--no-worktree] [--auto-continue] [--git-note/--no-git-note]\n```\n\n- `--overlay PROFILE` \u2013 select features defined in `forked.yml.overlays.<profile>.features`. When omitted, all patches in `patches.order` are applied. If `--id` is not provided, the profile name becomes the overlay id.\n- `--features NAME[,NAME...]` \u2013 comma-separated list of feature keys to include. Mutually exclusive with `--overlay`.\n- `--include` / `--exclude` \u2013 add or remove patch branches via exact names or glob patterns (applied after feature/overlay resolution).\n- `--skip-upstream-equivalents` \u2013 skip cherry-picking commits already present on trunk (based on `git cherry -v`) and log the skipped counts per patch.\n- `--id` \u2013 overlay identifier (default: overlay profile name or current date `YYYY-MM-DD`).\n- `--no-worktree` \u2013 build directly in the current working tree instead of creating/reusing a worktree.\n- `--emit-conflicts` \u2013 write a conflict bundle (`schema_version: 2`) when a cherry-pick halts, storing it at the default path `.forked/conflicts/<id>-<wave>.json`.\n- `--emit-conflicts-path PATH` \u2013 write the conflict bundle to a custom location instead of the default path.\n- `--emit-conflict-blobs` \u2013 export base/ours/theirs blobs for each conflicted path alongside the bundle (binary/large files always trigger blob export).\n- `--conflict-blobs-dir DIR` \u2013 store exported blobs under a custom directory when `--emit-conflict-blobs` is used.\n- `--on-conflict MODE` \u2013 conflict policy: `stop` (default, exit code 10), `bias` (apply recommended ours/theirs resolutions and continue), or `exec` (run an external command).\n- `--on-conflict-exec COMMAND` \u2013 shell command invoked when `--on-conflict exec` is selected (use `{json}` as a placeholder for the bundle path).\n- `--auto-continue` \u2013 legacy alias for `--on-conflict bias`.\n- `--git-note/--no-git-note` \u2013 opt in/out of writing provenance notes to `refs/notes/forked-meta`.\n\nBehaviour highlights:\n\n- Worktree directories are reused between builds. If a stale directory blocks reuse, the CLI suffixes the path (e.g., `test-1`) and prints a reminder to run `git worktree prune`.\n- Build summaries now display applied versus skipped commits (`--skip-upstream-equivalents`) and record the active feature set in `.forked/logs/forked-build.log` alongside resolver inputs.\n- Conflict bundles capture multi-wave context, recommended resolutions, and blob locations. They are logged to `.forked/logs/forked-build.log` alongside exit metadata (status `conflict` for stop/exec, `success` when bias resolves conflicts).\n- Optional git notes capture the selected features/patches to make overlay provenance discoverable with `git notes show`.\n\n### `forked sync`\n\n```bash\nforked sync [--emit-conflicts] [--emit-conflicts-path PATH]\n [--emit-conflict-blobs] [--conflict-blobs-dir DIR]\n [--on-conflict MODE] [--on-conflict-exec COMMAND]\n [--auto-continue]\n```\n\n- `--emit-conflicts` \u2013 emit a rebase conflict bundle when a patch fails to rebase, storing it at the default path `.forked/conflicts/sync-<branch>-<wave>.json`.\n- `--emit-conflicts-path PATH` \u2013 place the sync conflict bundle at a custom location.\n- `--emit-conflict-blobs` \u2013 export base/ours/theirs blobs for each conflicted file alongside the bundle.\n- `--conflict-blobs-dir DIR` \u2013 store exported blobs under a custom directory when `--emit-conflict-blobs` is used.\n- `--on-conflict MODE` \u2013 `stop` (default, exit code `10`), `bias` (apply recommended resolutions and continue the rebase), or `exec` (delegate to an external command).\n- `--on-conflict-exec COMMAND` \u2013 command executed when `--on-conflict exec` is selected; `{json}` is replaced with the bundle path.\n- `--auto-continue` \u2013 alias for `--on-conflict bias`.\n\nSuccessful syncs return to the previously checked-out ref and log the run to `.forked/logs/forked-build.log` (`event: \"forked.sync\"`). When conflicts remain unresolved, the command exits with code `10` (or the external command\u2019s status for `exec`).\n\n### `forked guard`\n\n```bash\nforked guard --overlay OVERLAY [--output PATH] [--mode MODE] [--verbose]\n```\n\n- `--overlay` *(required)* \u2013 overlay branch/ref to analyse (e.g., `overlay/test`).\n- `--output` \u2013 report destination (default `.forked/report.json`).\n- `--mode` \u2013 overrides `guards.mode` (`warn`, `block`, or `require-override`).\n- `--verbose` / `-v` \u2013 print sentinel matches and include extra debug data in the report/logs.\n\nPolicy overrides are configured in `forked.yml`:\n\n```yaml\npolicy_overrides:\n require_trailer: true\n trailer_key: \"Forked-Override\"\n allowed_values: [\"sentinel\",\"size\",\"both_touched\",\"all\"]\n```\n\nWhen `guards.mode=require-override` (or `policy_overrides.require_trailer` is set), guard looks\nfor override trailers in this order: overlay tip commit \u2192 annotated tag message \u2192 git note\n(`refs/notes/forked/override`). The first match wins; values can be comma- or space-delimited and\nare normalised to lowercase (`sentinel`, `size`, `both_touched`, or `all`). Overrides must cover\nevery violation scope (or specify `all`) and respect `allowed_values`.\n\nThe v2 report schema contains:\n\n- `both_touched` \u2013 files changed in both trunk and overlay since the merge base.\n- `sentinels.must_match_upstream` / `.must_diverge_from_upstream` \u2013 validation results for sentinel globs.\n- `size_caps` \u2013 diff size metrics via `git diff --numstat`.\n- `violations` \u2013 subset of the above that failed policy.\n- `override` \u2013 `{enabled, source, values, applied}` describing the override that was honoured (source `commit|tag|note|none`).\n- `features` \u2013 provenance-sourced feature list for the overlay (`source` reflects provenance log, git note, or resolver fallback).\n\nExample extract:\n\n```json\n{\n \"report_version\": 2,\n \"violations\": {\"sentinels\": {...}},\n \"override\": {\n \"enabled\": true,\n \"source\": \"commit\",\n \"values\": [\"sentinel\"],\n \"applied\": true\n },\n \"features\": {\n \"source\": \"provenance-log\",\n \"values\": [\"contract_update\"],\n \"patches\": [\"patch/contract-update\"]\n }\n}\n```\n\nExit codes:\n\n- `0` \u2013 pass (or violations in `warn` mode, or `require-override` when a valid override is applied).\n- `2` \u2013 policy violations in `block`/`require-override` mode without a valid override.\n- `3` \u2013 configuration missing/invalid.\n- `4` \u2013 Git failure (dirty tree, missing remote, etc.).\n\n### `forked status`\n\n```bash\nforked status [--latest N] [--json]\n```\n\n- Default output mirrors previous behaviour: upstream/trunk SHAs, patch branches in configured order, and the newest overlays with their build timestamps and both-touched counts. The overlay window defaults to the latest **5** entries; adjust with `--latest N`.\n- `--json` emits a machine-readable payload (`status_version: 1`) suitable for dashboards or guard automation. Provenance is sourced from `.forked/logs/forked-build.log` / `refs/notes/forked-meta`, with automatic fallbacks when those entries are missing.\n\nExample:\n\n```json\n{\n \"status_version\": 1,\n \"upstream\": {\"remote\": \"upstream\", \"branch\": \"main\", \"sha\": \"c0ffee...\"},\n \"trunk\": {\"name\": \"trunk\", \"sha\": \"b4d00d...\"},\n \"patches\": [\n {\"name\": \"patch/payments/01\", \"sha\": \"1234abcd...\", \"ahead\": 2, \"behind\": 0}\n ],\n \"overlays\": [\n {\n \"name\": \"overlay/dev\",\n \"sha\": \"feedf00d...\",\n \"built_at\": \"2025-10-20T18:45:02Z\",\n \"selection\": {\n \"source\": \"provenance-log\",\n \"features\": [\"payments\"],\n \"patches\": [\"patch/payments/01\"]\n },\n \"both_touched_count\": 1\n }\n ]\n}\n```\n\nCommon `jq` flows:\n\n```bash\nforked status --json | jq '.overlays[].selection.features'\nforked status --json --latest 1 | jq '.patches[] | {name, ahead, behind}'\n```\n\nWhen no overlays exist, the command returns an empty array and prints an informational message; consumers should treat `both_touched_count: null` as \u201cguard data not yet collected\u201d.\n\n### `forked clean`\n\n```bash\nforked clean [--overlays FILTER] [--keep N] [--worktrees] [--conflicts]\n [--conflicts-age DAYS] [--dry-run/--no-dry-run] [--confirm]\n```\n\n- Dry-run is the default: the command prints a grouped summary (overlays, worktrees, conflicts) with the exact Git/File operations that would occur. No changes are made until you pass both `--no-dry-run` **and** `--confirm`.\n- `--overlays FILTER` \u2013 target overlay branches by age (`30d`) or glob (`overlay/tmp-*`). Repeat the flag to combine filters. Use `--keep N` to preserve the N newest overlays regardless of filters. Tagged overlays, active worktrees, and the current branch are always skipped.\n- `--worktrees` \u2013 prune stale worktrees via `git worktree prune` and remove leftover directories under `.forked/worktrees/*` that no longer map to live overlays.\n- `--conflicts` \u2013 delete conflict bundles under `.forked/conflicts` older than the retention window (default 14 days). The newest bundle per overlay id is retained; override the threshold with `--conflicts-age`.\n- Every destructive run appends an entry to `.forked/logs/clean.log` so operators have an audit trail.\n\nExamples:\n\n```bash\n# Preview overlays older than 30 days, keeping the 2 most recent\nforked clean --dry-run --overlays 30d --keep 2\n\n# Remove temporary overlays once reviewed\nforked clean --overlays 'overlay/tmp-*' --no-dry-run --confirm\n\n# Clear stale worktrees and conflict bundles in a single sweep\nforked clean --worktrees --conflicts --no-dry-run --confirm\n```\n\n### `forked feature create`\n\n```bash\nforked feature create NAME [--slices N] [--slug TEXT]\n```\n\n- `NAME` \u2013 feature identifier (kebab/snake case recommended).\n- `--slices` \u2013 number of patch slices to create (default `1`).\n- `--slug` \u2013 optional suffix for each slice (e.g., `--slug initial` produces `patch/<name>/01-initial`).\n\nThe command enforces a clean working tree, creates patch branches based on the current `trunk` tip, appends them to `forked.yml.patches.order`, and writes a new `features.<name>` entry. Branch creation fails fast if the feature already exists or if any target branch name is present.\n\n### `forked feature status`\n\n```bash\nforked feature status\n```\n\nPrints each feature from `forked.yml.features` with the SHA (first 12 characters) of every slice and its ahead/behind counts relative to `trunk`. Fully merged slices are marked accordingly, providing a quick glance at feature progress before building or publishing overlays.\n\n### `forked publish`\n\n```bash\nforked publish --overlay OVERLAY [--tag TAG] [--push] [--remote REMOTE]\n```\n\nCreates (or force-updates) a tag pointing at the overlay and optionally pushes the tag and overlay branch to a remote (default `origin`). Useful once a guarded overlay is ready to share.\n\n---\n\n## Guard Reports\n\nDefault location: `.forked/report.json`\n\nExample (trimmed):\n\n```json\n{\n \"report_version\": 2,\n \"overlay\": \"overlay/dev\",\n \"trunk\": \"trunk\",\n \"base\": \"6c535ebe766748006eea7f5fc21d0eaa2bcf01a2\",\n \"violations\": {\n \"sentinels\": {\n \"must_match_upstream\": [\"api/contracts/v1.yaml\"],\n \"must_diverge_from_upstream\": []\n }\n },\n \"both_touched\": [\"src/service.py\"],\n \"size_caps\": {\n \"files_changed\": 3,\n \"loc\": 42,\n \"violations\": true\n },\n \"override\": {\n \"enabled\": true,\n \"source\": \"commit\",\n \"values\": [\"sentinel\"],\n \"applied\": true,\n \"allowed_values\": [\"sentinel\", \"size\", \"both_touched\", \"all\"]\n },\n \"features\": {\n \"source\": \"provenance-log\",\n \"values\": [\"contract_update\"],\n \"patches\": [\"patch/contract-update\"]\n }\n}\n```\n\nGuard checks in `mode=require-override` look for the configured trailer key (default `Forked-Override`) on the overlay tip commit, then annotated tags, then `refs/notes/forked/override`. The `override` block records which source supplied the escalation marker and whether it satisfied the active violations (or the special value `all`). The `features` block carries the provenance list harvested from build logs/notes so downstream tooling knows which slices were active.\n\nDownstream tooling (CI, bots) can parse `violations` and `override` to fail builds or surface escalation guidance. The `report_version` field allows the format to evolve while preserving backwards compatibility.\n\n---\n\n## Conflict Bundles\n\nWhen conflict bundling is enabled (`--emit-conflicts` or `--emit-conflicts-path`), `forked build` and `forked sync` record conflict bundles (`schema_version: 2`) under `.forked/conflicts/<id>-<wave>.json`:\n\n```json\n{\n \"schema_version\": 2,\n \"wave\": 1,\n \"context\": {\n \"mode\": \"build\",\n \"overlay\": \"overlay/dev\",\n \"patch_branch\": \"patch/conflict\",\n \"patch_commit\": \"44b1b20...\",\n \"merge_base\": \"6d913cc...\",\n \"feature\": \"conflict_feature\"\n },\n \"files\": [\n {\n \"path\": \"app.py\",\n \"binary\": false,\n \"size_bytes\": 29,\n \"precedence\": {\n \"sentinel\": \"must_match_upstream\",\n \"path_bias\": \"none\",\n \"recommended\": \"ours\",\n \"rationale\": \"matched sentinel must_match_upstream\"\n },\n \"commands\": {\n \"accept_ours\": \"git checkout --ours -- 'app.py' && git add 'app.py'\",\n \"accept_theirs\": \"git checkout --theirs -- 'app.py' && git add 'app.py'\",\n \"open_mergetool\": \"git mergetool -- 'app.py'\"\n }\n }\n ],\n \"resume\": {\n \"continue\": \"git cherry-pick --continue\",\n \"abort\": \"git cherry-pick --abort\",\n \"rebuild\": \"forked build --id dev --on-conflict stop\"\n },\n \"note\": \"Commands assume a POSIX-compatible shell (e.g. bash, git bash, WSL).\"\n}\n```\n\n- **Wave numbering** \u2013 repeated conflicts in a single invocation append `-2.json`, `-3.json`, etc., and every wave is logged to `.forked/logs/forked-build.log` (`event: \"forked.build\"` or `\"forked.sync\"`).\n- **Binary & large files** \u2013 `binary: true` entries omit diffs, record `size_bytes`, and always write `base.txt`/`ours.txt`/`theirs.txt` into the configured blob directory.\n- **Automation hooks** \u2013 `--on-conflict bias` records auto-applied actions; `--on-conflict exec` retains the conflicted worktree and exits with the delegated command\u2019s status.\n\nExit codes: `10` for unresolved conflicts, external command status for exec mode, and raw Git exit codes for non-conflict failures.\n\n---\n\n## Logs & Generated Artifacts\n\n| Path | Purpose |\n|------|---------|\n| `.forked/logs/forked-build.log` | Append-only JSON telemetry for each build (overlay id, resolver input/features, per-branch commit & skip summaries, reused path). |\n| `.forked/logs/forked-guard.log` | Append-only JSON telemetry for guard runs (overlay, mode, violations, optional debug). |\n| `.forked/report.json` | Latest guard report. |\n| `.forked/worktrees/<overlay-id>/` | Reused worktree for the overlay (removed by `git worktree prune`). |\n\nAll of these paths are ignored via `.gitignore` so your repo stays clean.\n\n---\n\n## CI Example\n\nCapture bundles in CI and highlight a deterministic failure when exit code `10` occurs:\n\n```yaml\n- name: Build overlay with conflict bundle\n run: |\n set -e\n forked build --overlay dev \\\n --emit-conflicts-path .forked/conflicts/ci \\\n --on-conflict stop || status=$?\n if [ \"${status:-0}\" = \"10\" ]; then\n bundle=$(ls .forked/conflicts/ci-*.json)\n echo \"::error::Conflict bundle generated at ${bundle}\"\n exit 1\n fi\n```\n\nUpload `.forked/conflicts/` as an artifact so reviewers (or downstream automation) can inspect the JSON and Blob exports when a rebase/build fails.\n\n---\n\n## Demo Repository\n\nNeed a sandbox with realistic branches? Use the helper script:\n\n```bash\n./scripts/setup-demo-repo.sh demo-forked\ncd demo-forked\nforked init\n# forked.yml now lists upstream, trunk, and patch branches created by the script\n```\n\nThe script provisions:\n\n- `patch/contract-update` and `patch/service-logging`\n- sentinel-friendly directories (`config/forked/**`, `branding/**`)\n- both upstream and origin bare remotes for push/pull simulation\n\n---\n\n## Development Workflow\n\n```bash\n# install runtime + dev dependencies inside a Poetry virtualenv\npoetry install --with dev\n\n# runtime modules live directly under src/\n\n# lint & format with Ruff\npoetry run ruff check .\npoetry run ruff format --check .\n# apply formatting automatically when needed\npoetry run ruff format .\n\n# run mypy (configured via pyproject.toml)\npoetry run mypy\n\n# run project handbook automation (e.g., sprint dashboards)\npoetry run make -C project-handbook help\n```\n\n> Tip: `poetry shell` drops you into the virtualenv; otherwise prefix commands\n> with `poetry run` (for example `poetry run forked status --json`).\n\n### Publishing to PyPI\n\n```bash\n# 1. Update version in pyproject.toml (PEP 440 format)\n# 2. Verify packaging artefacts locally\npoetry build\n# 3. Publish using an API token (set POETRY_PYPI_TOKEN_PYPI beforehand)\npoetry publish --build\n```\n\nThe build step produces `dist/forked_cli-<version>.whl` and `.tar.gz`. Inspect the wheel (`unzip -l dist/*.whl`) if you need to confirm module contents before publishing.\n\n#### GitHub Actions Release\n\n- Push a tag matching `v*` (e.g., `v0.2.0`) to trigger `.github/workflows/publish.yml`.\n- Store your PyPI token as `PYPI_API_TOKEN` in the repository secrets.\n- The workflow runs Ruff, mypy, pytest, builds the artefacts via Poetry, and publishes with `pypa/gh-action-pypi-publish`.\n\nKey handbook commands:\n\n- `make task-list` \u2013 show current sprint tasks (`project-handbook/Makefile`).\n- `make sprint-status` \u2013 current sprint health indicators.\n- `make release-status` \u2013 release v1.0.0 progress overview.\n\nWhen making CLI changes, regenerate the demo repo (script above), rerun `forked build` and `forked guard`, and inspect `.forked/logs/forked-build.log` to confirm logging.\n\n---\n\n## Troubleshooting\n\n| Symptom | Resolution |\n|---------|------------|\n| `forked init` prints \u201cUsing diff3 merge conflict style\u2026\u201d | You are running Git < 2.38; the CLI falls back to a supported conflict style automatically. Upgrade Git if you want `zdiff3`. |\n| Build warns about suffixing worktree directories | Run `git worktree prune` to remove stale entries, or delete the directory manually. |\n| Guard exits with code `2` unexpectedly | Inspect `.forked/report.json` \u2013 look under `violations`. Run in `--mode warn` to explore without failing. |\n| `forked build` applies no commits | Ensure `forked.yml.patches.order` lists your patch branches and that they diverge from trunk. |\n| Guard report missing sentinel hits | Confirm the globs in `forked.yml.guards.sentinels` match actual file paths. |\n\n---\n\nForked CLI is still evolving. If you have questions or ideas for the next iteration (better guard reporting, new commands, CI integrations), open an issue or capture it in the project handbook backlog. Happy overlaying! \ud83d\ude80\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Git-native fork overlay & guard tool",
"version": "0.0.1",
"project_urls": {
"Documentation": "https://github.com/Spenquatch/fork#readme",
"Issues": "https://github.com/Spenquatch/fork/issues",
"Repository": "https://github.com/Spenquatch/fork"
},
"split_keywords": [
"git",
" cli",
" fork",
" rebasing",
" automation"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "b2d1f45f246ecbd716ab44c2a825851bdd6a53c8654184ce591d5e709a32b45f",
"md5": "93bf1f9bf0b67b31d1c548a3d8a446dc",
"sha256": "119f48ab12558c9d09df95e6174b92eb6eda1b3662eef6a523d9420581e957cf"
},
"downloads": -1,
"filename": "forked_cli-0.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "93bf1f9bf0b67b31d1c548a3d8a446dc",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 42144,
"upload_time": "2025-10-21T22:42:14",
"upload_time_iso_8601": "2025-10-21T22:42:14.802745Z",
"url": "https://files.pythonhosted.org/packages/b2/d1/f45f246ecbd716ab44c2a825851bdd6a53c8654184ce591d5e709a32b45f/forked_cli-0.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "8886147b573b818587f6aa7d39536946787ad25e47f3dddd15cc37662f21fb1e",
"md5": "a7cc0ae21609ccad8844de660c49bbbc",
"sha256": "86952f8ef62d06458db98f262603bedb4153e6cbff6742ed7ad04c22e3de0024"
},
"downloads": -1,
"filename": "forked_cli-0.0.1.tar.gz",
"has_sig": false,
"md5_digest": "a7cc0ae21609ccad8844de660c49bbbc",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 47352,
"upload_time": "2025-10-21T22:42:15",
"upload_time_iso_8601": "2025-10-21T22:42:15.979109Z",
"url": "https://files.pythonhosted.org/packages/88/86/147b573b818587f6aa7d39536946787ad25e47f3dddd15cc37662f21fb1e/forked_cli-0.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-21 22:42:15",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Spenquatch",
"github_project": "fork#readme",
"github_not_found": true,
"lcname": "forked-cli"
}