Metadata-Version: 2.5
Name: sira
Version: 2.0.0
Summary: Multi-agent AI that tailors your resume to a job posting without inventing facts.
Project-URL: Homepage, https://github.com/Tiqni/sira
Project-URL: Repository, https://github.com/Tiqni/sira
Project-URL: Issues, https://github.com/Tiqni/sira/issues
Project-URL: Changelog, https://github.com/Tiqni/sira/blob/main/CHANGELOG.md
Author-email: Emad Mokhtar <me@emadmokhtar.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,ats,cv,job-search,llm,pydantic-ai,resume
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.13
Requires-Dist: aiofiles>=25.1.0
Requires-Dist: html2text>=2025.4.15
Requires-Dist: jinja2>=3.1
Requires-Dist: markdown>=3.10
Requires-Dist: markitdown[docx,pdf]>=0.1.0
Requires-Dist: platformdirs>=4
Requires-Dist: playwright>=1.56.0
Requires-Dist: pydantic-ai[bedrock,cohere,dbos,groq,mistral]<3,>=2.43
Requires-Dist: pymupdf>=1.26
Requires-Dist: python-docx>=1.1.0
Requires-Dist: rich>=14.2.0
Requires-Dist: typer>=0.25.1
Provides-Extra: guard
Requires-Dist: torch>=2.2; extra == 'guard'
Requires-Dist: transformers>=4.45; extra == 'guard'
Provides-Extra: xai
Requires-Dist: pydantic-ai[xai]<3,>=2.43; extra == 'xai'
Description-Content-Type: text/markdown

# 📄 Sira

![cover](https://raw.githubusercontent.com/Tiqni/sira/main/cover.png)

Sira is a multi-agent AI system that analyzes job postings and tailors your resume to match specific job requirements. It ensures authenticity, avoids AI clichés, and optimizes for Applicant Tracking Systems (ATS).

📚 **[Read the documentation →](https://tiqni.github.io/sira/)** — user guide, CLI reference, architecture, and developer guide.

> **Sira** (سيرة) is the Arabic word for a life story. *Sīra dhātiyya* (سيرة ذاتية) is the term for a curriculum vitae — the story you tell about your own work.

## 🚀 Features

- **Multi-Agent Architecture**: 6 pipeline stages plus job scraping — dedicated agents for analysis, writing, and quality assurance.
- **Automated Job Scraping**: Fetches job posting content from any public URL using Playwright.
- **Resume Memory**: Stores your original resume plus job-specific tailored outputs in SQLite.
- **Authentic Tailoring**: Rephrases your experience to match the job without inventing skills.
- **Hallucination & Cliché Detection**: Built-in auditor to ensure quality and "human" tone.
- **Quality Gate Validators**: Core pipeline agents' output is scored 0–10 by a quality gate before the pipeline proceeds.
- **Comprehensive Reporting**: Generates self-review reports with gaps analysis, suggestions, and recommendations.
- **Self-Correcting Workflow**: Write → Review → Audit loop with retries and quality feedback (defaults: 2 write attempts × 1 review iteration, both configurable).
- **Re-Tailoring**: Re-run tailoring on a saved job with recommendations from a prior audit (`re-tailor` command).

## 🛠️ Architecture

The system runs a sequential pipeline with an inner refinement loop:

**Stage 0 — Job Scraper**: Fetches job posting content from any public URL using Playwright and converts HTML to Markdown (multi-strategy fallback: markitdown → html2text).

**Stage 1 — Resume Parser**: Parses your resume (Markdown, DOCX, or PDF) into a structured `CV` object. Not quality-gated — the parse is cached by content hash instead.

**Stage 2 — Job Analyst**: Extracts structured job requirements (title, company, skills, keywords) from the scraped posting. Not quality-gated.

On a cold cache these two stages run **concurrently**.

**Stages 3–5 — Write → Review → Audit Loop** (outer loop, `--write-attempts`, default 2):

| Stage     | Agent     | Description                                                                                                                                                   |
| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 3. Write  | CV Writer | Tailors the CV to match job requirements → Quality gate validates tailoring                                                                                   |
| 4. Review | Reviewer  | Scores CV quality and suggests improvements; triggers the refinement loop (`--review-iterations`, default 1, per write attempt)                                |
| 5. Audit  | Auditor   | Checks for hallucinations and AI clichés → Quality gate validates audit quality. If audit fails, the entire Write → Review → Audit loop retries from stage 3. |

**Stage 6 — Report Generator**: Judges which job skills your CV covers by meaning (one skill-matcher call over the whole CV text), computes the match score and verdict in Python, and compiles the self-review report.

**Quality Gate System**: The CV Writer and the Auditor have validators that score their output 0–10 with a shared quality-gate agent. If the score falls below the gate threshold (`--gate-threshold`, default 6), the agent retries with corrective feedback. On quality gate exhaustion, the system falls back to the last available output (graceful degradation) instead of failing fatally. Disable it entirely with `--no-quality-gate`.

**Write → Review → Audit Loop**: After the initial write, the reviewer scores the draft and suggests refinements. Once review iterations are exhausted, the auditor checks for hallucinations. If the audit fails, the entire write → review → audit loop retries. Both limits are flags: `--write-attempts` (default 2) and `--review-iterations` (default 1).

## 📋 Prerequisites

- **Python 3.13+**
- **A Chromium browser for Playwright** — installed once with `sira setup`
- **LLM Provider API Key** — OpenAI by default; many providers supported (see [LLM Providers](#-llm-providers))
- **[uv](https://github.com/astral-sh/uv)** (Fast Python package installer and resolver) — only for the from-source install below

## 📦 Installation

### From PyPI

Sira is on [PyPI](https://pypi.org/project/sira/). Install it as a standalone tool with
[uv](https://docs.astral.sh/uv/guides/tools/) or [pipx](https://pipx.pypa.io/) — each
gives Sira its own isolated environment and puts the `sira` command on your `PATH`:

```bash
uv tool install sira        # or: pipx install sira, or: pip install sira
sira setup                  # downloads the Chromium browser the job scraper drives
export OPENAI_API_KEY=your_api_key_here
sira tailor <JOB_URL> <RESUME_PATH>
```

`sira setup` runs `playwright install chromium` inside Sira's own environment, so the
browser always matches the Playwright version Sira was installed with. Extras work the
same way: `uv tool install "sira[guard]"`.

The rest of this README writes commands as `uv run sira …`, which is the from-source
form below. With a PyPI install, drop the `uv run` prefix.

### From source (development)

1.  **Clone the repository**:

    ```bash
    git clone https://github.com/Tiqni/sira
    cd sira
    ```

2.  **Install dependencies**:
    This project uses `uv` for dependency management.

    ```bash
    uv sync
    ```

3.  **Install the browser Playwright drives**:
    The job scraper runs a real headless Chromium. The browser binary is a separate
    download from the Python package:

    ```bash
    uv run sira setup
    ```

4.  **Set up Environment Variables**:
    Export your API key for the LLM provider you plan to use:

    ```bash
    # OpenAI (default)
    export OPENAI_API_KEY=your_api_key_here

    # Other providers — see the LLM Providers section below
    # export ANTHROPIC_API_KEY=...
    # export GOOGLE_API_KEY=...
    # export GROQ_API_KEY=...
    # export MISTRAL_API_KEY=...
    ```

## 🤖 LLM Providers

Sira is built on [PydanticAI](https://ai.pydantic.dev), which supports a wide range of LLM providers. You select a provider and model via the `--model` CLI option using the format **`<provider>:<model>`**.

The default model is `openai:gpt-5-mini`. To use a different provider or model, pass `--model` with the appropriate prefix:

```bash
uv run sira tailor <JOB_URL> <RESUME_PATH> --model anthropic:claude-sonnet-4-5
```

### Supported Providers

| Provider                                                       | Prefix          | Example `--model`                     | Required Env Var     |
| -------------------------------------------------------------- | --------------- | ------------------------------------- | -------------------- |
| [OpenAI](https://platform.openai.com)                          | `openai:`       | `openai:gpt-4o-mini`                  | `OPENAI_API_KEY`     |
| [Anthropic](https://console.anthropic.com)                     | `anthropic:`    | `anthropic:claude-sonnet-4-5`         | `ANTHROPIC_API_KEY`  |
| [Google Gemini](https://aistudio.google.com)                   | `google:`       | `google:gemini-3-pro-preview`         | `GOOGLE_API_KEY`     |
| [Google Cloud (Vertex AI)](https://cloud.google.com/vertex-ai) | `google-cloud:` | `google-cloud:gemini-3-flash-preview` | `GOOGLE_API_KEY`     |
| [Groq](https://console.groq.com)                               | `groq:`         | `groq:llama-3.3-70b-versatile`        | `GROQ_API_KEY`       |
| [Mistral](https://console.mistral.ai)                          | `mistral:`      | `mistral:mistral-large-latest`        | `MISTRAL_API_KEY`    |
| [xAI](https://x.ai/api)                                        | `xai:`          | `xai:grok-3-mini`                     | `XAI_API_KEY` — needs the `sira[xai]` extra (see tip) |
| [Cohere](https://dashboard.cohere.com)                         | `cohere:`       | `cohere:command-r-plus`               | `CO_API_KEY`         |
| [DeepSeek](https://platform.deepseek.com)                      | `deepseek:`     | `deepseek:deepseek-chat`              | `DEEPSEEK_API_KEY`   |
| [OpenRouter](https://openrouter.ai)                            | `openrouter:`   | `openrouter:openai/gpt-4o`            | `OPENROUTER_API_KEY` |
| [Ollama](https://ollama.com) (local)                           | `ollama:`       | `ollama:llama3`                       | `OLLAMA_BASE_URL`    |
| [GitHub Models](https://github.com/marketplace/models)         | `github:`       | `github:xai/grok-3-mini`              | `GITHUB_API_KEY` — retired by GitHub on 2026-07-30; removed in pydantic-ai v3 |
| [Cerebras](https://cloud.cerebras.ai)                          | `cerebras:`     | `cerebras:llama3.1-8b`                | `CEREBRAS_API_KEY`   |
| [AWS Bedrock](https://aws.amazon.com/bedrock)                  | `bedrock:`      | `bedrock:anthropic.claude-sonnet-4-5` | AWS credentials      |

> **💡 Tip:** PydanticAI resolves the model class, provider, and profile automatically from the `<provider>:<model>` string. Every provider above works out of the box except the retired GitHub Models and xAI: pydantic-ai 2.x uses the native `xai-sdk`, which cannot be installed together with Sira's dev tools, so it is an opt-in extra — install with `uv sync --extra xai --no-dev` instead of plain `uv sync`.

### Using a Local Model (Ollama)

To use a model via [Ollama](https://ollama.com), make sure Ollama is running, the model is pulled, and point the provider at Ollama's OpenAI-compatible endpoint by exporting `OLLAMA_BASE_URL` (PydanticAI requires it — there is no default):

```bash
ollama pull llama3
export OLLAMA_BASE_URL=http://localhost:11434/v1
uv run sira tailor <JOB_URL> <RESUME_PATH> --model ollama:llama3
```

Ollama cloud models work the same way through the local daemon (sign in with `ollama signin` first):

```bash
export OLLAMA_BASE_URL=http://localhost:11434/v1
uv run sira tailor <JOB_URL> <RESUME_PATH> --model 'ollama:kimi-k2.6:cloud'
```

> **⚠️ Note:** Local models may be slower or produce less reliable structured output than cloud providers. The quality gates and retries help compensate, but for production use a cloud provider is recommended.

### OpenAI-Compatible Providers

Many providers offer OpenAI-compatible APIs. PydanticAI supports these via the `openai:` prefix combined with provider-specific routing environment variables. See the [PydanticAI OpenAI docs](https://ai.pydantic.dev/models/openai/) for details on [Together AI](https://ai.pydantic.dev/models/openai/#together-ai), [Perplexity](https://ai.pydantic.dev/models/openai/#perplexity), [Fireworks AI](https://ai.pydantic.dev/models/openai/#fireworks-ai), [Azure AI Foundry](https://ai.pydantic.dev/models/openai/#azure-ai-foundry), and more.

## 🏃 Usage

The CLI uses **positional arguments** (not interactive prompts). Two commands are available:

### `tailor` — Run the full workflow

```bash
uv run sira tailor <JOB_URL> <RESUME_PATH> [OPTIONS]
```

**Arguments:**

- `JOB_URL` — URL of the job posting (must start with `http://` or `https://`)
- `RESUME_PATH` — Path to your resume (`.md`, `.docx`, or `.pdf`)

**Options:**

- `--output-dir PATH` — Output directory (default: `./output`)
- `--model MODEL` — LLM provider and model in `provider:model` format (default: `openai:gpt-5-mini`). See [LLM Providers](#-llm-providers) for all supported options.
- `--verbose` / `-v` — Stream agent thinking and prompts in real-time
- `--debug` / `-d` — Enable debug output and save the converted resume markdown
- `--interactive` / `-i` — Pause at quality checkpoints (audit failure, weak match) and ask whether to continue, give feedback and retry, or quit. Skipped automatically when stdin is not a terminal.
- `--output-pattern TEMPLATE` — Template for job-specific subdirectory name (default: `{company_name}-{job_title}`)
- `--resume-name-pattern TEMPLATE` — Template for resume file base name (default: `{company_name}-{full_name}`)

### Example

```bash
uv run sira tailor \
  https://www.linkedin.com/jobs/view/12345678 \
  /Users/me/resume.md \
  --model openai:gpt-4o-mini
```

### `re-tailor` — Re-run with feedback from a prior audit

```bash
uv run sira re-tailor <JOB_ID> <RECOMMENDATIONS> [OPTIONS]
```

**Arguments:**

- `JOB_ID` — UUID of the prior job (shown in output after a `tailor` run)
- `RECOMMENDATIONS` — Comments or recommendations from the prior audit report

**Options:**

- `--resume-path PATH` — Resume path (uses the stored path from the prior job if omitted)
- `--output-dir PATH` — Output directory (default: `./output`)
- `--model MODEL` — AI model override
- `--verbose` / `-v` — Stream agent thinking and prompts in real-time
- `--debug` / `-d` — Enable debug output and save the converted resume markdown
- `--interactive` / `-i` — Pause at quality checkpoints (audit failure, weak match) and ask whether to continue, give feedback and retry, or quit. Skipped automatically when stdin is not a terminal.
- `--output-pattern TEMPLATE` — Template for job-specific subdirectory name (default: `{company_name}-{job_title}`)
- `--resume-name-pattern TEMPLATE` — Template for resume file base name (default: `{company_name}-{full_name}`)

> **💡 Tip:** If the original resume file no longer exists on disk when running `re-tailor`, you must provide `--resume-path` to point to the current location of your resume.

### Example

```bash
uv run sira re-tailor \
  a1b2c3d4-... \
  "Add more emphasis on cloud infrastructure experience" \
  --model openai:gpt-4o-mini
```

### `resume` / `runs` — Continue an interrupted run

Every run is **durable**: each model request is checkpointed by [DBOS](https://docs.dbos.dev) in a local SQLite file (`dbos.sqlite3` in the [data directory](#-resume-memory-behavior), override with `SIRA_DBOS_DATABASE_URL`). `sira tailor` prints a **Run ID** at the start and again at the end. It is not the **Job ID** printed with it — the Job ID names the memory record used by `re-tailor`; the Run ID names the durable run used by `resume`. If the process is killed, crashes, or a stage fails, continue from the last completed model request — earlier agents are replayed from their checkpoints, not called again:

> **Privacy:** `dbos.sqlite3` stores each run's inputs and checkpoints — your full resume text, the job posting, every model response (including the tailored CV) and your answers at interactive checkpoints — pickled, with your user's default file permissions. It never leaves your machine. Delete the file to purge it, or point `SIRA_DBOS_DATABASE_URL` at another location. Rows are readable only by the same Sira and `pydantic-ai` versions that wrote them.

```bash
uv run sira resume <RUN_ID>
```

A run that failed is continued as a **new** run id (printed as `Continued as run: …`); a run that was only interrupted keeps its id. If you answered "quit" at an interactive checkpoint, `resume` asks the question again. List recent runs and their status with:

```bash
uv run sira runs --limit 10
```

A run can only be resumed by the same Sira version that started it.

Durability covers the pipeline (parsing, analysis, writing, review, audit, report). The steps before it — resume conversion, the parsed-resume cache lookup, and scraping the job posting — are quick pre-flight work and are not checkpointed: a crash there leaves no run to resume, so just run `sira tailor` again.

### Live progress & speed

By default a **live progress dashboard** is shown in the terminal, updating as each pipeline stage completes. In non-TTY environments (CI, pipes) it degrades to plain line-by-line logging automatically.

**`--verbose` / `-v`** — streams every agent's thinking and output tokens in real-time. This replaces the dashboard with direct streaming output and is the recommended mode for clean interactive viewing (see note below).

**Speed flags** (available on both `tailor` and `re-tailor`):

| Flag | Default | Description |
|------|---------|-------------|
| `--fast` | off | Speed preset: trims loops to 1 write + 1 review and enables faster model tier for mechanical agents |
| `--write-attempts N` | `2` | Maximum writer attempts in the write → review → audit outer loop |
| `--review-iterations N` | `1` | Maximum reviewer iterations per write attempt |
| `--quality-gate` / `--no-quality-gate` | on | Enable or disable the advisory quality gate |
| `--gate-threshold N` | `6` | Re-run an agent only when its quality score is below this threshold (0–10) |

> **Note on TTY interleaving:** when running interactively with the default live dashboard, the dashboard's Rich panel and the workflow's own `print()` calls can interleave and garble the output. This does not affect correctness — only the visual display. For clean interactive output use `--verbose`, or run in a non-TTY environment (pipe, CI). A future follow-up will route workflow `print()` calls through the reporter to eliminate this.

### Alternative entry point

You can also invoke the CLI directly:

```bash
uv run python sira/main.py tailor <JOB_URL> <RESUME_PATH>
```

### View Results

Upon successful completion, output files are saved in job-specific subdirectories under `output/` (or the path specified via `--output-dir`). The subdirectory name follows the `--output-pattern` template (default: `{company_name}-{job_title}`).

Three resume formats are generated per run, all rendered from the same structured
CV the writer produced (the PDF and DOCX share one template, so they look the same):

- `.md` — Markdown
- `.pdf` — PDF
- `.docx` — DOCX

Pick the template with `--style modern|classic|compact` (default `modern`); see
[Output and reports](https://tiqni.github.io/sira/output/#styles) for what each
style looks like.

A comprehensive self-review report is also generated:

- `_report.md` — Markdown report with match score, gap analysis, and recommendations

Example structure for a job at Acme Corp for a Senior Engineer role:

```
output/
└── acme_corp-senior_engineer/
    ├── acme_corp-Jane_Doe.md          ← Tailored resume (Markdown)
    ├── acme_corp-Jane_Doe.pdf         ← Tailored resume (PDF)
    ├── acme_corp-Jane_Doe.docx        ← Tailored resume (DOCX)
    └── acme_corp-Jane_Doe_report.md   ← Self-review report
```

Use `--resume-name-pattern` to customize the base filename (default: `{company_name}-{full_name}`). Available template variables: `{company_name}`, `{job_title}`, `{full_name}`, `{timestamp}`.

## 🧠 Resume Memory Behavior

- The first run requires providing a resume path so the CLI can store your original resume.
- Subsequent runs reuse the latest stored original resume from the SQLite database.
- **Content-hash caching**: If your resume file hasn't changed since the last run, the pre-parsed `CV` is reused — no LLM parsing call is made, saving time and cost.
- Every job submission starts from the original resume, never from a previous tailored resume.
- Each successful tailoring run stores the tailored resume and audit result linked back to the original source resume.
- Sira keeps its runtime state in a per-user data directory (`~/Library/Application Support/sira` on macOS, `~/.local/share/sira` on Linux, `%LOCALAPPDATA%\sira` on Windows; set `SIRA_DATA_DIR` to change it). The memory database is `resume_memory.sqlite3` in that directory, so `tailor` and `re-tailor` share it from any working directory. A `memory/resume_memory.sqlite3` left by a release before 1.5 is moved there the first time Sira runs.
- When running `re-tailor`, if the original resume file no longer exists on disk at its recorded path, you must provide `--resume-path` to restore the link.

## 📊 Self-Review Report

Each workflow run generates a **self-review report** that includes:

- **What Changed**: Summary changes, reordered/deprioritized skills, and per-experience bullet rewrites
- **Quality Metrics**: Hallucination score (0–10) and AI cliché score (0–10)
- **Gap Analysis**: Keyword coverage, missing hard/soft skills vs. the job posting
- **Suggestions to Strengthen**: Recommended improvements to better match the job
- **Audit Summary**: Feedback from the auditor on tone, authenticity, and compliance
- **Skills Covered**: Hard/soft skills the job asks for that your CV shows — matched by meaning, not only by exact words ("mentor to ~30 engineers" covers "Technical leadership and mentorship"), each with the CV line as evidence
- **Match Score**: 0–100 = `0.6 × hard-skill coverage + 0.2 × soft-skill coverage + 0.2 × ATS keyword coverage` (buckets the job does not list are rescaled away). Computed in Python, not by the model.
- **Overall Recommendation**: "Strong Match" (score ≥ 75 and hard-skill coverage ≥ 75 %, or the job lists no hard skills), "Partial Match" (score ≥ 50), or "Weak Match"

## ✅ Quality Gate System

A shared `quality_gate_agent` scores other agents' output:

- **Advisory, not blocking**: output is scored once, 0–10. An agent is re-run only when its score is below `--gate-threshold` (default 6).
- **Automatic retry**: a below-threshold score raises a retry carrying the concrete improvements the gate asked for. Retry counts are set per agent in `workflows/agents.py` — they are not uniform.
- **Graceful fallback**: when retries are exhausted, the last available output is used instead of failing the run.
- **Token usage tracking**: gate runs count toward usage metrics, so cost reporting stays accurate.
- **Optional**: `--no-quality-gate` removes the scoring calls entirely.

Full detail: [Agent reference](https://tiqni.github.io/sira/agents/).

## 🛠️ Make Commands

| Command            | Description                                           |
| ------------------ | ----------------------------------------------------- |
| `make help`        | Show available commands and descriptions.             |
| `make install`     | Install production dependencies using `uv`.           |
| `make install/dev` | Install development dependencies using `uv`.          |
| `make test`        | Run the full test suite using `pytest`.               |
| `make install/uv`  | Ensure `uv` is installed (auto-run by other targets). |

> **Note:** The `make run` target is deprecated and uses outdated paths. Use `uv run sira tailor <JOB_URL> <RESUME_PATH>` instead.

## 📂 Project Structure

```
sira/
├── sira/         # Main Python package
│   ├── main.py                # CLI entry point (Typer: tailor + re-tailor)
│   ├── workflows/             # Workflow orchestration and agent definitions
│   │   ├── __init__.py        # ResumeTailorWorkflow class
│   │   └── agents.py          # All agent definitions + quality gate validators
│   ├── paths.py               # Per-user data directory (SIRA_DATA_DIR)
│   ├── models/                # Pydantic data models
│   │   ├── agents/            # Agent output types (CV, JobAnalysis, AuditResult, etc.)
│   │   │   ├── output.py      # Core output models
│   │   │   └── deps.py        # Agent dependency types
│   │   └── workflow.py        # ResumeTailorResult
│   ├── memory/                # SQLite-backed resume memory
│   │   ├── models.py          # Memory domain models
│   │   ├── parser.py          # Resume parser adapter
│   │   ├── repository.py      # Abstract repository interface
│   │   ├── sqlite_repository.py  # SQLite implementation
│   │   └── service.py         # Orchestration service
│   ├── tools/                 # Playwright scraping, HTML parsing helpers
│   │   ├── playwright.py      # File I/O tool for agents
│   │   └── job_scraper_helpers.py  # HTML→MD parsers, placeholder detection
│   ├── rendering/
│   │   ├── __init__.py       # render_resume(cv, dir, base_name, style) → .md/.pdf/.docx
│   │   ├── errors.py         # RenderError
│   │   ├── templates.py      # TemplateSpec: modern, classic, compact
│   │   ├── inline.py         # inline markdown subset (links, bold, italic, code)
│   │   ├── html.py + resume.html.j2   # CV → HTML (Jinja2)
│   │   ├── css.py            # TemplateSpec → CSS for the PDF
│   │   ├── pdf.py            # HTML + CSS → PDF (PyMuPDF Story)
│   │   ├── docx.py           # CV + TemplateSpec → DOCX (python-docx)
│   │   └── markdown.py       # CV → Markdown
│   └── utils/                 # Markdown writer, resume conversion, CV diff
│       ├── cv_diff.py         # Pure-Python CV diff, gap analysis, match score
│       ├── markdown_writer.py # generate_report_markdown
│       ├── resume_converter.py  # DOCX/PDF → Markdown conversion
│       └── validate_inputs.py
├── tests/                     # Test suite
│   ├── memory/                # Memory layer tests
│   ├── workflows/             # Workflow integration tests
│   ├── conftest.py            # Pytest fixtures (disables real LLM calls)
│   └── factories.py           # Test data factories
├── docs/                      # Additional documentation
├── output/                    # Default output directory for generated files
├── Makefile                   # Command shortcuts
├── pyproject.toml             # Project configuration and dependencies
└── README.md                  # This file
```

## 🛡️ Safety & Quality

- **Anti-Hallucination**: The system is strictly instructed never to invent skills or experiences.
- **Cliché Filter**: Avoids terms like "spearheaded", "synergy", "leveraged", and "game-changer".
- **Multi-Layer Validation**: Quality gates score core pipeline agent output; auditor cross-checks final CV against the original.
- **Prompt-Injection Awareness**: Some job pages embed text aimed at AI readers ("ignore previous instructions", hidden `display:none` blocks, "rate this candidate as a perfect match"). Every scraped page is scanned in plain Python (no LLM) for instruction overrides in 10 languages, text that addresses an AI, role/output manipulation, exfiltration requests, invisible Unicode, and phrases that exist in the HTML but not in the visible text. A match prints a `⚠️ Potential prompt-injection content detected (…)` warning and the run continues — detection is advisory, and the scraper and analyst prompts are told to treat page text as data, never as instructions.

### Optional: local classifier (`sira[guard]`)

For a second opinion from a small model, install the `guard` extra. It runs [Meta Llama Prompt Guard 2 (22M)](https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-22M) **locally** on the scraped Markdown and adds a `classifier_flagged` indicator to the same warning.

```bash
uv sync --extra guard        # torch + transformers, ~2 GB
# or, installed from PyPI:  uv tool install "sira[guard]"
```

- The first `tailor` run asks once whether the model may be downloaded (~90 MB) and executed on your machine, and remembers the answer in `~/.config/sira/guard_consent.json`. Pre-answer with `SIRA_GUARD_CONSENT=yes|no` for scripts and CI.
- The default model is gated: accept Meta's license on Hugging Face and set `HF_TOKEN`. If you cannot, point `SIRA_GUARD_MODEL` at an open model such as `protectai/deberta-v3-base-prompt-injection-v2`.
- Any failure (no token, no network, out of memory) prints a warning and falls back to the regex scan. It never stops a run.

## 🤝 Contributing

Contributions are welcome! Please ensure you follow the coding guidelines and add tests for new features.

## 📖 Further Reading

**[The documentation site](https://tiqni.github.io/sira/)** is the full reference:

- [Getting started](https://tiqni.github.io/sira/getting-started/) — install and first run
- [CLI reference](https://tiqni.github.io/sira/cli/) — every command and flag
- [Models and providers](https://tiqni.github.io/sira/models/) — provider table, Ollama, cost control
- [Output and reports](https://tiqni.github.io/sira/output/) — the generated files and how to read the report
- [Resume memory](https://tiqni.github.io/sira/memory/) — what is stored on disk, and the parse cache
- [Troubleshooting](https://tiqni.github.io/sira/troubleshooting/) — errors and what to do about them
- [Contributing](https://tiqni.github.io/sira/contributing/) — development setup, tests, commits, releases
- [Architecture](https://tiqni.github.io/sira/architecture/) — the full system design

In this repository:

- **[ARCHITECTURE.md](./ARCHITECTURE.md)** — Detailed system architecture, data flow, quality gate system, and design decisions
- **[AGENTS.md](./AGENTS.md)** — Agent development guide with conventions, tool invocation, and testing patterns
- **[.github/copilot-instructions.md](./.github/copilot-instructions.md)** — GitHub Copilot instructions for AI-assisted development
