patchloom 0.25.0

Structured file editing library and CLI for AI agents: parser-backed JSON/YAML/TOML edits, AST-aware code operations, multi-file batching, markdown operations, and MCP server
Documentation
//! Hand-written **workflow examples** and AST CLI shapes.
//!
//! Shell recipes for common agent tasks; not schema inventory.

/// CLI workflow examples and (when enabled) AST-aware operation examples.
pub(crate) fn append_workflows(
    out: &mut String,
    show_cli: bool,
    show_linux: bool,
    _show_windows: bool,
) {
    // Workflow examples (CLI only)
    if show_cli {
        out.push_str("## Workflow examples\n\n");

        out.push_str(
            "### Rename a function across a codebase\n\n\
             ```bash\n\
             # Prefer AST-aware rename for code identifiers (skips strings/comments)\n\
             patchloom ast rename src/ --old old_function_name --new new_function_name --apply\n\n\
             # Fallback: text-based workflow when AST mode is unavailable\n\
             patchloom search --count \"old_function_name\" src/\n\
             patchloom replace \"old_function_name\" --new \"new_function_name\" src/ --apply\n\
             ```\n\n\
             Default: `replace --fuzzy` with a misspelled `old` that is **not** in the file \
             refuses the write (#1758). Opt in only with `--allow-absent-old` for deliberate \
             approximate recovery, then check `matched_text` (#1736).\n\n",
        );

        out.push_str(
            "### Insert a line after an anchor\n\n\
             ```bash\n\
             # OLD is the preserved anchor; payload goes on --insert-after (not positional NEW)\n\
             patchloom replace 'use std::io;' --insert-after 'use std::fs;' src/main.rs --apply\n\
             # Same idea before a match:\n\
             patchloom replace 'fn main() {' --insert-before '// entry point' src/main.rs --apply\n\
             ```\n\n\
             Whole-line anchors and line-like payloads get a separating newline by default (#1885).\n\n",
        );

        out.push_str(
            "### Delete lines matching a pattern\n\n\
             ```bash\n\
             # Delete entire lines containing a pattern; collapse consecutive blanks\n\
             patchloom replace 'dbg!' --whole-line --new '' src/ --collapse-blanks --apply\n\n\
             # Restrict to a line range (e.g. implementation only, skip tests)\n\
             patchloom replace 'TODO' --whole-line --range 10:200 --new '' notes.md --apply\n\n\
             # Rewrite shell invocable tokens only (not uv pip / pipenv):\n\
             patchloom replace pip --new uv install.sh --command-position --require-change --apply\n\
             patchloom replace 'fn proccess_data' --new 'fn process_data' src/ --fuzzy --allow-absent-old --apply\n\
             patchloom replace 'fn proccess_data' --new 'fn process_data' src/ --fuzzy --allow-absent-old --min-fuzzy-score 0.80 --apply\n\
             ```\n\n",
        );

        out.push_str(
            "### Edit a CI workflow\n\n\
             ```bash\n\
             # Set a value in a YAML workflow by selector path (preserves comments and formatting)\n\
             patchloom doc set .github/workflows/ci.yml jobs.test.timeout-minutes 30 --apply\n\
             ```\n\n",
        );

        out.push_str(
            "### Stale patch recovery via three-way merge\n\n\
             ```bash\n\
             # Apply a patch using three-way merge when context has drifted\n\
             patchloom patch apply changes.patch --on-stale merge --apply\n\
             \n\
             # Or use the merge subcommand directly\n\
             patchloom patch merge changes.patch --apply\n\
             \n\
             # If merge produces conflicts, allow them to be written as markers\n\
             # WARNING: never commit files containing conflict markers\n\
             patchloom patch merge changes.patch --apply --allow-conflicts\n\
             \n\
             # Diff from stdin (--stdin or a bare '-' path both work)\n\
             cat changes.patch | patchloom patch apply --stdin --apply\n\
             cat changes.patch | patchloom patch apply - --apply\n\
             ```\n\n",
        );

        if show_linux {
            out.push_str(
                "```bash\n\
                 # Same via transaction plan (JSON):\n\
                 patchloom tx - --apply <<'EOF'\n\
                 {\"version\": 1, \"operations\": [\n\
                   {\"op\": \"patch.apply\", \"diff\": \"...\", \"on_stale\": \"merge\", \"allow_conflicts\": true}\n\
                 ]}\n\
                 EOF\n\
                 ```\n\n",
            );
        }

        if show_linux {
            out.push_str(
                "### Bump a version across config files\n\n\
                 ```bash\n\
                 patchloom batch --apply <<'EOF'\n\
                 doc.set package.json version \"2.0.0\"\n\
                 doc.set Cargo.toml package.version \"2.0.0\"\n\
                 replace README.md \"1.0.0\" \"2.0.0\"\n\
                 md.upsert_bullet CHANGELOG.md \"## [2.0.0]\" \"- Initial 2.0 release\"\n\
                 EOF\n\
                 ```\n\n",
            );

            out.push_str("### Multi-file refactoring with a transaction\n\n\
                 ```bash\n\
                 patchloom tx - --apply <<'EOF'\n\
                 {\"version\": 1, \"operations\": [\n\
                   {\"op\": \"replace\", \"path\": \"src/config.rs\", \"old\": \"old_default\", \"new\": \"new_default\"},\n\
                   {\"op\": \"doc.set\", \"path\": \"config.toml\", \"selector\": \"default_value\", \"value\": \"new_default\"},\n\
                   {\"op\": \"md.replace_section\", \"path\": \"docs/config.md\", \"heading\": \"## Defaults\",\n\
                    \"content\": \"The default value is now `new_default`.\\n\"}\n\
                 ]}\n\
                 EOF\n\
                 ```\n\n\
                 All operations succeed atomically or roll back together.\n\n\
                 Plan/MCP `replace` accepts library flags (default false):\n\
                 - `require_change`: fail when the pattern matches zero times (agent fail-closed).\n\
                 - `command_position`: rewrite only shell invocable tokens (`sudo`/`timeout`/`flock`/`runuser`/`setsid`/`run0`/`gosu`/`su-exec`/`tini`/`dumb-init`/`unshare`/`nsenter`/`taskset`/`systemd-run`/`firejail`/`busybox`/`chpst`/`softlimit`/`envdir`/`setlock` wrappers yes; `uv pip` no).\n\
                 - `fuzzy`: similarity fallback when exact match fails (also with before_context/after_context).\n\
                 Example: `{\"op\":\"replace\",\"path\":\"install.sh\",\"old\":\"pip\",\"new\":\"uv\",\
                 \"command_position\":true,\"require_change\":true}`\n\
                 Successful plan/tx and `batch_replace` JSON includes `match_mode` (`exact`/`fuzzy`/`anchored`), optional `matched_text` for fuzzy/anchored spans, and `match_count` on replace-backed changes plus worst-case aggregate mode and sum of counts when any replace matched (#1674, #1736). Partial soft-refuses list `refused[]` with reason (`exact_old_absent` / `below_min_fuzzy_score` / `no_matches`); do not treat ok/success alone as full multi-path coverage. Tx search with `max_results` may set `truncated: true` on search results when the matches array was capped.\n\n");
        }
    }

    // AST-aware operations (always shown when AST feature is enabled)
    #[cfg(feature = "ast")]
    if show_cli {
        out.push_str(
            "## AST-aware operations\n\n\
             Tree-sitter-backed operations that understand code structure (20 languages).\n\n\
             ```bash\n\
             # Rename an identifier across a file, skipping strings and comments\n\
             # (path first, then --old / --new)\n\
             patchloom ast rename src/lib.rs --old OldName --new NewName --apply\n\n\
             # Replace text only within a specific function body\n\
             # (PATH then SYMBOL are positionals — no --symbol flag; #1841)\n\
             patchloom ast replace src/config.rs default_timeout --old 30 --new 60 --apply\n\n\
             # List all symbol definitions\n\
             patchloom ast list src/lib.rs\n\n\
             # Find all references to a symbol\n\
             # (SYMBOL then PATH — order differs from replace/read; #1841)\n\
             patchloom ast refs my_function src/\n\
             # impact is also SYMBOL then PATH: ast impact my_function src/\n\
             ```\n\n\
             **AST CLI arg order:** `rename`/`replace`/`read`/`validate` use path-first; \
`refs`/`impact` use symbol-first. There is no `--symbol` or `--name` flag on these commands.\n\n\
             AST rename, replace, and rewrite_signature can also be used in batch and tx plans:\n\n\
             ```bash\n\
             # In a batch file (path, then old, then new — same names as CLI flags):\n\
             ast.rename src/lib.rs OldStruct NewStruct\n\
             ast.replace src/config.rs default_timeout \"30\" \"60\"\n\
             # path old parameters [return_type]:\n\
             ast.rewrite_signature src/lib.rs process \"(x: u64)\" \"-> u64\"\n\
             ```\n\n",
        );
    }
}