# Milco AI

Deterministic, human-in-the-loop repo automation. Hybrid approach: rule-based tasks (map-gen) run without an LLM; LLM-powered tasks (doc-gen) use a pluggable provider (Ollama, OpenAI). Local-first, Python-first, Windows-compatible.

## Quick Start (Windows)

```
python -m venv .venv
.venv\Scripts\activate
pip install -e .
milco init
milco run --contract TASK_CONTRACT.md
```

To apply changes: `milco run --contract TASK_CONTRACT.md --apply`

**Reproducible install (from repo):** From repo root, `pip install -r requirements.txt` then `pip install -e .`. Regenerate the lockfile with `pip-compile requirements.in -o requirements.txt` (requires `pip-tools`).

**From PyPI (when published):** `pip install milco`. Optional extras: `pip install milco[format]`, `milco[lint]`, `milco[test]`.

**Publishing to PyPI:** (1) Create a PyPI API token at https://pypi.org/manage/account/token/. (2) **Option A — GitHub:** Settings → Secrets → add `PYPI_API_TOKEN`, then create a GitHub Release from a version tag; the "Publish to PyPI" workflow uploads to PyPI. (3) **Option B — Local:** From repo root run `$env:PYPI_API_TOKEN = "pypi-..."; .\scripts\publish-pypi.ps1` (builds and uploads with twine).

**Tip:** For the gate to pass in apply mode, commit or stash first so the repo is not dirty (repo_sanity gate).

Each run uses a task type specified in the contract. Add `## Task Type` to your contract:

```markdown
## Task Type

map-gen
```

| Task | LLM | Description |
|------|-----|-------------|
| `map-gen` | No | Generate repo knowledge index (map.json) |
| `doc-gen` | Yes | Generate/update README.md from repo context |
| `format` | No | Run ruff format and produce patches.diff |
| `lint` | No | Run ruff check and produce patches.diff (fix diff or report) |
| `scaffold` | No | Create file(s) from templates (see ## Scaffold below) |

Run `milco --list-tasks` to see available types. The `format` and `lint` tasks require [ruff](https://docs.astral.sh/ruff/): `pip install ruff` or `pip install milco[format]` / `pip install milco[lint]`.

### Scaffold task

For task type `scaffold`, add a `## Scaffold` section to your contract with `template:`, `target:`, and any template variables (e.g. `module_name`, `docstring`). Run `milco --list-templates` to see template names. For `package_init`, use `exports: foo, bar` to set `__all__`. For `script`, use `module_name`, `description` (or `docstring`) for a minimal argparse CLI.

```markdown
## Task Type

scaffold

## Scaffold

template: python_module
target: src/mymodule.py
module_name: mymodule
docstring: My new module.
```

## LLM Configuration

For tasks that need an LLM (e.g. `doc-gen`), create `milco.toml` in the repo root:

```toml
[llm]
provider = "ollama"
model = "llama3"
# base_url = "http://localhost:11434"
```

For OpenAI-compatible APIs:

```toml
[llm]
provider = "openai"
model = "gpt-4o"

[llm.openai]
api_key_env = "OPENAI_API_KEY"
```

See `milco.toml.example` for a full template. Run `milco init` to generate starter config.

## CLI Commands

```
milco --version
milco --list-tasks
milco --list-templates
milco init
milco init --task-type doc-gen
milco audit --contract TASK_CONTRACT.md
milco fix --contract TASK_CONTRACT.md
milco gate --contract TASK_CONTRACT.md
milco run --contract TASK_CONTRACT.md
milco run --contract TASK_CONTRACT.md --apply
milco history
milco history --last 5 --failed --json
milco show
milco show <run_id>
milco show <run_id> --json
```

### Global Flags

| Flag | Description |
|------|-------------|
| `--apply` | Enable apply mode. Requires `CONFIRM APPLY` in the contract file. |
| `--run-id <id>` | Reuse an existing run ID instead of generating a new one. |
| `--contract <path>` | Path to `TASK_CONTRACT.md`. |

## Workflow

1. **audit** — Scans the repo, validates the contract, checks task type and LLM reachability, produces `evidence.md` and a plan in `summary.md`.
2. **fix** — Delegates to the task registry. Generates `patches.diff` (unified diff). Only applies if `--apply` AND `CONFIRM APPLY` is in the contract.
3. **gate** — Runs three gates (pytest, repo sanity, policy) and writes `gate_report.json`. In apply mode, repo_sanity fails if the repo was dirty before the run—commit or stash first.
4. **run** — Executes audit, fix, gate in sequence.
5. **init** — Creates starter `TASK_CONTRACT.md` and `milco.toml`.
6. **history** — Read-only. Shows past runs from `runs/index.jsonl`.
7. **show** — Read-only. Inspect a specific run.

### milco history

```
milco history              # last 20 runs, table format
milco history --last 5     # last 5 runs
milco history --failed     # only exit_code == 1
milco history --json       # output as JSON lines
```

### milco show

```
milco show                 # list available run IDs
milco show <run_id>        # human-readable run detail
milco show <run_id> --json # raw manifest.json output
```

## Run Artifacts

Every run writes to `runs/<run_id>/`:

| File | Purpose |
|------|---------|
| `task_contract.md` | Copy of the validated contract |
| `evidence.md` | Repo scan results |
| `patches.diff` | Unified diff for proposed changes |
| `gate_report.json` | Gate pass/fail results |
| `summary.md` | Run summary and plan |
| `manifest.json` | Deterministic run manifest with hashes, repo state, outcome |

## Dry-Run vs Apply

By default, Milco runs in **dry-run** mode:
- Artifacts are written (they're the audit trail).
- No user payload files (map.json, README.md, etc.) are written.
- No commands (e.g. pytest) are executed via `exec_tools`.

To apply changes, **both** conditions must be met:
1. Pass `--apply` on the command line.
2. Include `CONFIRM APPLY` in your `TASK_CONTRACT.md`.

## Exit Codes

| Command | Exit 0 | Exit 1 |
|---------|--------|--------|
| `milco audit` | Audit succeeded | Contract validation error |
| `milco fix` | Fix succeeded (dry-run or apply) | Evidence missing or other error |
| `milco gate` | `overall_status` is `PASS` | `overall_status` is `FAIL` |
| `milco run` | Full pipeline PASS | Any FAIL, or REFUSED (--apply without CONFIRM APPLY, unknown task type, LLM not configured) |

## Task Contract

Include `## Task Type` (defaults to `map-gen` if omitted). Required sections: Goal, Scope, Out of scope, Success Criteria, Constraints, Approvals required, Notes.

Run `milco init` to generate a starter contract.

## Troubleshooting

| Issue | What to do |
|-------|------------|
| Run REFUSED (any reason) | Read the "What to do next" lines in the output; fix contract or config and re-run. |
| Gate fails with "Repo is dirty before applying patch" | Commit or stash changes, then run `milco run --apply` again. |
| "Task 'doc-gen' requires an LLM but none is configured" | Add `milco.toml` with an `[llm]` section (e.g. run `milco init`), set `provider` and `model`, and ensure Ollama or your API is running. |
| "Unknown task type" | Check `## Task Type` in your contract. Run `milco --list-tasks` to see valid names. |
| "ruff not found" for format/lint | Install ruff: `pip install ruff` or `pip install milco[format]` / `milco[lint]`. |

## Project Structure

```
milco/
  cli.py              # argparse CLI
  core/
    run_context.py    # Run ID, run dir, mode, task_type, llm
    artifacts.py      # Write the 5 required artifacts
    contract.py       # Load, validate, parse task type
    manifest.py       # Build + write manifest.json
    run_index.py      # Append-only runs/index.jsonl
    history.py        # Read-only index reader
    show.py           # Read-only run detail viewer
  audit/
    auditor.py        # Produce evidence.md + plan
  fix/
    fixer.py         # Thin wrapper → task registry
  gate/
    gates.py         # pytest, repo sanity, policy gates
  llm/
    base.py          # LLMProvider interface
    ollama.py        # Ollama provider
    openai.py       # OpenAI-compatible provider
    config.py        # milco.toml loader
  policy/
    policy.py        # Pure-function policy checks
  tasks/
    base.py          # FixResult, TaskBase
    registry.py      # @register_task, get_task, list_tasks
    map_gen.py       # map-gen task
    doc_gen.py       # doc-gen task (LLM-powered)
  tools/
    fs_tools.py      # Safe repo scanning
    git_tools.py     # Git status/diff
    exec_tools.py    # Allowlisted command runner
```

## How to Run Tomorrow

```
cd C:\Milco.AI
.venv\Scripts\activate
pytest -q
milco run --contract TASK_CONTRACT.md
```

To apply changes:

```
milco run --contract TASK_CONTRACT.md --apply
```

To push to a remote (after creating the repo on GitHub etc.): `git remote add origin <url>`, then `git push -u origin master` and `git push origin <tag>` (e.g. `v0.4.4`).
