This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer, or a future-me sees. If it is empty, they leave.
Two years later, the other bill comes due. The dependencies have moved, the build is deprecated, and the release notes are a wall. I do not need a generic summary. I need to know which lines in my repo are affected.
The obvious fix is "ask an AI to write it." That gives you fluent sentences like built a scalable platform used by thousands of users or reduced latency by 40%, which you then have to fact-check from memory, or defend in an interview. Fluent is not the same as true.
So I built Doxify, a local-first CLI where the model writes and the code checks. It scans a git repository, turns it into numbered evidence cards, asks a local Gemma to write only from those cards, and then a deterministic verifier decides which sentences survive. Whatever it can't prove becomes a question instead of a claim.
I almost skipped this challenge. My laptop has a weak CPU, and most AI demos I saw assumed a GPU. gemma3:1b, a model of about 806 MB, changed that. The constraint shaped the whole design: a small model, short prompts, and a verifier that carries the trust work.
It gives open-source developers, reviewers, bots, and fellow sloths their time back.
The model writes. The code checks.
TL;DR
- What: a CLI that compiles a git repo into a cited case study, an evidence-marked README, a list of upgrade breakages that touch your code, and a CI gate. One evidence engine, four outputs.
- The result that matters: on one real repo, Gemma wrote 47 claims. 13 were verified, 34 were flagged "needs evidence", 0 were rejected. Those 34 are the review queue, not a failure.
- Where it runs: an 8 GB, CPU-only laptop with
gemma3:1bthrough Ollama. No API key, no upload. Cloud mode exists but is opt-in and warns you first.- How it is checked: 74 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
What I Built
Doxify turns a finished repository into the things a tired developer actually needs:
| Surface | Question it answers | What it writes |
|---|---|---|
| Case study compiler | What did I build, and can I defend it? |
case-study.md, claims.json, defense.md
|
| README compiler | What does this repo do, and how do I run it? | An evidence-marked README.md
|
| Scout | Which upgrade actually touches my code? |
scout.md, scout.json, release notes, code matches |
| CI gate | Can I trust the docs in this PR? | A pass or fail from the same verifier |
The case study came first. It was the core of this build and it is still the deepest part: six sections, a claim ledger, and an interview defense pack. The README and Scout reuse the same evidence engine, so they are not side projects. The case study is the story. The README is what visitors read. Scout is what future-you runs.
Doxify is for the developer who just pushed the last commit and cannot face a blank page. It is for the developer whose best work is private or under NDA. It is for the maintainer who comes back to a project after two years of dependency drift. It is also for the open-source maintainer whose README is the first thing contributors and bots read.
It is not a portfolio site generator. It is a review tool. The useful output is not just a draft. It is the list of claims that did not survive verification.
The sloth test is simple: start it, walk away, and come back to a review queue.
README
doxify readme runs the same compile, then renders a README from verified and attested material.
Readers get clean Markdown. CI gets provenance:
<!-- doxify-evidence: E2 E4 E5 E6 E7 -->
<!-- doxify-attested: project description and positioning -->
Unresolved claims are left out of the visible page. The README builder also refuses to overwrite an existing README without --force, and it keeps a backup when it does.
if target.exists() and not force:
raise SessionError(
f"README target already exists: {target}. "
"Pass --force to overwrite it, or use --output to write elsewhere."
)
The tool does not eat your existing README while you are looking away.
Scout
doxify scout react 18 19 --repo /path/to/app fetches release notes in the upgrade range, extracts breaking changes with exact quotes, and searches the tracked code for the affected APIs.
The point is not "here is everything that changed in React 19." The point is "here is the short list of changes that touch this repository, with file and line matches."
Scout drops any breaking change whose quote is not actually in the release notes. If the model fails, a deterministic fallback extracts breaking-change lines instead. The report only shows changes that have a code match, because a breaking change that does not touch this repository is not this repository's problem.
The repository includes a replay fixture for the React 18 to 19 upgrade, so extraction recall can be checked against a known list instead of judged by how convincing the output looks.
Case study
doxify compile produces six sections: Snapshot, The problem, The approach, The hard part, What shipped, and What I would change.
For each narrative section, Doxify retrieves up to three evidence cards and gives them to Gemma. The model writes with [E#] citations. The verifier checks the citations, numbers, identifiers, and a list of claims that code cannot prove.
Unsupported sentences are not deleted silently. They are marked [needs evidence], paired with a gap question, and included in the claim ledger. Author context can be added with doxify attest and is labeled as an attestation, not repository proof.
The checked-in nextjs-dashboard run left 34 of its 47 claims needing evidence. That is the product working. The 34 are the review queue.
That artifact is the raw run, not a cleaned-up sample. The [needs evidence] tags and the model's awkward openings are the point: the verifier flags them instead of hiding them.
Demo
There is no hosted demo. Doxify is a CLI, and the repository under analysis does not have to leave the machine. The walkthrough is the command block below; the checked-in artifacts are its output.
The motivating demo is scholarship-mcp: 20 commits, nine MCP tools, three models, 26 seeded records, and no README. The reproducibility artifact is a run against nextjs-dashboard, because it is a useful stress test.
That run produced:
- 13 verified claims
- 34 claims that need evidence
- 0 rejected claims
Install Doxify on Linux, macOS, or WSL:
curl -fsSL --retry 3 --retry-delay 2 https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
doxify --version
The installer sets up an isolated uv environment and runs doxify self-check. It does not use sudo, install Ollama, or download a model.
Reproduce the run locally:
ollama pull gemma3:1b
doxify self-check --json
doxify compile /path/to/nextjs-dashboard --out /tmp/doxify-demo
doxify readme /path/to/nextjs-dashboard --output /tmp/README.md
SERPAPI_API_KEY=... doxify scout react 18 19 --repo /path/to/app
Working from a source checkout instead? Run uv sync, then prefix each command with uv run.
Sentry is opt-in. Leave SENTRY_DSN unset to keep tracing off, or set it to see command, stage, model, and HTTP spans.
Precomputed artifacts:
Code
The full project is at github.com/Ekomjah/doxify.
Start with:
-
install.sh— one-command install for Linux, macOS, and WSL. -
src/doxify/pipeline.py— compile, README, verify, and export. -
src/doxify/evidence.py— deterministic scanning and evidence cards. -
src/doxify/verifier.py— citation, number, identifier, and forbidden-claim checks. -
src/doxify/scout.py— release notes, quote validation, SerpApi fallback, and code matching. -
src/doxify/monitoring.py— opt-in Sentry tracing. -
src/doxify/redaction.py— the redaction policy. -
src/doxify/selfcheck.py— health checks for the Sentry cron monitor. -
docs/observability.md— Sentry setup, data policy, and dashboards. -
action.yml— the composite GitHub Action. -
.github/workflows/ci.yml— tests, build, and the self-audit. -
.github/workflows/sentry-nightly.yml— nightly self-check and cron check-in.
The repository includes 74 tests, a Ruff check, a build check, and a self-audit that runs Doxify's verifier against Doxify's README.
How I Built It
Doxify is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, Ollama, and the Sentry SDK. The default model is gemma3:1b running locally. Users who cannot or do not want to install Ollama can opt into Ollama Cloud with --provider cloud and OLLAMA_API_KEY; cloud mode defaults to gemma4:31b and warns that evidence cards will leave the machine.
The core pipeline is deliberately boring:
repo -> evidence cards -> 3 cards per section -> Gemma with [E#]
-> deterministic verifier -> README / case study / defense / CI
A 1B model cannot hold a whole repository. Doxify does not ask it to. It gives the model a small set of precise evidence cards and a citation grammar. The model turns those cards into prose. The compiler decides whether the prose is allowed to survive.
The compile loop
for spec in SECTION_SPECS:
cards = retrieve_evidence(scan.evidence, spec, limit=3)
if spec.id in {"snapshot", "problem", "reflection"}:
generated = GeneratedSection(
body=template_section(spec, cards),
fallback_used=False,
)
else:
generated = generate_section(
client,
spec,
cards,
repo_name=scan.repo.name,
role=role,
draft=draft,
)
section_claims = extract_claims(
spec.id,
generated.body,
start_index=len(claims) + 1,
)
claims.extend(section_claims)
claims = verify_claims(claims, scan.evidence)
Snapshot, problem framing, and reflection are deterministic templates because those sections are mostly structure. The narrative sections use the model. If the model fails, the section falls back to a template and the failure is recorded in the manifest.
The verifier
Every substantive sentence becomes a claim. A generated claim without a citation cannot pass. A citation to an evidence ID that does not exist is rejected.
if not claim.evidence_ids:
claim.status = ClaimStatus.NEEDS_EVIDENCE
claim.reason = "No citation token was found."
return claim
cited = [evidence_by_id.get(evidence_id) for evidence_id in claim.evidence_ids]
unknown = [e for e, card in zip(claim.evidence_ids, cited, strict=False) if card is None]
if unknown:
claim.status = ClaimStatus.REJECTED
claim.reason = f"Unknown evidence citation(s): {', '.join(unknown)}."
return claim
The verifier also checks numbers against the cited evidence, checks code identifiers, checks token overlap, and flags claims that code cannot prove: deployment, "tests pass," OAuth, user counts, performance improvements, awards, team size, and "first/only" claims.
A sentence that says the repository has 30 records when the evidence says 26 is rejected. A sentence that says "reduced latency by 40%" without a measurement needs evidence. A sentence that says "deployed to production" needs human evidence; a deployment keyword in a config file is not enough.
README provenance
The visible README only gets supported material. The omitted claims stay in the compile session.
omitted = [
claim for claim in claims
if claim.status not in {ClaimStatus.VERIFIED, ClaimStatus.ATTESTED}
]
The hidden markers tell CI which evidence supports each section:
ids = " ".join(dict.fromkeys(card.id for card in cards))
if ids:
lines.extend(["", f"<!-- doxify-evidence: {ids} -->"])
That is how Doxify audits its own README. The repository has a committed evidence file, hidden markers, and a CI job that fails if a section is unmarked or unsupported. The self-audit currently reports five verified claims and eight author-attested claims.
Scout's exact-quote rule
def quote_in_notes(quote: str, body: str) -> bool:
normalized = _normalize_quote(quote)
return len(normalized) >= 12 and normalized in _normalize_quote(body)
A breaking change whose quote is not found is dropped:
if note is None:
warnings.append("Dropped a change because its quote was not found in the release notes.")
continue
Then the deterministic code search looks for the affected APIs in tracked files, with word-boundary matching so render does not match renderToString:
pattern = re.compile(rf"(?<![A-Za-z0-9_$]){re.escape(alias)}(?![A-Za-z0-9_$])")
Scout is a candidate finder, not a type checker. It tells you where to look. It does not promise the migration is complete.
SerpApi
Scout prefers an explicit --notes file, then GitHub releases, then SerpApi. The fallback searches for release-note pages and pulls their text. The exact-quote rule still applies, so a search result cannot become a confident breaking-change claim unless the quote is actually on the page.
key = serpapi_key or os.environ.get("SERPAPI_API_KEY")
if key:
notes = fetch_serpapi_notes(package, from_version, to_version, api_key=key)
warnings.append("Used SerpApi because GitHub release notes were unavailable.")
No key means the user can still pass --notes FILE and stay offline. When the key is used, the HTTP span records only the scheme, host, and path, so the query string and API key do not leak into Sentry:
return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
Sentry Agent Tracing
A local CLI has a visibility problem: the user sees a spinner, not the pipeline. Sentry gives Doxify an opt-in view of what happened without turning the tool into a data collector.
init_monitoring()
command = _command_name()
with command_transaction(command):
app()
Inside the Ollama client, a model call becomes a span with provider, model, temperature, max tokens, latency, and token usage. The prompt and the output are not recorded.
with model_span(
provider=self.provider,
model=self.model,
temperature=temperature,
max_tokens=num_predict,
) as span:
response = self._client.chat(**payload)
record_model_usage(
span,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
)
The trace looks like this:
doxify.compile
├── validate
├── scan
├── generate × 6
│ └── model.chat (provider, model, tokens, latency)
├── verify
├── defense
└── export
When a compile is slow, the trace says whether the time went into scanning, the model, or verification. When Scout fails, the HTTP span shows which fetch failed without exposing the URL query string.
Monitoring only initializes when SENTRY_DSN is set, and DOXIFY_SENTRY=0 turns it off. The SDK uses a restricted data collection policy:
SAFE_DATA_COLLECTION = {
"user_info": False,
"cookies": {"mode": "off"},
"http_headers": {"request": {"mode": "off"}},
"http_bodies": [],
"url_query_params": {"mode": "off"},
"graphql": {"document": False, "variables": False},
"gen_ai": {"inputs": False, "outputs": False},
"database_query_data": False,
"queues": False,
"stack_frame_variables": False,
"frame_context_lines": 0,
}
Tokens, API keys, and absolute home paths are redacted before events leave the process. before_send, before_breadcrumb, before_send_transaction, and before_send_log all pass through the same policy.
self-check can also use a Sentry cron monitor:
with cron_monitor(monitor_slug):
result = run_self_check(client=client)
The monitor config describes a nightly 3 a.m. UTC check-in with a 10-minute margin and a 15-minute maximum runtime. .github/workflows/sentry-nightly.yml runs doxify self-check on that schedule, so a broken install shows up as a missed check-in.
GitHub Actions
The test job installs uv, syncs the lockfile, runs Ruff, runs the 74 tests, builds the package, and uploads the distributions. A separate installer job runs install.sh from a source checkout, then checks doxify --version and doxify self-check.
The verify job runs the self-audit through the local composite action:
- name: Verify self-audit README
uses: ./
with:
artifact: README.md
evidence: docs/self-audit/evidence.json
fail-on: needs_evidence,rejected
It also runs the unresolved nextjs-dashboard case study through the same verifier and asserts that the step fails. That negative test matters. A verification system that has never failed is just a green checkmark. The job sets SENTRY_DSN="" so CI does not initialize monitoring.
The composite action installs uv, syncs with --no-dev, and runs doxify verify --check with either a session or an artifact/evidence pair. External users can call it from their own workflow:
- uses: Ekomjah/doxify@main
with:
session: path/to/session
fail-on: needs_evidence,rejected
It is the same CLI with a smaller interface.
What broke along the way
The first case study was too confident. The run against the local demo repo left 34 of its 47 claims needing evidence. That is exactly the kind of fluent, vague writing I was trying to avoid. The fix was not a longer prompt. It was smaller evidence sets, deterministic templates for structural sections, and a review queue that shows every unresolved claim.
The README writer could overwrite an existing file. It now refuses without --force and backs up the old file when it does.
Sentry's defaults are built for web apps, not private repositories. They collect more than a local CLI should. Request bodies, headers, query strings, user info, and model inputs are now explicitly off. Tokens and paths are redacted. A monitoring feature that leaks the repository is worse than no monitoring.
A passing CI check proves very little if it cannot fail. The verify job includes an unresolved case study that is expected to fail. If it ever passes, the job fails.
What I would do next
The obvious next step is a hosted review UI for editing claims and attesting gaps. I have kept it out of this build on purpose: the CLI and the verifier are the parts that have to be right first.
After that: semantic verification instead of lexical overlap, more language support in Scout, and a better story for non-semver dependencies.
Why Does Open Innovation Matter?
Four things were possible because the pieces are open:
- The repo can stay private. A developer with an NDA or sensitive customer data cannot upload the code to a hosted writing tool. Local Gemma means the evidence does not go to a model API.
-
It runs on a normal laptop. The same weak CPU that almost kept me out is enough for
gemma3:1b, which is about 806 MB. After the model download, the default workflow is offline. No API key, no per-token bill. -
The model is a flag.
--modelorDOXIFY_MODELcan swap Gemma for another Ollama model without changing the verifier. - The verifier is inspectable. You do not have to trust the model or trust me. The checks are in the repo, and the evidence is in the output.
The optional services do not change that. Sentry is off without a DSN. SerpApi is a fallback; --notes FILE keeps Scout offline. The GitHub Action runs the same local verifier.
Cloud mode exists for people who cannot install Ollama, but it is opt-in and warns that evidence cards leave the machine. The default stays local because privacy is the feature.
A sloth should not have to choose between a useful tool and a private repository.
My Agent Session
I built Doxify in a VERY VERY VERY LONGG opencode session.
plan how to allow users of doxify without local install of ollama or a model be able to still use the product using the model from teh cloud instead?
$ ls -la
total 244
drwxrwxr-x 12 [user] [user] 4096 Oct 24 00:29 .
drwxrwxr-x 16 [user] [user] 4096 Oct 23 23:32 ..
drwxrwxr-x 7 [user] [user] 4096 Oct 23 23:53 .git
drwxrwxr-x 3 [user] [user] 4096 Oct 23 23:15 .github
-rw-rw-r-- 1 [user] [user] 158 Oct 23 23:21 .gitignore
drwxrwxr-x 2 [user] [user] 4096 Oct 22 17:40 .obsidian
drwxrwxr-x 3 [user] [user] 4096 Oct 23 21:08 .pytest_cache
-rw-rw-r-- 1 [user] [user] 5 Oct 22 22:27 .python-version
drwxrwxr-x 3 [user] [user] 4096 Oct 23 21:01 .ruff_cache
drwxrwxr-x 4 [user] [user] 4096 Oct 22 22:27 .venv
-rw-rw-r-- 1 [user] [user] 92089 Oct 23 23:31 PLAN.md
-rw-rw-r-- 1 [user] [user] 0 Oct 22 22:27 README.md
drwxrwxr-x 2 [user] [user] 4096 Oct 24 00:29 benchmarks
drwxrwxr-x 4 [user] [user] 4096 O
… [truncated 248 chars]
$ git log --oneline -10 && git status
f4b1d79 chore(demo): add precomputed nextjs-dashboard compile artifacts
95a5e02 build(ci): add Python 3.10 TOML fallback and package CI
11a198d docs(plan): align roadmap with CLI MVP and README mode
663805d test(readme): cover evidence rendering and temp-session safety
a668d97 feat(core): implement evidence-backed compiler and README output
e845094 Refactor code structure for improved readability and maintainability
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/doxify/cli.py
modified: src/doxify/console.py
modified: src/doxify/errors.py
modified: src/doxify/git_repo.py
modified: src/doxify/models.py
modified: src/doxify/prompts
… [truncated 209 chars]
$ find src tests benchmarks -type f | sort
benchmarks/scout-react-18-19.json
src/doxify/claims.py
src/doxify/cli.py
src/doxify/console.py
src/doxify/defense.py
src/doxify/errors.py
src/doxify/evidence.py
src/doxify/exporter.py
src/doxify/generator.py
src/doxify/git_repo.py
src/doxify/__init__.py
src/doxify/interview.py
src/doxify/__main__.py
src/doxify/models.py
src/doxify/ollama_client.py
src/doxify/paths.py
src/doxify/pipeline.py
src/doxify/prompts.py
src/doxify/__pycache__/claims.cpython-310.pyc
src/doxify/__pycache__/cli.cpython-310.pyc
src/doxify/__pycache__/console.cpython-310.pyc
src/doxify/__pycache__/defense.cpython-310.pyc
src/doxify/__pycache__/errors.cpython-310.pyc
src/doxify/__pycache__/evidence.cpython-310.pyc
src/doxify/__pycache__/exporter.cpython-310.pyc
src/doxify/__pycache__/generator.cpython-310.pyc
src/doxify/
… [truncated 1161 chars]
$ cat pyproject.toml
[project]
name = "doxify"
version = "0.1.0"
description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
readme = "README.md"
authors = [
{ name = "Ekomjah Edet", email = "[redacted-email]" }
]
requires-python = ">=3.10"
dependencies = [
"gitpython>=3.2.1",
"ollama>=0.6.3",
"pydantic>=2.14.1",
"rich>=15.0.0",
"tomli>=2.5.0",
"typer>=0.27.3",
]
[project.scripts]
doxify = "doxify:main"
[build-system]
requires = ["uv_build>=0.13.1,<0.14.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff>=0.17.2",
]
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "UP", "B", "SIM", "RUF"]
ignore = ["B008"]
pattern: ollama|Ollama|OLLAMA
path:
Found 75 matches
~/Desktop/repos/doxify/pyproject.toml:
Line 12: "ollama>=0.6.3",
~/Desktop/repos/doxify/src/doxify/errors.py:
Line 14: """Ollama is unavailable or the requested model is missing."""
~/Desktop/repos/doxify/src/doxify/pipeline.py:
Line 36: from .ollama_client import OllamaClient
Line 100: client: OllamaClient,
Line 223: client: OllamaClient,
~/Desktop/repos/doxify/PLAN.md:
Line 13: > Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. The FastAPI and
Line 40: - run fully locally with Ge
… [truncated 5731 chars]
file: ~/Desktop/repos/doxify/src/doxify/ollama_client.py
<path>~/Desktop/repos/doxify/src/doxify/ollama_client.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import os
4: from typing import Any
5:
6: from ollama import Client, RequestError, ResponseError
7:
8: from .errors import ModelError
9:
10: DEFAULT_HOST = "http://127.0.0.1:11434"
11: DEFAULT_MODEL = "gemma3:1b"
12:
13:
14: class OllamaClient:
15: def __init__(self, model: str = DEFAULT_MODEL, host: str | None = None) -> None:
16: configured_host = host or os.environ.get("DOXIFY_OLLAMA_HOST") or DEFAULT_HOST
17: if "://"
… [truncated 2794 chars]
file: ~/Desktop/repos/doxify/src/doxify/generator.py
<path>~/Desktop/repos/doxify/src/doxify/generator.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import re
4: from dataclasses import dataclass
5:
6: from .claims import CITATION_RE, GAP_RE
7: from .errors import GenerationError, ModelError
8: from .models import EvidenceCard, SectionSpec
9: from .ollama_client import OllamaClient
10: from .prompts import repair_messages, section_messages
11:
12:
13: @dataclass
14: class GeneratedSection:
15: body: str
16: fallback_used: bool
17: error: str | None = None
18:
19:
20: def generate_
… [truncated 4425 chars]
file: ~/Desktop/repos/doxify/src/doxify/cli.py
<path>~/Desktop/repos/doxify/src/doxify/cli.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import re
4: import shutil
5: from datetime import datetime
6: from pathlib import Path
7:
8: import typer
9: from rich.table import Table
10:
11: from .console import (
12: console,
13: error,
14: print_compile_summary,
15: print_readme_summary,
16: print_scan_summary,
17: print_scout_summary,
18: stage_progress,
19: )
20: from .errors import DoxifyError
21: from .models import CompileSummary, ScanResult
22: from .ollama_clien
… [truncated 12671 chars]
file: ~/Desktop/repos/doxify/src/doxify/pipeline.py
<path>~/Desktop/repos/doxify/src/doxify/pipeline.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import re
4: import shutil
5: import tempfile
6: from collections.abc import Callable
7: from pathlib import Path
8:
9: from .claims import extract_claims
10: from .defense import build_defense
11: from .errors import SessionError
12: from .exporter import (
13: write_case_study,
14: write_claims_json,
15: write_defense,
16: write_defense_json,
17: write_evidence_json,
18: write_sections_json,
19: write_verification_json,
2
… [truncated 12739 chars]
file: ~/Desktop/repos/doxify/src/doxify/defense.py
<path>~/Desktop/repos/doxify/src/doxify/defense.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import re
4:
5: from .errors import ModelError
6: from .models import CaseStudySection, Claim, ClaimStatus, DefenseQuestion, EvidenceCard
7: from .ollama_client import OllamaClient
8: from .prompts import defense_messages
9:
10: QUESTION_RE = re.compile(r"^Q:\s*(.+)$", re.MULTILINE)
11: EVIDENCE_RE = re.compile(r"^EVIDENCE:\s*(.+)$", re.MULTILINE)
12: WHY_RE = re.compile(r"^WHY:\s*(.+)$", re.MULTILINE)
13: GAP_RE = re.compile(r"^GAP:\s*(.+)$", re.MULT
… [truncated 6337 chars]
file: ~/Desktop/repos/doxify/src/doxify/errors.py
<path>~/Desktop/repos/doxify/src/doxify/errors.py</path>
<type>file</type>
<content>
1: class DoxifyError(Exception):
2: """Base class for expected Doxify failures."""
3:
4:
5: class RepoError(DoxifyError):
6: """The selected path is not a usable local git repository."""
7:
8:
9: class SessionError(DoxifyError):
10: """A session directory is missing or invalid."""
11:
12:
13: class ModelError(DoxifyError):
14: """Ollama is unavailable or the requested model is missing."""
15:
16:
17: class GenerationError(DoxifyError):
18: """A section could not be gener
… [truncated 179 chars]
file: ~/Desktop/repos/doxify/src/doxify/models.py
<path>~/Desktop/repos/doxify/src/doxify/models.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: from enum import Enum
4: from typing import Any
5:
6: from pydantic import BaseModel, ConfigDict, Field
7:
8: SCHEMA_VERSION = "1.0"
9: TOOL_NAME = "doxify"
10:
11:
12: class EvidenceKind(str, Enum):
13: METRIC = "metric"
14: STACK = "stack"
15: FILE = "file"
16: CODE = "code"
17: COMMIT = "commit"
18: TEST = "test"
19: DEPLOY = "deploy"
20: README = "readme"
21: ATTESTED = "attested"
22:
23:
24: class EvidenceSt
… [truncated 8152 chars]
$ rg -n "client\.|OllamaClient|ensure_ready|DEFAULT_MODEL|DEFAULT_HOST|DOXIFY_" src tests PLAN.md | head -120
tests/test_scout.py:56: def ensure_ready(self) -> None:
src/doxify/scout.py:29:from .ollama_client import OllamaClient
src/doxify/scout.py:707: client: OllamaClient,
src/doxify/scout.py:732: response = client.chat(
src/doxify/scout.py:1088: client: OllamaClient,
src/doxify/scout.py:1121: client.ensure_ready()
src/doxify/cli.py:22:from .ollama_client import DEFAULT_MODEL, OllamaClient
src/doxify/cli.py:70: model: str = typer.Option(DEFAULT_MODEL, help="Ollama model to check."),
src/doxify/cli.py:83: client = OllamaClient(model=model, host=host)
src/doxify/cli.py:84: healthy, detail = client.health()
src/doxify/cli.py:96: model: str = typer.Option(DEFAULT_MODEL, help="Model recorded in the manifest."),
src/doxify/cli.py:120: model: str = typer.Option
… [truncated 1866 chars]
file: ~/Desktop/repos/doxify/src/doxify/prompts.py
<path>~/Desktop/repos/doxify/src/doxify/prompts.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: from .models import EvidenceCard, ReleaseNote, SectionSpec
4:
5: SYSTEM_PROMPT = """You are a technical portfolio editor working inside a compiler.
6:
7: You will receive evidence cards and a section goal. You may only use the
8: evidence cards provided. You must cite evidence for every sentence using
9: tokens like [E1] or [E2][E5].
10:
11: Rules:
12: - Never invent a number, user count, performance result, team size, award,
13: deployment status,
… [truncated 7276 chars]
file: ~/Desktop/repos/doxify/src/doxify/interview.py
<path>~/Desktop/repos/doxify/src/doxify/interview.py</path>
<type>file</type>
<content>
(End of file - total 0 lines)
</content>
$ rg -n "hosted|cloud|provider|API key|OpenRouter|OpenAI|Gemini|Groq|Ollama cloud|remote" PLAN.md | head -100
5:> Status: CLI MVP implemented; hosted mode and optional integrations planned.
14:> Next.js sections below describe the deferred hosted UI, not the current build.
69:The current implementation is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, and a local `gemma3:1b` model through Ollama. A future hosted mode may add FastAPI and Next.js, but the shipped MVP is local-first: it reads a local repository, writes compile artifacts to a local session directory, and calls only the local Ollama server. It has no database, no cloud API, no telemetry, and no account system.
230:Using a cloud API would undermine the core story. `gemma3:1b` via Ollama is small enough to run on the target laptop and open enough to be inspectable and portable.
237:- no API keys,
328:- hosted tools have a
… [truncated 1182 chars]
$ sed -n '1,120p' PLAN.md
# Doxify — Hacktoberfest Week 3 Project Plan
> Working title: **Doxify**
> Tagline: **Portfolio case studies you can defend.**
> Status: CLI MVP implemented; hosted mode and optional integrations planned.
> Plan date: 2026-10-22.
> Last implementation update: 2026-10-23.
> Hackathon deadline: 2026-10-25, 11:59 PM PDT = 2026-10-26, 7:59 AM WAT.
> Target category: **Best Use of Gemma**.
> Demo input repo: `~/Desktop/repos/scholarship-mcp`.
> **Implementation status:** The working MVP is a local CLI in `src/doxify` using
> Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. The FastAPI and
> Next.js sections below describe the deferred hosted UI, not the current build.
> Current commands: `doctor`, `scan`, `compile`, `readme`, `verify`, `attest`,
> and `export`.
… [truncated 3721 chars]
$ sed -n '680,770p' src/doxify/scout.py; sed -n '1070,1140p' src/doxify/scout.py
for start in starts:
try:
payload, _ = json.JSONDecoder().raw_decode(cleaned[start:])
break
except json.JSONDecodeError as exc:
last_error = exc
if payload is None:
raise ScoutError("Gemma returned invalid JSON.") from last_error
if isinstance(payload, dict):
raw_changes = payload.get("changes") or payload.get("breaking_changes") or []
elif isinstance(payload, list):
raw_changes = payload
else:
raw_changes = []
if not isinstance(raw_changes, list):
raise ScoutError("Gemma returned an unexpected JSON shape.")
changes: list[_RawChange] = []
for item in raw_changes:
change = _coerce_raw_change(item)
if change is not None:
changes.append(chan
… [truncated 4769 chars]
file: ~/Desktop/repos/doxify/tests/test_scout.py
<path>~/Desktop/repos/doxify/tests/test_scout.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import json
4: from pathlib import Path
5:
6: import pytest
7: from git import Repo
8: from typer.testing import CliRunner
9:
10: import doxify.cli as cli
11: import doxify.scout as scout
12: from doxify.models import (
13: BreakingChange,
14: CodeMatch,
15: ReleaseNote,
16: RepoSummary,
17: ScoutReport,
18: ScoutScore,
19: ScoutSummary,
20: )
21: from doxify.scout import (
22: ScoutResult,
23: fetch_github_releases,
… [truncated 14028 chars]
file: ~/Desktop/repos/doxify/src/doxify/session.py
<path>~/Desktop/repos/doxify/src/doxify/session.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import json
4: from datetime import datetime, timezone
5: from pathlib import Path
6: from typing import Any, TypeVar
7:
8: from pydantic import BaseModel
9:
10: from .errors import SessionError
11: from .models import SessionManifest
12:
13: T = TypeVar("T", bound=BaseModel)
14:
15:
16: def utc_now() -> str:
17: return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
18:
19:
20: def prepare_output_dir(path: Path, *, force: bool
… [truncated 1800 chars]
$ python - <<'PY'
import inspect
from ollama import Client
print(inspect.signature(Client.__init__))
print(inspect.signature(Client.chat))
PY
zsh:1: command not found: python
$ .venv/bin/python - <<'PY'
import inspect
from ollama import Client
print(inspect.signature(Client.__init__))
print(inspect.signature(Client.chat))
print(inspect.signature(Client.list))
PY
(self, host: Optional[str] = None, **kwargs) -> None
(self, model: str = '', messages: Optional[Sequence[Union[Mapping[str, Any], ollama._types.Message]]] = None, *, tools: Optional[Sequence[Union[Mapping[str, Any], ollama._types.Tool, Callable]]] = None, stream: bool = False, think: Union[bool, str, NoneType] = None, logprobs: Optional[bool] = None, top_logprobs: Optional[int] = None, format: Union[Literal['', 'json'], dict[str, Any], NoneType] = None, options: Union[Mapping[str, Any], ollama._types.Options, NoneType] = None, keep_alive: Union[float, str, NoneType] = None) -> Union[ollama._types.ChatResponse, collections.abc.Iterator[ollama._types.ChatResponse]]
(self) -> ollama._types.ListResponse
$ rg -n "name = \"httpx\"|name = \"ollama\"" uv.lock | head; sed -n '200,230p' uv.lock
61: { name = "ollama" },
77: { name = "ollama", specifier = ">=0.6.3" },
149:name = "httpx"
203:name = "ollama"
207: { name = "httpx" },
]
[[package]]
name = "ollama"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b8/97/eeafe65594e4f4b25e443e068ef7d83aa3105b023e12e1c408c38669fc07/ollama-0.6.3.tar.gz", hash = "sha256:41fc49a8095c4a75939c4c1f8582e4d0671692fb6eac2a5a7ede8c9872b67096", size = 56868, upload-time = "2026-09-29T01:26:51.906Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/64/87505d9e006461233c21c8e66dc1ecee49c996090584b216abd0dd4a8322/ollama-0.6.3-py3-none-any.whl", hash = "sha256:6a20bc42c1a5f889295d7e
… [truncated 1486 chars]
$ rg -n "OLLAMA|API_KEY|ENV|environment|DOXIFY" src/doxify tests .github -g '!*.pyc'
src/doxify/scout.py:519: key = serpapi_key or os.environ.get("SERPAPI_API_KEY")
src/doxify/scout.py:532: "set SERPAPI_API_KEY for the SerpApi fallback, or pass --notes FILE."
src/doxify/ollama_client.py:16: configured_host = host or os.environ.get("DOXIFY_OLLAMA_HOST") or DEFAULT_HOST
src/doxify/ollama_client.py:19: self.model = model or os.environ.get("DOXIFY_MODEL") or DEFAULT_MODEL
src/doxify/readme.py:270: "Add install, environment, and database setup steps where the repository requires them.",
src/doxify/readme.py:314: "Add installation, environment, deployment, and licensing details that are not "
$ .venv/bin/python - <<'PY'
import inspect, ollama
from ollama import Client
print(inspect.getsource(Client.__init__))
print(inspect.getsource(ollama.Client.list)[:1000])
PY
def __init__(self, host: Optional[str] = None, **kwargs) -> None:
super().__init__(httpx.Client, host, **kwargs)
def list(self) -> ListResponse:
return self._request(
ListResponse,
'GET',
'/api/tags',
)
$ sed -n '840,930p' PLAN.md; sed -n '2430,2470p' PLAN.md
│ │ │ └── jobs.py
│ │ └── prompts/
│ │ ├── __init__.py
│ │ ├── system.py
│ │ ├── section.py
│ │ ├── repair.py
│ │ └── defense.py
│ └── tests/
│ ├── test_citation.py
│ ├── test_verifier.py
│ ├── test_evidence.py
│ └── fixtures/
├── web/
│ ├── package.json
│ ├── tsconfig.json
│ ├── next.config.ts
│ ├── app/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── globals.css
│ ├── components/
│ │ ├── CompileForm.tsx
│ │ ├── StageList.tsx
│ │ ├── CaseStudy.tsx
│ │ ├── EvidencePane.tsx
│ │ ├── DefensePane.tsx
│ │ ├── ClaimChip.tsx
│ │ └── SummaryBar.tsx
│ └── lib/
│ ├── api.ts
│ └── types.ts
├── shared/
│ └── openapi.json
└── demo/
└── scholarship-mcp/
├─
… [truncated 2879 chars]
$ .venv/bin/python - <<'PY'
import inspect
import ollama._client as c
print(inspect.getsource(c.BaseClient.__init__))
print(inspect.getsource(c.BaseClient._request)[:2500])
PY
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
AttributeError: 'Client' object has no attribute 'BaseClient'
$ .venv/bin/python - <<'PY'
import inspect, ollama
from ollama._client import BaseClient
print(inspect.getsource(BaseClient.__init__))
PY
def __init__(
self,
client,
host: Optional[str] = None,
*,
follow_redirects: bool = True,
timeout: Any = None,
headers: Optional[Mapping[str, str]] = None,
**kwargs,
) -> None:
"""
Creates a httpx client. Default parameters are the same as those defined in httpx
except for the following:
- `follow_redirects`: True
- `timeout`: None
`kwargs` are passed to the httpx client.
"""
headers = {
k.lower(): v
for k, v in {
**(headers or {}),
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': f'ollama-python/{__version__} ({platform.machine()} {platform.system().lower()}) Python/{platform.python_version()}',
}.items()
if v is not None
}
api
… [truncated 347 chars]
Ollama Cloud API key https://ollama.com/api/chat models list API 2026
Title: Authentication - Ollama
URL: https://docs.ollama.com/api/authentication
Published: N/A
Author: N/A
Highlights:
The local API at `http://localhost:11434` does not require authentication. To use cloud models, publish models, or download private models, sign in to Ollama. For direct API access to ollama.com, use an API key.
API keys
Direct cloud inference at `https://ollama.com/api` and `https://ollama.com/v1` requires an API key. No Ollama installation or local server is required. Create an API key, then set it in your terminal:
```
export OLLAMA_API_KEY="[redacted]"
```
Use the API key in the `Authorization: Bearer` header. This also applies to the hosted Anthropic-compatible `/v1/messages` endpoint; `x-api-key` alone is not supported.
```
curl https://ollama.com/api/chat \
… [truncated 15678 chars]
$ sed -n '1,120p' tests/test_readme.py
from __future__ import annotations
from pathlib import Path
import pytest
from typer.testing import CliRunner
import doxify.cli as cli
import doxify.pipeline as pipeline
from doxify.models import (
Claim,
ClaimStatus,
CompileResult,
CompileSummary,
EvidenceCard,
EvidenceKind,
EvidenceStrength,
RepoSummary,
SessionManifest,
)
from doxify.readme import (
GENERATED_MARKER,
ReadmeResult,
build_readme,
resolve_readme_target,
)
def _card(
card_id: str,
kind: EvidenceKind,
title: str,
text: str,
*,
meta: dict[str, object] | None = None,
strength: EvidenceStrength = EvidenceStrength.DETERMINISTIC,
) -> EvidenceCard:
return EvidenceCard(
id=card_id,
kind=kind,
title=title,
text=t
… [truncated 2399 chars]
$ rg -n "local|Ollama|model|offline|private|upload|cloud" src/doxify/readme.py src/doxify/exporter.py src/doxify/console.py | head -80
src/doxify/console.py:9:from .models import CompileResult, RepoSummary, ScanResult
src/doxify/exporter.py:5:from .models import (
src/doxify/exporter.py:33: "> Compiled locally with Doxify. Citations point to the evidence appendix.",
src/doxify/exporter.py:148: "repo": repo.model_dump(mode="json"),
src/doxify/exporter.py:149: "evidence": [card.model_dump(mode="json") for card in evidence],
src/doxify/readme.py:9:from .models import (
src/doxify/readme.py:162: models = _first_card(evidence, "database models")
src/doxify/readme.py:163: if models is not None:
src/doxify/readme.py:164: names = _meta_list(models, "model_names")
src/doxify/readme.py:165: body.extend(["", models.text])
src/doxify/readme.py:168: ids.append(models.id)
$ .venv/bin/python - <<'PY'
import json, urllib.request
try:
with urllib.request.urlopen('https://ollama.com/api/tags', timeout=10) as r:
data=json.load(r)
for m in data.get('models',[]):
name=m.get('model','')
if 'gemma' in name.lower():
print(name)
except Exception as e:
print(type(e).__name__, e)
PY
gemma4:31b
$ sed -n '1,120p' src/doxify/console.py
from __future__ import annotations
from pathlib import Path
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
from rich.table import Table
from .models import CompileResult, RepoSummary, ScanResult
from .readme import ReadmeResult
from .scout import ScoutResult
console = Console()
def stage_progress():
return Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
TimeElapsedColumn(),
console=console,
transient=False,
)
def print_scan_summary(result: ScanResult, output: Path) -> None:
table = Table(title="Scan complete", show_header=False)
table.add_column("Field", style="cyan")
table.add_column("Value")
table.add_row("Repository
… [truncated 3858 chars]
$ git diff --stat && git diff -- src/doxify/cli.py src/doxify/models.py src/doxify/prompts.py src/doxify/errors.py | head -200
src/doxify/cli.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++++-
src/doxify/console.py | 42 ++++++++++++++++++++
src/doxify/errors.py | 4 ++
src/doxify/git_repo.py | 4 +-
src/doxify/models.py | 90 ++++++++++++++++++++++++++++++++++++++++++
src/doxify/prompts.py | 60 +++++++++++++++++++++++++++-
6 files changed, 299 insertions(+), 4 deletions(-)
diff --git a/src/doxify/cli.py b/src/doxify/cli.py
index fd59f97..34bebd5 100644
--- a/src/doxify/cli.py
+++ b/src/doxify/cli.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import re
import shutil
from datetime import datetime
from pathlib import Path
@@ -13,6 +14,7 @@ from .console import (
print_compile_summary,
print_readme_summary,
print_scan_summary,
+ print_scout_summary,
s
… [truncated 5621 chars]
$ sed -n '1,160p' src/doxify/scout.py
from __future__ import annotations
import html
import json
import os
import re
from collections.abc import Callable
from dataclasses import dataclass
from html.parser import HTMLParser
from pathlib import Path, PurePosixPath
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from .errors import ModelError, ScoutError
from .git_repo import RepoContext, collect_repo_context, open_repo
from .models import (
BreakingChange,
CodeMatch,
ReleaseNote,
RepoSummary,
ScoutReport,
ScoutScore,
ScoutSource,
ScoutSummary,
SessionManifest,
)
from .ollama_client import OllamaClient
from .paths import is_secret, is_skippable, read_text_limited
from .prompts import scout_messa
… [truncated 2774 chars]
$ sed -n '1,90p' src/doxify/exporter.py
from __future__ import annotations
from pathlib import Path
from .models import (
CaseStudySection,
Claim,
ClaimsDocument,
ClaimStatus,
CompileSummary,
DefenseDocument,
DefenseQuestion,
EvidenceCard,
RepoSummary,
SectionsDocument,
VerificationReport,
)
from .session import write_json, write_text
def write_case_study(
output: Path,
repo: RepoSummary,
sections: list[CaseStudySection],
evidence: list[EvidenceCard],
summary: CompileSummary,
claims: list[Claim] | None = None,
) -> Path:
path = output / "case-study.md"
lines = [
f"# {repo.name}: an evidence-backed case study",
"",
"> Compiled locally with Doxify. Citations point to the evidence appendix.",
"> Unverified statements are
… [truncated 1986 chars]
$ rg -n "locally|local model|no cloud|no API|private|upload" src tests PLAN.md | head -60
PLAN.md:40:- run fully locally with Gemma via Ollama.
PLAN.md:69:The current implementation is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, and a local `gemma3:1b` model through Ollama. A future hosted mode may add FastAPI and Next.js, but the shipped MVP is local-first: it reads a local repository, writes compile artifacts to a local session directory, and calls only the local Ollama server. It has no database, no cloud API, no telemetry, and no account system.
PLAN.md:129:### 2.3 The private-work problem
PLAN.md:131: The problem gets worse for private or NDA work.
PLAN.md:133:Many developers cannot share the repository at all. Their best work lives behind a company login. Cloud portfolio tools ask them to connect GitHub or upload code, which is often not allowed.
PLA
… [truncated 2085 chars]
$ rg -n "manifest|SessionManifest|ScoutReport\(" src/doxify/scout.py | head -50; sed -n '1140,1260p' src/doxify/scout.py
27: SessionManifest,
36: write_manifest,
170: manifest_path: Path
929: return ScoutReport(
1144: manifest = SessionManifest(
1151: manifest_path = output / "manifest.json"
1152: write_manifest(manifest_path, manifest, [RELEASE_NOTES_JSON])
1192: manifest.status = "scouted"
1193: manifest.warnings = warnings
1194: write_manifest(
1195: manifest_path,
1196: manifest,
1206: manifest_path=manifest_path,
notes_path = output / RELEASE_NOTES_JSON
write_json(notes_path, notes_payload)
now = utc_now()
manifest = SessionManifest(
created_at=now,
updated_at=now,
repo=context.summary,
model=model,
status="notes_fetched",
)
manifest_path = output / "manifest.json"
write_manifest(manif
… [truncated 1665 chars]
$ .venv/bin/python - <<'PY'
import json, urllib.request
with urllib.request.urlopen('https://ollama.com/api/tags', timeout=15) as r:
data=json.load(r)
models=data.get('models',[])
print('count', len(models))
for m in models:
print(m.get('model'), '|', m.get('name'))
PY
count 23
gpt-oss:20b | gpt-oss:20b
clef:27b | clef:27b
beam | beam
minimax-m3 | minimax-m3
glm-5.3 | glm-5.3
glm-5.3-flash | glm-5.3-flash
kimi-k2.6 | kimi-k2.6
deepseek-v4-pro:0813 | deepseek-v4-pro:0813
gpt-oss:120b | gpt-oss:120b
nemotron-3-nano:30b | nemotron-3-nano:30b
deepseek-v4.1-flash:1017 | deepseek-v4.1-flash:1017
gemma4:31b | gemma4:31b
kimi-k2.7-code | kimi-k2.7-code
muse-spark-open | muse-spark-open
glm-5.2 | glm-5.2
kimi-k3 | kimi-k3
minimax-m2.7 | minimax-m2.7
minimax-m3.1-flash | minimax-m3.1-flash
mistral-large-3:675b | mistral-large-3:675b
mistral-large-4 | mistral-large-4
nemotron-3-super | nemotron-3-super
nemotron-3-ultra | nemotron-3-ultra
deepseek-v4.1-flash | deepseek-v4.1-flash
$ rg -n "Doxify|doxify" src/doxify/readme.py src/doxify/scout.py | head -50
src/doxify/readme.py:19:GENERATED_MARKER = "<!-- doxify-generated -->"
src/doxify/readme.py:50: lines.append(f"<!-- doxify-compile-session: {session_dir} -->")
src/doxify/readme.py:53: f"<!-- doxify-summary: {_summary_text(result)} -->",
src/doxify/readme.py:78: f"<!-- doxify-unresolved: {len(omitted)} claim(s) need evidence; "
src/doxify/readme.py:85: "<!-- doxify-note: Add a LICENSE file and license section if applicable. -->"
src/doxify/readme.py:307: "Doxify did not detect deployment configuration in the tracked files."
src/doxify/readme.py:331: lines.extend(["", f"<!-- doxify-attested: {' '.join(claim.id for claim in attested)} -->", ""])
src/doxify/readme.py:344: lines.extend(["", f"<!-- doxify-evidence: {ids} -->"])
sr
… [truncated 253 chars]
$ sed -n '220,340p' src/doxify/readme.py
def _project_layout(lines: list[str], evidence: list[EvidenceCard]) -> None:
layout = _first_card(evidence, "Top-level structure")
if layout is None:
return
top_level = layout.meta.get("top_level")
if not isinstance(top_level, dict) or not top_level:
return
entries = sorted(
((str(name), int(count)) for name, count in top_level.items()),
key=lambda item: (-item[1], item[0]),
)
body = [
"Top-level paths detected in the repository:",
"",
"```text",
*[f"{name}/ ({count} files)" if count > 1 else f"{name} ({count} file)" for name, count in entries],
"```",
]
_section(lines, "Project layout", body, [layout])
def _getting_started(
lines: list[str],
repo: RepoSummary,
eviden
… [truncated 3115 chars]
$ rg -n "render_scout_markdown|Compiled|Generated|model" src/doxify/scout.py | head -40
18:from .models import (
713: max_model_calls: int = MAX_MODEL_CALLS,
724: if calls >= max_model_calls:
726: f"Stopped after {max_model_calls} model calls; later release chunks were skipped."
784: "Review the release notes or try a larger model."
920: model: str,
935: model=model,
968:def render_scout_markdown(report: ScoutReport) -> str:
976: f"Repository: `{report.repo.name}` · Model: `{report.model}` · "
977: f"Generated: {report.created_at}",
989: "changes, and the model can miss APIs.",
1089: model: str,
1138: "notes": [note.model_dump() for note in notes],
1148: model=model,
1179: model=model,
1190: write_text(markdown_path, render_scout_markdown(report))
$ find .github -type f -maxdepth 3 -print -exec sed -n '1,160p' {} \;
.github/workflows/ci.yml
name: CI
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
test:
name: Lint, test, and build
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Test
run: uv run pytest -q
- name: Build package
run: uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
… [truncated 37 chars]
$ sed -n '1,40p' src/doxify/__init__.py; cat .python-version; rg -n "pytest|ruff" .github PLAN.md | head -20
from .cli import main
__all__ = ["main"]
3.10
PLAN.md:724:- Pytest + `pytest-asyncio` for tests.
PLAN.md:883:- `server/` — FastAPI app, scanning, generation, verification, export, pytest tests.
PLAN.md:898:make test # cd server && uv run pytest
PLAN.md:1193:- `.pytest_cache/`
PLAN.md:1967:- `uv run pytest -q` passes.
PLAN.md:1968:- `uv run ruff check .` passes.
PLAN.md:2020:Use `pytest` for the CLI and deterministic pipeline. The current suite lives in
PLAN.md:2121:uv run pytest -q
PLAN.md:2122:uv run ruff check .
PLAN.md:2141:3. `uv run ruff check .`,
PLAN.md:2142:4. `uv run pytest -q`,
PLAN.md:2347:- [ ] `.gitignore` excludes `.doxify/`, `.venv/`, `__pycache__/`, `.pytest_cache/`, `.mypy_cache/`, `node_modules/`, `.next/`, and secrets.
PLAN.md:2350:- [ ] `uv run ruff check .` pass
… [truncated 373 chars]
{"questions": [{"question": "Which cloud backend should this plan target?", "header": "Cloud backend", "options": [{"label": "Ollama Cloud (Recommended)", "description": "Set host to https://ollama.com and use OLLAMA_API_KEY. No Ollama install needed, minimal code change, keeps Gemma (gemma4:31b) and the existing SDK."}, {"label": "Any OpenAI-compatible API", "description": "Add a new adapter for OpenRouter/Groq/OpenAI/etc. Broader model choice, but more code and another SDK/dependency."}, {"label": "Both via provider abstraction", "description": "Introduce a provider interface with Ollama Cloud first and OpenAI-compatible adapters later. Cleanest long-term, more work before the deadline."}]}, {"question": "How should cloud mode be activated?", "header": "Activation", "options": [{"label": "Explicit opt-in (Recommended)", "description": "Require --provider cloud or DOXIFY_PROVIDER=cloud. Never uploads evidence implicitly, preserving the local-first privacy promise."}, {"label": "Auto-fallback", "description": "Try local Ollama; if unavailable and OLLAMA_API_KEY is set, use the cloud with a clear warning. More convenient but riskier for private repos."}, {"label": "Cloud whenever ke
… [truncated 653 chars]
User has answered your questions: "Which cloud backend should this plan target?"="Ollama Cloud (Recommended)", "How should cloud mode be activated?"="Explicit opt-in (Recommended)", "Should the cloud default model be Gemma?"="gemma4:31b (Recommended)". You can now continue with the user's answers in mind.
$ .venv/bin/python - <<'PY'
import json, urllib.request, urllib.error
for path, payload in [('/api/show', {'model':'gemma4:31b'}), ('/api/me', None)]:
url='https://ollama.com'+path
try:
data=json.dumps(payload).encode() if payload else None
req=urllib.request.Request(url, data=data, headers={'Content-Type':'application/json'})
with urllib.request.urlopen(req, timeout=15) as r:
body=r.read(200)
print(path, r.status, body[:160])
except urllib.error.HTTPError as e:
print(path, 'HTTPError', e.code, e.read(200))
except Exception as e:
print(path, type(e).__name__, e)
PY
/api/show 200 b'{"capabilities":["completion","thinking","tools","vision"],"details":{"parent_model":"gemma4:31b","format":"","family":"gemma4","families":null,"parameter_size"'
/api/me HTTPError 405 b'Method Not Allowed\n'
$ .venv/bin/python - <<'PY'
import json, urllib.request, urllib.error
url='https://ollama.com/api/chat'
data=json.dumps({'model':'gemma4:31b','messages':[{'role':'user','content':'hi'}],'stream':False}).encode()
req=urllib.request.Request(url, data=data, headers={'Content-Type':'application/json','Authorization':'Bearer [redacted]'})
try:
with urllib.request.urlopen(req, timeout=15) as r:
print(r.status, r.read(200))
except urllib.error.HTTPError as e:
print('HTTPError', e.code, e.read(300))
except Exception as e:
print(type(e).__name__, e)
PY
HTTPError 401 b'{"error":"Unauthorized"}\n'
$ .venv/bin/python - <<'PY'
import inspect
from ollama import ResponseError, RequestError
print(inspect.getsource(ResponseError))
print(inspect.getsource(RequestError))
PY
class ResponseError(Exception):
"""
Common class for response errors.
"""
def __init__(self, error: str, status_code: int = -1):
# try to parse content as JSON and extract 'error'
# fallback to raw content if JSON parsing fails
with contextlib.suppress(json.JSONDecodeError):
error = json.loads(error).get('error', error)
super().__init__(error)
self.error = error
'Reason for the error.'
self.status_code = status_code
'HTTP status code of the response.'
def __str__(self) -> str:
return f'{self.error} (status code: {self.status_code})'
class RequestError(Exception):
"""
Common class for request errors.
"""
def __init__(self, error: str):
super().__init__(error)
self.error = error
'Reason for the error.'
$ cat .gitignore; git ls-files README.md PLAN.md
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
# Local tool caches
.pytest_cache/
.ruff_cache/
PLAN.md
README.md
$ sed -n '1800,1870p' PLAN.md; sed -n '2180,2210p' PLAN.md; sed -n '2260,2290p' PLAN.md
### 15.3 Claims JSON
The claims JSON is the machine-readable ledger. It is useful for:
- debugging,
- future integrations,
- showing the verification result in the UI,
- proving the tool is not just a text generator.
### 15.4 Optional HTML export
Stretch goal only. Do not build it until the core works.
If time allows, export a single self-contained HTML file with the same dossier aesthetic. This would be a nice portfolio artifact, but Markdown is enough for the hackathon.
### 15.5 README export
`doxify readme REPO` writes an evidence-backed README without polluting the
target repository with compile artifacts:
- compile runs in `/tmp/doxify-readme-<repo>-<timestamp>/`;
- the target defaults to `REPO/README.md`;
- `--output` selects a different file or directory;
- `--force` is requ
… [truncated 3328 chars]
$ rg -n "Gemma|gemma" src/doxify tests PLAN.md | head -60
PLAN.md:9:> Target category: **Best Use of Gemma**.
PLAN.md:40:- run fully locally with Gemma via Ollama.
PLAN.md:69:The current implementation is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, and a local `gemma3:1b` model through Ollama. A future hosted mode may add FastAPI and Next.js, but the shipped MVP is local-first: it reads a local repository, writes compile artifacts to a local session directory, and calls only the local Ollama server. It has no database, no cloud API, no telemetry, and no account system.
PLAN.md:221:### 4.3 Why it fits the Gemma category
PLAN.md:230:Using a cloud API would undermine the core story. `gemma3:1b` via Ollama is small enough to run on the target laptop and open enough to be inspectable and portable.
PLAN.md:581: - Small print: “Ru
… [truncated 1806 chars]
$ .venv/bin/python - <<'PY'
import inspect
from ollama._client import _parse_host
print(inspect.getsource(_parse_host))
PY
def _parse_host(host: Optional[str]) -> str:
"""
>>> _parse_host(None)
'http://127.0.0.1:11434'
>>> _parse_host('')
'http://127.0.0.1:11434'
>>> _parse_host('1.2.3.4')
'http://1.2.3.4:11434'
>>> _parse_host(':56789')
'http://127.0.0.1:56789'
>>> _parse_host('1.2.3.4:56789')
'http://1.2.3.4:56789'
>>> _parse_host('http://1.2.3.4')
'http://1.2.3.4:80'
>>> _parse_host('https://1.2.3.4')
'https://1.2.3.4:443'
>>> _parse_host('https://1.2.3.4:56789')
'https://1.2.3.4:56789'
>>> _parse_host('example.com')
'http://example.com:11434'
>>> _parse_host('example.com:56789')
'http://example.com:56789'
>>> _parse_host('http://example.com')
'http://example.com:80'
>>> _parse_host('https://example.com')
'https://example.com:443'
>>> _parse_host('https://
… [truncated 2190 chars]
$ rg -n "SessionManifest\(|ScoutReport\(" tests src | head -40
tests/test_scout.py:207: report = ScoutReport(
tests/test_scout.py:379: report = ScoutReport(
src/doxify/scout.py:929: return ScoutReport(
src/doxify/scout.py:1144: manifest = SessionManifest(
src/doxify/models.py:178:class SessionManifest(BaseModel):
src/doxify/models.py:285:class ScoutReport(BaseModel):
tests/test_readme.py:171: manifest = SessionManifest(
src/doxify/pipeline.py:84: manifest = SessionManifest(
src/doxify/pipeline.py:114: manifest = SessionManifest(
$ sed -n '160,190p' tests/test_readme.py
user_answer="I used it in a small production workflow.",
),
]
summary = CompileSummary(
verified=1,
attested=1,
needs_evidence=1,
rejected=0,
sections=3,
evidence_cards=len(evidence),
)
manifest = SessionManifest(
created_at="2026-01-04T00:00:00+00:00",
updated_at="2026-01-04T00:00:00+00:00",
repo=repo,
model="test-model",
status="done",
)
return CompileResult(
manifest=manifest,
evidence=evidence,
claims=claims,
summary=summary,
)
def test_build_readme_uses_evidence_and_hides_unresolved(tmp_path: Path) -> None:
result = _result(tmp_path / "repo")
content = build_readme(result, session_dir=tmp_path / "session")
… [truncated 46 chars]
## Plan: opt-in Ollama Cloud inference
Decisions
-
Backend: Ollama Cloud direct API at
https://ollama.com, using the existingollamaPython SDK. No local Ollama binary/server or model download required. -
Activation: explicit
--provider cloudorDOXIFY_PROVIDER=cloud. Local mode remains the default. -
Auth:
OLLAMA_API_KEYenvironment variable only. Do not add--api-key, so keys stay out of shell history and process args. -
Defaults: local
gemma3:1bunchanged; cloudgemma4:31b, overridable with--model. -
Dependencies: none added. The SDK already supports custom hosts, auth headers, and
OLLAMA_API_KEY.
Resolution order:
| Setting | Order |
|---|---|
| Provider |
--provider → DOXIFY_PROVIDER → local
|
| Host |
--host → DOXIFY_OLLAMA_HOST → [REDACTED]:11434 or https://ollama.com
|
| Model |
--model → DOXIFY_MODEL → gemma3:1b or gemma4:31b
|
| API key |
OLLAMA_API_KEY (required only for cloud) |
1. Make the client provider-aware
src/doxify/ollama_client.py
- Add DEFAULT_CLOUD_HOST, DEFAULT_CLOUD_MODEL, and provider aliases (local, cloud, ollama-cloud).
- Extend OllamaClient.__init__ with provider and api_key; resolve host/model defaults by provider, set self.is_cloud, self.provider, and self.label.
- Pass headers={"Authorization": f"Bearer {api_key}"} when a key is present.
- Do not raise in the constructor when the key is missing; let health() and ensure_ready() report it cleanly.
- Update health() / ensure_ready():
- Cloud: fail with actionable OLLAMA_API_KEY guidance if missing.
- Cloud: check the model appears in /api/tags; suggest --model or the cloud model library if not.
- Local: preserve existing ollama serve / ollama pull messages.
- Update chat():
- Omit keep_alive for cloud (local-only option).
- Map ResponseError status codes: 401/403 → key rejected; 404 → model unavailable; 429 → rate limit; 5xx → cloud service error.
- Provider-specific connection error messages.
- Optional: one bounded retry with backoff for 429/503.
2. Wire up the CLI
src/doxify/cli.py
- Add --provider to doctor, compile, readme, and scout.
- Make --model optional (None) so the provider decides the default.
- Update --host help to say “Ollama endpoint URL”.
- Construct OllamaClient inside the existing try blocks so config errors produce friendly DoxifyError output.
- Pass the resolved client.model to compile_repo and scout_repo so manifests record the actual model.
- In doctor, show Provider, Endpoint, Model, and API key (set/missing) rows; never print the key.
- Print a cloud warning before inference:
Cloud mode sends evidence cards (file paths, snippets, commit messages, dependency names) to Ollama Cloud. Do not use it for confidential repositories. Cloud usage may incur charges.
-scan,verify,attest, andexportstay unchanged and remain fully offline.
3. Record provenance in artifacts
src/doxify/models.py
- Add optional fields with defaults:
- SessionManifest.provider: str = "local"
- SessionManifest.model_host: str | None = None
- ScoutReport.provider and ScoutReport.model_host
- Existing sessions and tests remain readable because the fields have defaults.
src/doxify/pipeline.py / src/doxify/scout.py
- Populate provider/host in manifests and scout reports.
- Use "none" / None for scan-only sessions.
- Replace user-visible “Gemma” wording with “the model” in scout errors and output, since --model may select a non-Gemma cloud model.
src/doxify/exporter.py
- Change “Compiled locally with Doxify” to provider-neutral wording such as “Compiled with Doxify.” The manifest carries the actual provider.
4. Tests
Add tests/test_ollama_client.py and a small CLI test, all network-free:
- Local defaults resolve to gemma3:1b + http://[REDACTED]:11434.
- Cloud defaults resolve to gemma4:31b + https://ollama.com.
- Cloud without OLLAMA_API_KEY produces an actionable failure.
- DOXIFY_PROVIDER, DOXIFY_MODEL, and DOXIFY_OLLAMA_HOST overrides work.
- A fake ollama.Client.list verifies cloud health success/failure.
- Simulated ResponseError(401) maps to an API-key message.
- Cloud chat() does not send keep_alive.
- doxify doctor --provider cloud with no key exits 1 without a traceback.
- Update tests/test_scout.py’s _FakeClient with provider / host if the manifest code reads them.
Run:
bash
uv run ruff check .
uv run pytest -q
5. Documentation and privacy messaging
- Update
README.mdwith local and cloud quickstarts: ```bash # Local (unchanged) ollama pull gemma3:1b doxify readme .
# Cloud, no Ollama install
export OLLAMA_API_KEY=...
doxify doctor --provider cloud
doxify readme . --provider cloud
``PLAN.md
- Updatesections that currently say “no cloud API calls / no API keys” to “local by default; opt-in Ollama Cloud mode.”gemma4:31b
- Document thatis the cloud default, that model names differ from CLI aliases likegemma4:cloud`, and that account limits/pricing apply.
Acceptance criteria
-
doxify doctor --provider cloudworks on a machine with no Ollama installed or model downloaded. -
doxify compile/readme/scout --provider cloudcomplete usinggemma4:31b. - Manifests record
provider,model_host, and the resolved model. - Local behavior and all offline commands are unchanged.
- No API key appears in logs, artifacts, or generated Markdown.
- Lint and tests pass.
Out of scope
- OpenAI/Anthropic/OpenRouter adapters; the client will be structured so they can be added later.
- Automatic local-to-cloud fallback, to preserve the local-first privacy guarantee.
- Hosted web UI and browser-side auth.
No changes have been made. Say the word and I’ll implement this plan.
go
{"todos": [{"content": "Implement provider-aware Ollama client with cloud auth and errors", "status": "in_progress", "priority": "high"}, {"content": "Wire --provider, cloud warning, and doctor output into CLI", "status": "pending", "priority": "high"}, {"content": "Record provider/host in manifests and scout reports", "status": "pending", "priority": "high"}, {"content": "Update exports and model-neutral wording", "status": "pending", "priority": "medium"}, {"content": "Add/adjust unit and CLI tests", "status": "pending", "priority": "high"}, {"content": "Update README/PLAN documentation", "status": "pending", "priority": "medium"}, {"content": "Run ruff and pytest", "status": "pending", "p
… [truncated 19 chars]
[
{
"content": "Implement provider-aware Ollama client with cloud auth and errors",
"status": "in_progress",
"priority": "high"
},
{
"content": "Wire --provider, cloud warning, and doctor output into CLI",
"status": "pending",
"priority": "high"
},
{
"content": "Record provider/host in manifests and scout reports",
"status": "pending",
"priority": "high"
},
{
"content": "Update exports and model-neutral wording",
"status": "pending",
"priority": "medium"
},
{
"content": "Add/adjust unit and CLI tests",
"status": "pending
… [truncated 236 chars]
file: ~/Desktop/repos/doxify/src/doxify/ollama_client.py
content:
from __future__ import annotations
import os
from typing import Any, Literal
from ollama import Client, RequestError, ResponseError
from .errors import ModelError
DEFAULT_HOST = "http://127.0.0.1:11434"
DEFAULT_MODEL = "gemma3:1b"
DEFAULT_CLOUD_HOST = "https://ollama.com"
DEFAULT_CLOUD_MODEL = "gemma4:31b"
Provider = Literal["local", "cloud"]
def _resolve_provider(value: str | None) -> Provider:
normalized = (value or "local").strip().lower()
if normalized in {"local", "ollama", "ollama-local"}:
return "local"
if normalized in {"cloud", "ollama-cloud"}:
return "cloud"
raise ModelError(f"Unknown provider {value!r}. Use 'local' or 'cloud'.")
class Ollam
… [truncated 5148 chars]
Wrote file successfully.
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
def _read_draft(path: Path | None) -> str | None:
if path is None:
return None
try:
return path.read_text(encoding="utf-8")
except OSError as exc:
raise typer.BadPa
… [truncated 889 chars]
new:
def _read_draft(path: Path | None) -> str | None:
if path is None:
return None
try:
return path.read_text(encoding="utf-8")
except OSError as exc:
raise typer.BadParameter(f"Could not read draft file: {exc}") from exc
def _warn_cloud(client: OllamaClient) -> None:
if not client.is_cloud:
return
console.print(
"[yellow]Cloud mode sends r
… [truncated 1744 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
draft: Path | None = typer.Option(None, "--draft", help="Optional Markdown notes."),
role: str | None = typer.Option(None, "--role", help="Target role for tone."),
model: str = typer.Optio
… [truncated 981 chars]
new:
draft: Path | None = typer.Option(None, "--draft", help="Optional Markdown notes."),
role: str | None = typer.Option(None, "--role", help="Target role for tone."),
model: str | None = typer.Option(
None,
help=(
"Model to use. Defaults to gemma3:1b locally or "
"gemma4:31b on Ollama Cloud."
),
),
host: str | None = typer.Option(Non
… [truncated 1102 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
draft: Path | None = typer.Option(None, "--draft", help="Optional Markdown notes."),
role: str | None = typer.Option(None, "--role", help="Target role for tone."),
model: str = typer.Optio
… [truncated 1124 chars]
new:
draft: Path | None = typer.Option(None, "--draft", help="Optional Markdown notes."),
role: str | None = typer.Option(None, "--role", help="Target role for tone."),
model: str | None = typer.Option(
None,
help=(
"Model to use. Defaults to gemma3:1b locally or "
"gemma4:31b on Ollama Cloud."
),
),
host: str | None = typer.Option(Non
… [truncated 1245 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
max_releases: int = typer.Option(
12,
"--max-releases",
help="Maximum number of GitHub releases to analyze.",
),
model: str = typer.Option(DEFAULT_MODEL, help="Olla
… [truncated 1209 chars]
new:
max_releases: int = typer.Option(
12,
"--max-releases",
help="Maximum number of GitHub releases to analyze.",
),
model: str | None = typer.Option(
None,
help=(
"Model to use. Defaults to gemma3:1b locally or "
"gemma4:31b on Ollama Cloud."
),
),
host: str | None = typer.Option(None, help="Ollama endpoint UR
… [truncated 1319 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/models.py
old:
created_at: str
updated_at: str
repo: RepoSummary
model: str
status: str = "created"
new:
created_at: str
updated_at: str
repo: RepoSummary
model: str
provider: str = "local"
model_host: str | None = None
status: str = "created"
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/models.py
old:
repo: RepoSummary
model: str
created_at: str
sources: list[ScoutSource] = Field(default_factory=list)
new:
repo: RepoSummary
model: str
created_at: str
provider: str = "local"
model_host: str | None = None
sources: list[ScoutSource] = Field(default_factory=list)
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/pipeline.py
old:
manifest = SessionManifest(
created_at=now,
updated_at=now,
repo=scan.repo,
model=model,
status="scanned",
)
new:
manifest = SessionManifest(
created_at=now,
updated_at=now,
repo=scan.repo,
model=model,
provider="none",
status="scanned",
)
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/pipeline.py
old:
manifest = SessionManifest(
created_at=now,
updated_at=now,
repo=scan.repo,
model=model,
status="running",
)
new:
manifest = SessionManifest(
created_at=now,
updated_at=now,
repo=scan.repo,
model=model,
provider=client.provider,
model_host=client.host,
status="running",
)
Edit applied successfully.
$ sed -n '660,800p' src/doxify/scout.py; sed -n '900,960p' src/doxify/scout.py; sed -n '960,1060p' src/doxify/scout.py
return _RawChange(
title=title,
quote=quote,
apis=apis,
migration=str(migration).strip() if migration else None,
severity=severity,
)
def parse_changes_response(text: str) -> list[_RawChange]:
cleaned = text.strip()
cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned, flags=re.IGNORECASE)
cleaned = re.sub(r"\s*```$", "", cleaned)
starts = sorted(
{index for index in (cleaned.find("{"), cleaned.find("[")) if index >= 0}
)
if not starts:
raise ScoutError("Gemma did not return JSON.")
payload: Any = None
last_error: json.JSONDecodeError | None = None
for start in starts:
try:
payload, _ = json.JSONDecoder().raw_decode(cleaned[start:])
break
except json.JS
… [truncated 9520 chars]
$ sed -n '910,950p' src/doxify/scout.py | cat -n
1 return ScoutScore(expected=expected, extracted=extracted, touched=touched, missed=missed)
2
3
4 def build_report(
5 *,
6 package: str,
7 github_repo: str | None,
8 from_version: str,
9 to_version: str,
10 repo: RepoSummary,
11 model: str,
12 sources: list[ReleaseNote],
13 changes: list[BreakingChange],
14 files_scanned: int,
15 score: ScoutScore | None,
16 warnings: list[str],
17 ) -> ScoutReport:
18 touching = [change for change in changes if change.matches]
19 unique_apis = {api.casefold() for change in changes for api in change.apis}
20 return ScoutReport(
21 package=package,
22 github_repo=github_repo,
2
… [truncated 681 chars]
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
if not starts:
raise ScoutError("Gemma did not return JSON.")
new:
if not starts:
raise ScoutError("The model did not return JSON.")
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
raise ScoutError("Gemma returned invalid JSON.") from last_error
new:
raise ScoutError("The model returned invalid JSON.") from last_error
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
raise ScoutError("Gemma returned an unexpected JSON shape.")
new:
raise ScoutError("The model returned an unexpected JSON shape.")
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
raise ScoutError(f"Gemma could not analyze {note.title}: {exc}") from exc
new:
raise ScoutError(f"The model could not analyze {note.title}: {exc}") from exc
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
warnings.append(f"{note.title}: Gemma returned an unparseable response.")
new:
warnings.append(f"{note.title}: The model returned an unparseable response.")
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
"Gemma returned no parseable breaking-change JSON. "
new:
"The model returned no parseable breaking-change JSON. "
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
f"- Extracted by Gemma: {len(score.extracted)}/{len(score.expected)}",
new:
f"- Extracted by the model: {len(score.extracted)}/{len(score.expected)}",
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
f"- Gemma extracted {report.summary.changes} breaking change(s) with "
new:
f"- The model extracted {report.summary.changes} breaking change(s) with "
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
repo: RepoSummary,
model: str,
sources: list[ReleaseNote],
changes: list[BreakingChange],
files_scanned: int,
score: ScoutScore | None,
warnings: list[str],
) -> ScoutRepor
… [truncated 364 chars]
new:
repo: RepoSummary,
model: str,
provider: str,
model_host: str | None,
sources: list[ReleaseNote],
changes: list[BreakingChange],
files_scanned: int,
score: ScoutScore | None,
warnings: list[str],
) -> ScoutReport:
touching = [change for change in changes if change.matches]
unique_apis = {api.casefold() for change in changes for api in change.apis}
re
… [truncated 269 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
manifest = SessionManifest(
created_at=now,
updated_at=now,
repo=context.summary,
model=model,
status="notes_fetched",
)
new:
manifest = SessionManifest(
created_at=now,
updated_at=now,
repo=context.summary,
model=model,
provider=getattr(client, "provider", "local"),
model_host=getattr(client, "host", None),
status="notes_fetched",
)
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
repo=context.summary,
model=model,
sources=notes,
changes=changes,
new:
repo=context.summary,
model=model,
provider=getattr(client, "provider", "local"),
model_host=getattr(client, "host", None),
sources=notes,
changes=changes,
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/scout.py
old:
f"Repository: `{report.repo.name}` · Model: `{report.model}` · "
f"Generated: {report.created_at}",
new:
f"Repository: `{report.repo.name}` · Model: `{report.model}` · "
f"Provider: `{report.provider}` · Generated: {report.created_at}",
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/exporter.py
old:
"> Compiled locally with Doxify. Citations point to the evidence appendix.",
new:
"> Compiled with Doxify. Citations point to the evidence appendix.",
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/console.py
old:
table.add_row("Repository", result.manifest.repo.name)
table.add_row("Sections", str(summary.sections))
table.add_row("Evidence cards", str(summary.evidence_cards))
new:
table.add_row("Repository", result.manifest.repo.name)
table.add_row(
"Model",
f"{result.manifest.model} ({result.manifest.provider})",
)
table.add_row("Sections", str(summary.sections))
table.add_row("Evidence cards", str(summary.evidence_cards))
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/errors.py
old:
class ModelError(DoxifyError):
"""Ollama is unavailable or the requested model is missing."""
new:
class ModelError(DoxifyError):
"""The configured Ollama local or cloud endpoint is unavailable."""
Edit applied successfully.
$ uv run ruff check .
All checks passed!
$ uv run pytest -q
.................... [100%]
20 passed in 5.13s
file: ~/Desktop/repos/doxify/tests/test_ollama_client.py
content:
from __future__ import annotations
from types import SimpleNamespace
from typing import Any
import pytest
from ollama import ResponseError
from typer.testing import CliRunner
import doxify.cli as cli
import doxify.ollama_client as ollama_client
from doxify.errors import ModelError
from doxify.ollama_client import OllamaClient
def _use_temp_env(monkeypatch: pytest.MonkeyPatch) -> None:
for name in (
"DOXIFY_PROVIDER",
"DOXIFY_MODEL",
"DOXIFY_OLLAMA_HOST",
"OLLAMA_API_KEY",
):
monkeypatch.delenv(name, raising=False)
def _install_fake_client(
monkeypatch: pytest.MonkeyPatch,
*,
models: list[str] | Exception = (),
chat_respon
… [truncated 5150 chars]
Wrote file successfully.
$ uv run ruff check .
F401 [*] `.prompts.scout_batch_messages` imported but unused
--> src/doxify/scout.py:31:22
|
29 | from .ollama_client import OllamaClient
30 | from .paths import is_secret, is_skippable, read_text_limited
31 | from .prompts import scout_batch_messages
| ^^^^^^^^^^^^^^^^^^^^
32 | from .session import (
33 | prepare_output_dir,
|
help: Remove unused import: `.prompts.scout_batch_messages`
|
30 | from .paths import is_secret, is_skippable, read_text_limited
- from .prompts import scout_batch_messages
31 | from .session import (
|
F821 Undefined name `scout_messages`
--> src/doxify/scout.py:737:21
|
735 | try:
736 | response = client.chat(
737 | scout_messages(package, from_version, to_version,
… [truncated 196 chars]
$ uv run pytest -q
.............................F. [100%]
=================================== FAILURES ===================================
_______________ test_scout_repo_writes_report_and_scores_replay ________________
tmp_path = PosixPath('/tmp/pytest-of-[user]/pytest-9/test_scout_repo_writes_report_0')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x77a56bab1570>
def test_scout_repo_writes_report_and_scores_replay(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
repo_path = tmp_path / "demo"
repo_path.mkdir()
_make_git_repo(repo_path)
notes = tmp_path / "react-notes.md"
notes.write_text(
"## Breaking Changes\n\n"
"Removed: `ReactDOM.render` in favor of
… [truncated 3121 chars]
$ sed -n '20,40p' src/doxify/scout.py; rg -n "scout_batch_messages|scout_messages" src/doxify tests
CodeMatch,
ReleaseNote,
RepoSummary,
ScoutReport,
ScoutScore,
ScoutSource,
ScoutSummary,
SessionManifest,
)
from .ollama_client import OllamaClient
from .paths import is_secret, is_skippable, read_text_limited
from .prompts import scout_batch_messages
from .session import (
prepare_output_dir,
utc_now,
write_json,
write_manifest,
write_text,
)
GITHUB_API = "https://api.github.com/repos/{repo}/releases"
src/doxify/prompts.py:141:def scout_batch_messages(
src/doxify/scout.py:31:from .prompts import scout_batch_messages
src/doxify/scout.py:788: scout_batch_messages(package, from_version, to_version, batch_text),
$ git diff -- src/doxify/scout.py | head -40; git status --short
M src/doxify/cli.py
M src/doxify/console.py
M src/doxify/errors.py
M src/doxify/exporter.py
M src/doxify/git_repo.py
M src/doxify/models.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/prompts.py
?? benchmarks/
?? src/doxify/scout.py
?? tests/test_ollama_client.py
?? tests/test_scout.py
$ sed -n '100,230p' src/doxify/prompts.py
"Every sentence must cite evidence. Do not add new claims.\n\n"
f"Evidence cards:\n{format_cards(cards)}\n\n"
f"Previous output:\n{previous[:3000]}"
),
},
]
SCOUT_SYSTEM_PROMPT = """You are a release-note analyst working inside a compiler.
Your job is to extract breaking changes from release notes, not to summarize
the release or recommend an upgrade.
Rules:
- Only include changes that can break existing code or require a migration.
- Every change must include a verbatim quote copied from the release notes.
- Every change must include at least one exact API, identifier, package path,
or config key affected by the change.
- Do not invent APIs, versions, migration steps, or quotes.
- Do not include new features, bu
… [truncated 2642 chars]
$ sed -n '700,830p' src/doxify/scout.py
raise ScoutError("The model returned an unexpected JSON shape.")
changes: list[_RawChange] = []
for item in raw_changes:
change = _coerce_raw_change(item)
if change is not None:
changes.append(change)
return changes
def build_batches(
notes: list[ReleaseNote],
*,
max_chars: int = MAX_BATCH_CHARS,
) -> list[tuple[str, list[ReleaseNote]]]:
entries: list[tuple[ReleaseNote, str]] = []
for note in notes:
chunks = release_chunks(note)
if not chunks:
continue
if len(note.body) < 500 and not _BREAKING_RE.search(note.body):
continue
for chunk in chunks:
header = f"## Release {note.version}\nSource: {note.url}\n\n"
entries.append((note, header + chunk
… [truncated 3820 chars]
$ stat -c '%y %n' src/doxify/prompts.py src/doxify/scout.py src/doxify/ollama_client.py
2026-10-24 08:56:04.078893508 +0100 src/doxify/prompts.py
2026-10-24 08:56:59.097637687 +0100 src/doxify/scout.py
2026-10-24 08:53:29.105380338 +0100 src/doxify/ollama_client.py
$ rg -n "MAX_BATCH_CHARS|class _RawChange|release:" src/doxify/scout.py; sed -n '600,705p' src/doxify/scout.py; sed -n '830,900p' src/doxify/scout.py
50:MAX_BATCH_CHARS = 9_000
160: prerelease: bool
174:class _RawChange:
180: release: str | None,
713: max_chars: int = MAX_BATCH_CHARS,
743: if raw.release:
def _normalize_quote(value: str) -> str:
value = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", value)
value = re.sub(r"[`*_~#>]", "", value)
value = re.sub(r"\s+", " ", value)
return html.unescape(value).strip().casefold()
def quote_in_notes(quote: str, body: str) -> bool:
normalized = _normalize_quote(quote)
return len(normalized) >= 12 and normalized in _normalize_quote(body)
def normalize_api_tokens(values: Any) -> list[str]:
if isinstance(values, str):
raw_values: list[Any] = [values]
elif isinstance(values, list):
raw_values = values
else:
return []
to
… [truncated 5479 chars]
$ sed -n '755,860p' src/doxify/scout.py | cat -n
1 if quote_in_notes(raw.quote, note.body):
2 return note
3 return None
4
5
6 def extract_breaking_changes(
7 client: OllamaClient,
8 notes: list[ReleaseNote],
9 package: str,
10 from_version: str,
11 to_version: str,
12 *,
13 max_model_calls: int = MAX_MODEL_CALLS,
14 ) -> tuple[list[BreakingChange], list[str]]:
15 changes: list[BreakingChange] = []
16 warnings: list[str] = []
17 seen: set[tuple[str, tuple[str, ...]]] = set()
18 calls = 0
19 successful_calls = 0
20 stop = False
21 batches = build_batches(notes)
22 if not batches:
23 warnings.append("No release notes contained breaking-change sectio
… [truncated 3563 chars]
$ uv run ruff check .
All checks passed!
$ rg -n "provider=|model_host=|Extracted by|The model extracted|Provider:" src/doxify/scout.py | head -30
991: provider=provider,
992: model_host=model_host,
1034: f"Provider: `{report.provider}` · Generated: {report.created_at}",
1095: f"- Extracted by the model: {len(score.extracted)}/{len(score.expected)}",
1108: f"- The model extracted {report.summary.changes} breaking change(s) with "
1206: provider=getattr(client, "provider", "local"),
1207: model_host=getattr(client, "host", None),
1239: provider=getattr(client, "provider", "local"),
1240: model_host=getattr(client, "host", None),
$ uv run pytest -q
............................... [100%]
31 passed in 1.76s
file: ~/Desktop/repos/doxify/README.md
content:
# Doxify
Doxify compiles evidence-backed READMEs and portfolio case studies from local
git repositories. It also scouts breaking dependency upgrades against your
code.
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
## Install
Requires Python 3.10+ and git.
```bash
uv sync
uv run doxify --help
```
Or install the package and run `doxify` directly.
## Local mode (default)
Local mode uses Ollama and `gemma3:1b`. Nothing about the repository leaves
your machine.
```bash
ollama serve
ollama pull gemma3:1b
doxify doctor
doxify readme /pat
… [truncated 1445 chars]
Wrote file successfully.
$ sed -n '1,18p' PLAN.md; sed -n '64,72p' PLAN.md; sed -n '220,236p' PLAN.md; sed -n '1840,1850p' PLAN.md; sed -n '2440,2455p' PLAN.md; sed -n '2195,2205p' PLAN.md; sed -n '2272,2282p' PLAN.md
# Doxify — Hacktoberfest Week 3 Project Plan
> Working title: **Doxify**
> Tagline: **Portfolio case studies you can defend.**
> Status: CLI MVP implemented; hosted mode and optional integrations planned.
> Plan date: 2026-10-22.
> Last implementation update: 2026-10-23.
> Hackathon deadline: 2026-10-25, 11:59 PM PDT = 2026-10-26, 7:59 AM WAT.
> Target category: **Best Use of Gemma**.
> Demo input repo: `~/Desktop/repos/scholarship-mcp`.
> **Implementation status:** The working MVP is a local CLI in `src/doxify` using
> Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. The FastAPI and
> Next.js sections below describe the deferred hosted UI, not the current build.
> Current commands: `doctor`, `scan`, `compile`, `readme`, `verify`, `attest`,
> and `export`.
… [truncated 2558 chars]
$ sed -n '2545,2570p' PLAN.md
These are the current local development and verification commands.
```bash
cd ~/Desktop/repos/doxify
# Confirm the local model and Python tooling
ollama list
ollama --version
python3 --version
uv --version
# Install the locked environment
uv sync --frozen
# Check local dependencies and Ollama
uv run doxify doctor
# Scan the demo repository without calling the model
uv run doxify scan ~/Desktop/repos/scholarship-mcp
# Compile the full case study and defense pack
uv run doxify compile ~/Desktop/repos/scholarship-mcp
# Generate an evidence-backed README into /tmp
uv run doxify readme ~/Desktop/repos/scholarship-mcp \
--output /tmp/doxify-readme.md
file: ~/Desktop/repos/doxify/PLAN.md
old:
> Status: CLI MVP implemented; hosted mode and optional integrations planned.
> Plan date: 2026-10-22.
> Last implementation update: 2026-10-23.
new:
> Status: CLI MVP implemented; opt-in Ollama Cloud inference added; hosted mode planned.
> Plan date: 2026-10-22.
> Last implementation update: 2026-10-24.
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
> **Implementation status:** The working MVP is a local CLI in `src/doxify` using
> Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. The FastAPI and
> Next.js sections below describe the def
… [truncated 134 chars]
new:
> **Implementation status:** The working MVP is a local CLI in `src/doxify` using
> Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. Local inference is
> still the default; `--provider cloud` routes model calls to Ollama Cloud for
> users who do not want to install Ollama locally. The FastAPI and Next.js
> sections below describe the deferred hosted UI, not the current build.
> Current c
… [truncated 92 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
The current implementation is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, and a local `gemma3:1b` model through Ollama. A future hosted mode may add FastAPI and Next.js, but the shi
… [truncated 219 chars]
new:
The current implementation is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, and a local `gemma3:1b` model through Ollama. Local inference remains the default: it reads a local repository, writes compile artifacts to a local session directory, and calls only the local Ollama server. Users without a local Ollama install can explicitly opt into Ollama Cloud with `--provider cloud` an
… [truncated 170 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
Using a cloud API would undermine the core story. `gemma3:1b` via Ollama is small enough to run on the target laptop and open enough to be inspectable and portable.
The submission should make this ex
… [truncated 57 chars]
new:
Cloud inference is offered only as an explicit opt-in for users who cannot or do not want to install Ollama. The default remains `gemma3:1b` via local Ollama: it is small enough to run on the target laptop and open enough to be inspectable and portable. When `--provider cloud` is selected, the same Gemma-family workflow runs on Ollama Cloud (`gemma4:31b` by default).
The default submission demo s
… [truncated 92 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
### 16.1 Local-only guarantees
- API binds to `127.0.0.1`.
- No telemetry.
- No analytics.
- No cloud API calls.
- No GitHub OAuth.
- No repository upload.
- Only network call: `http://127.0.0.1:1143
… [truncated 74 chars]
new:
### 16.1 Default local-first guarantees
- API binds to `127.0.0.1`.
- No telemetry.
- No analytics.
- No cloud API calls unless the user explicitly passes `--provider cloud` or sets `DOXIFY_PROVIDER=cloud`.
- No GitHub OAuth.
- No repository upload.
- Default network call: `http://127.0.0.1:11434` for Ollama.
- The app should work with Wi-Fi off in local mode.
### 16.2 Opt-in Ollama Cloud
- Act
… [truncated 571 chars]
Edit applied successfully.
$ rg -n "16\.[0-9]" PLAN.md | head -30
1840:### 16.1 Default local-first guarantees
1851:### 16.2 Opt-in Ollama Cloud
1861:### 16.3 Path safety
1869:### 16.3 Secret handling
1886:### 16.4 Output safety
1899:### 16.5 Model safety
$ sed -n '1861,1905p' PLAN.md
### 16.3 Path safety
- Resolve the repo path with `pathlib.Path.resolve(strict=True)` and verify it is a directory.
- Reject `/`, the home directory, and the tool’s own repo.
- Reject paths outside the user’s selected root if a root is configured.
- Do not follow symlinks outside the repo.
- Use `asyncio.create_subprocess_exec("git", [...])` or `subprocess.run(["git", ...])`, never string interpolation into a shell and never `shell=True`.
### 16.3 Secret handling
Skip or redact:
- `.env`, `.env.*`
- `*.pem`, `*.key`, `*.p12`
- files with names containing `secret`, `credential`, `token`
- AWS keys, GitHub tokens, private keys, generic high-entropy strings
- database connection strings
- user-specified exclusion terms
Redaction should happen before evidence cards are created, not just b
… [truncated 718 chars]
file: ~/Desktop/repos/doxify/PLAN.md
old:
### 16.3 Secret handling
new:
### 16.4 Secret handling
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
### 16.4 Output safety
new:
### 16.5 Output safety
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
### 16.5 Model safety
new:
### 16.6 Model safety
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
### 22.8 Ollama is unavailable on the judge’s machine
Mitigation:
- precomputed demo files,
- video,
- clear setup instructions,
- a “bring your own model” note,
- the app can still show the precomp
… [truncated 46 chars]
new:
### 22.8 Ollama is unavailable on the judge’s machine
Mitigation:
- precomputed demo files,
- video,
- clear setup instructions,
- a “bring your own model” note,
- `--provider cloud` with `OLLAMA_API_KEY` for machines without Ollama,
- the app can still show the precomputed session in read-only mode if implemented.
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- Show `ollama ps` or the local model call.
- Say: “No API keys, no upload, no per-token cost.”
new:
- Show `ollama ps` or the local model call.
- Say: “In local mode: no API keys, no upload, no per-token cost.”
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- Private repos and career claims.
- No upload, no API keys, no per-token cost.
- Offline.
new:
- Private repos and career claims.
- Local mode by default: no upload, no API keys, no per-token cost.
- Opt-in Ollama Cloud mode for machines without a local model.
- Offline in local mode.
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
# Check local dependencies and Ollama
uv run doxify doctor
# Scan the demo repository without calling the model
uv run doxify scan ~/Desktop/repos/scholarship-mcp
# Compile the full case
… [truncated 89 chars]
new:
# Check local dependencies and Ollama
uv run doxify doctor
# Or check Ollama Cloud without installing Ollama locally
export OLLAMA_API_KEY="[redacted]"
uv run doxify doctor --provider cloud
# Scan the demo repository without calling the model
uv run doxify scan ~/Desktop/repos/scholarship-mcp
# Compile the full case study and defense pack
uv run doxify compile ~/Deskto
… [truncated 148 chars]
Edit applied successfully.
$ rg -n "no cloud|No cloud|no API keys|No API keys|Only network call|local-only|local only" PLAN.md | head -30
239:- no API keys,
746:- No cloud services.
760:- The whole app still runs locally with no cloud API, no API keys, and no per-token cost.
1845:- No cloud API calls unless the user explicitly passes `--provider cloud` or sets `DOXIFY_PROVIDER=cloud`.
2213:- Say: “In local mode: no API keys, no upload, no per-token cost.”
2290:- Local mode by default: no upload, no API keys, no per-token cost.
2427:- local-only is central, not decorative,
2475:- local-only architecture,
$ sed -n '228,250p' PLAN.md; sed -n '736,765p' PLAN.md
- the career claims may be sensitive,
- the output may mention internal architecture,
- the user may be offline.
Cloud inference is offered only as an explicit opt-in for users who cannot or do not want to install Ollama. The default remains `gemma3:1b` via local Ollama: it is small enough to run on the target laptop and open enough to be inspectable and portable. When `--provider cloud` is selected, the same Gemma-family workflow runs on Ollama Cloud (`gemma4:31b` by default).
The default submission demo should make local mode explicit:
- local model,
- local repo,
- no network calls by default,
- no API keys,
- no per-token cost.
### 4.4 What must be new
The submitted project must be a new repository created during the entry period.
`scholarship-mcp` is an existing repository. It i
… [truncated 1589 chars]
$ sed -n '2420,2432p' PLAN.md; sed -n '2468,2480p' PLAN.md
### 22.4 The lane is crowded
Mitigation:
- the story is anti-slop, not “AI writes your portfolio,”
- the demo shows a false claim being rejected,
- the output includes an interview defense pack,
- local-only is central, not decorative,
- the DEV post explains the difference clearly.
### 22.5 The demo repo has no README
Mitigation:
- `--provider cloud` with `OLLAMA_API_KEY` for machines without Ollama,
- the app can still show the precomputed session in read-only mode if implemented.
### 22.9 Privacy concerns
Mitigation:
- local-only architecture,
- no telemetry,
- no upload,
- secret skipping,
- export warning,
- no GitHub OAuth.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- no network calls by default,
- no API keys,
- no per-token cost.
new:
- no network calls by default,
- no API keys in local mode,
- no per-token cost in local mode.
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- No cloud services.
- No authentication.
new:
- No cloud services in local mode; opt-in Ollama Cloud inference only.
- No authentication beyond `OLLAMA_API_KEY` in cloud mode.
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- The whole app still runs locally with no cloud API, no API keys, and no per-token cost.
new:
- The whole app runs locally by default with no cloud API, no API keys, and no per-token cost. Cloud mode is an explicit alternative.
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- local-only is central, not decorative,
new:
- local-first is central, not decorative,
Edit applied successfully.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- local-only architecture,
- no telemetry,
- no upload,
- secret skipping,
- export warning,
- no GitHub OAuth.
new:
- local-first architecture,
- no telemetry,
- no upload in local mode,
- explicit cloud warning before any upload,
- secret skipping,
- export warning,
- no GitHub OAuth.
Edit applied successfully.
$ rg -n "127\.0\.0\.1:11434|Ollama HTTP|Ollama: " PLAN.md | head -20
232:Cloud inference is offered only as an explicit opt-in for users who cannot or do not want to install Ollama. The default remains `gemma3:1b` via local Ollama: it is small enough to run on the target laptop and open enough to be inspectable and portable. When `--provider cloud` is selected, the same Gemma-family workflow runs on Ollama Cloud (`gemma4:31b` by default).
759:- The Ollama HTTP API is straightforward with `httpx.AsyncClient`.
909:- Ollama: `http://127.0.0.1:11434`
1848:- Default network call: `http://127.0.0.1:11434` for Ollama.
2893:- Ollama: https://ollama.com
$ sed -n '903,914p' PLAN.md
```
### 9.4 Runtime and ports
- FastAPI: `http://127.0.0.1:8787`
- Next.js dev server: `http://127.0.0.1:3000`
- Ollama: `http://127.0.0.1:11434`
The FastAPI server must bind to `127.0.0.1`, not `0.0.0.0`. Next.js should also bind to `127.0.0.1` for the local demo.
`next.config.ts` rewrites `/api/:path*` to `http://127.0.0.1:8787/api/:path*`. This keeps the browser on one origin and avoids a CORS package or permissive CORS middleware. The frontend calls relative `/api/...` URLs.
file: ~/Desktop/repos/doxify/PLAN.md
old:
- Ollama: `http://127.0.0.1:11434`
new:
- Ollama (local mode): `http://127.0.0.1:11434`
- Ollama Cloud (opt-in): `https://ollama.com`
Edit applied successfully.
$ sed -n '55,120p' src/doxify/cli.py; sed -n '135,230p' src/doxify/cli.py; sed -n '260,340p' src/doxify/cli.py
/ f"scout-{safe_package}-{safe_from}-to-{safe_to}-{stamp}"
)
def _read_draft(path: Path | None) -> str | None:
if path is None:
return None
try:
return path.read_text(encoding="utf-8")
except OSError as exc:
raise typer.BadParameter(f"Could not read draft file: {exc}") from exc
def _warn_cloud(client: OllamaClient) -> None:
if not client.is_cloud:
return
console.print(
"[yellow]Cloud mode sends repository evidence (file paths, snippets, "
"commit messages, dependency names) to Ollama Cloud. Do not use it for "
"confidential repositories. Cloud usage may incur charges.[/yellow]"
)
@app.command()
def doctor(
model: str | None = typer.Option(
None,
help=(
"Model to
… [truncated 7406 chars]
$ sed -n '100,150p' src/doxify/cli.py; sed -n '220,275p' src/doxify/cli.py
git_path = shutil.which("git")
table.add_row("git", "ok" if git_path else "missing", git_path or "install git")
table.add_row("Python", "ok", "local CLI runtime")
try:
client = OllamaClient(model=model, host=host, provider=provider)
except DoxifyError as exc:
error(str(exc))
raise typer.Exit(code=1) from exc
table.add_row("Provider", client.label, client.host)
table.add_row("Model", client.model, "cloud" if client.is_cloud else "local")
if client.is_cloud:
table.add_row(
"API key",
"ok" if client.api_key else "missing",
"OLLAMA_API_KEY",
)
healthy, detail = client.health()
table.add_row("Endpoint", "ok" if healthy else "missing", detail)
console.print(table)
if not
… [truncated 2872 chars]
$ env -u OLLAMA_API_KEY uv run doxify doctor --provider cloud; echo "exit=$?"
Doxify doctor
┏━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Check ┃ Status ┃ Detail ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ git │ ok │ /usr/bin/git │
│ Python │ ok │ local CLI runtime │
│ Provider │ Ollama Cloud │ https://ollama.com │
│ Model │ gemma4:31b │ cloud │
│ API key │ missing │ OLLAMA_API_KEY │
│ Endpoint │ missing │ Ollama Cloud requires an API key. Create on
… [truncated 260 chars]
$ env -u OLLAMA_API_KEY uv run doxify doctor --provider mainframe; echo "exit=$?"
Error: Unknown provider 'mainframe'. Use 'local' or 'cloud'.
exit=1
file: ~/Desktop/repos/doxify/tests/test_ollama_client.py
old:
from doxify.errors import ModelError
from doxify.ollama_client import OllamaClient
new:
from doxify.errors import DoxifyError, ModelError
from doxify.ollama_client import OllamaClient
Edit applied successfully.
file: ~/Desktop/repos/doxify/tests/test_ollama_client.py
old:
def test_doctor_cloud_without_key_exits_cleanly(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_temp_env(monkeypatch)
runner = CliRunner()
result = runner.invoke(cli.app, ["doctor",
… [truncated 103 chars]
new:
def test_doctor_cloud_without_key_exits_cleanly(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_use_temp_env(monkeypatch)
runner = CliRunner()
result = runner.invoke(cli.app, ["doctor", "--provider", "cloud"])
assert result.exit_code == 1
assert "OLLAMA_API_KEY" in result.output
def test_compile_uses_resolved_cloud_model(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
… [truncated 738 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/tests/test_ollama_client.py
old:
from __future__ import annotations
from types import SimpleNamespace
from typing import Any
new:
from __future__ import annotations
from pathlib import Path
from types import SimpleNamespace
from typing import Any
Edit applied successfully.
file: ~/Desktop/repos/doxify/tests/test_ollama_client.py
old:
def test_compile_uses_resolved_cloud_model(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
new:
def test_compile_uses_resolved_cloud_model(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
Edit applied successfully.
$ uv run ruff check .
All checks passed!
$ uv run pytest -q
................................ [100%]
32 passed in 3.53s
$ git status --short; git diff --stat
M PLAN.md
M README.md
M src/doxify/cli.py
M src/doxify/console.py
M src/doxify/errors.py
M src/doxify/exporter.py
M src/doxify/git_repo.py
M src/doxify/models.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/prompts.py
?? benchmarks/
?? src/doxify/scout.py
?? tests/test_ollama_client.py
?? tests/test_scout.py
PLAN.md | 83 +++++++++++-------
README.md | 82 ++++++++++++++++++
src/doxify/cli.py | 199 ++++++++++++++++++++++++++++++++++++++++----
src/doxify/console.py | 46 ++++++++++
src/doxify/errors.py | 6 +-
src/doxify/exporter.py | 2 +-
src/doxify/git_repo.py | 4 +-
src/doxify/models.py | 94 +++++++++++++++++++++
src/doxify/ollama_client.py | 134 ++++++++++++++++
… [truncated 154 chars]
$ git diff -- src/doxify/ollama_client.py src/doxify/models.py src/doxify/pipeline.py src/doxify/exporter.py src/doxify/errors.py src/doxify/console.py | head -300
diff --git a/src/doxify/console.py b/src/doxify/console.py
index e316ae7..86c5a66 100644
--- a/src/doxify/console.py
+++ b/src/doxify/console.py
@@ -8,6 +8,7 @@ from rich.table import Table
from .models import CompileResult, RepoSummary, ScanResult
from .readme import ReadmeResult
+from .scout import ScoutResult
console = Console()
@@ -45,6 +46,10 @@ def print_compile_summary(result: CompileResult, output: Path) -> None:
table.add_column("Field", style="cyan")
table.add_column("Value")
table.add_row("Repository", result.manifest.repo.name)
+ table.add_row(
+ "Model",
+ f"{result.manifest.model} ({result.manifest.provider})",
+ )
table.add_row("Sections", str(summary.sections))
table.add_row("Evidence cards", str(summary.evidence_cards))
… [truncated 9311 chars]
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
if client.is_cloud:
table.add_row(
"API key",
"ok" if client.api_key else "missing",
"OLLAMA_API_KEY",
)
new:
if client.is_cloud:
table.add_row(
"API key",
"set" if client.api_key else "missing",
"OLLAMA_API_KEY (checked on first call)",
)
Edit applied successfully.
$ OLLAMA_API_KEY=[redacted] uv run doxify doctor --provider cloud; echo "exit=$?"
Doxify doctor
┏━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Check ┃ Status ┃ Detail ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ git │ ok │ /usr/bin/git │
│ Python │ ok │ local CLI runtime │
│ Provider │ Ollama Cloud │ https://ollama.com │
│ Model │ gemma4:31b │ cloud │
│ API key │ set │ OLLAMA_API_KEY (checked on first call) │
│ Endpoint │ ok │ Ollama Cloud is reachable and gemma4:31b is
… [truncated 179 chars]
$ uv run ruff check . && uv run pytest -q
All checks passed!
................................ [100%]
32 passed in 4.02s
$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
file: ~/Desktop/repos/doxify/src/doxify/ollama_client.py
old:
options: dict[str, Any] = {
"temperature": temperature,
"top_p": 0.9,
"num_ctx": 4096,
"num_predict": num_predict,
"repeat_penalty":
… [truncated 15 chars]
new:
options: dict[str, Any] = {
"temperature": temperature,
"top_p": 0.9,
"num_ctx": 8192 if self.is_cloud else 4096,
"num_predict": num_predict,
"repeat_penalty": 1.1,
}
Edit applied successfully.
$ uv run ruff check . && uv run pytest -q
All checks passed!
................................ [100%]
32 passed in 3.12s
file: ~/Desktop/repos/doxify/src/doxify/ollama_client.py
<path>~/Desktop/repos/doxify/src/doxify/ollama_client.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import os
4: from typing import Any, Literal
5:
6: from ollama import Client, RequestError, ResponseError
7:
8: from .errors import ModelError
9:
10: DEFAULT_HOST = "http://127.0.0.1:11434"
11: DEFAULT_MODEL = "gemma3:1b"
12: DEFAULT_CLOUD_HOST = "https://ollama.com"
13: DEFAULT_CLOUD_MODEL = "gemma4:31b"
14:
15: Provider = Literal["local", "cloud"]
16:
17:
18: def _resolve_provider(value: str | None) -> Provider:
19: normalized = (value
… [truncated 6149 chars]
$ uv run doxify compile --help | sed -n '1,80p'
Usage: doxify compile [OPTIONS] {repo}
Run the full scan, draft, verify, defense, and export pipeline.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ * repo <directory> [required] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --out -o <path> Output directory.
… [truncated 740 chars]
$ uv run doxify doctor --help | sed -n '1,60p'
Usage: doxify doctor [OPTIONS]
Check Python, git, and the configured model provider.
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --model <str> Model to check. Defaults to gemma3:1b locally or │
│ gemma4:31b on Ollama Cloud. │
│ --host <str> Ollama endpoint URL. │
│ --provider <str> Inference backend: local (default) or cloud.
… [truncated 173 chars]
$ tmp=$(mktemp -d); cd "$tmp"; git init -q; git config user.email [redacted-email]; git config user.name T; printf 'print("hi")\n' > app.py; git add app.py; git commit -qm init; uv run --project ~/Desktop/repos/doxify doxify scan "$tmp" --out "$tmp/out" >/tmp/doxify-scan.log 2>&1; code=$?; cat /tmp/doxify-scan.log; echo "exit=$code"; cat "$tmp/out/manifest.json" 2>/dev/null
Scan complete
┌────────────────┬──────────────────────────┐
│ Repository │ tmp.Ogrp0fW6w7 │
│ Commits │ 1 │
│ Timeline │ 2026-10-24 to 2026-10-24 │
│ Tracked files │ 1 │
│ README │ missing │
│ Evidence cards │ 6 │
│ Output │ /tmp/tmp.Ogrp0fW6w7/out │
└────────────────┴──────────────────────────┘
exit=0
{
"schema_version": "1.0",
"tool": "doxify",
"tool_version": "0.1.0",
"created_at": "2026-10-24T08:05:26+00:00",
"updated_at": "2026-10-24T08:05:26+00:00",
"repo": {
"path": "/tmp/tmp.Ogrp0fW6w7",
"name": "tmp.Ogrp0fW6w7",
"head_sha": "c9f512a00e65f55e19438b40dcf860975058b092",
"branch": "main",
"com
… [truncated 413 chars]
$ .venv/bin/python - <<'PY'
from doxify.ollama_client import OllamaClient
from doxify.errors import ModelError
c = OllamaClient(provider='cloud', api_key='[redacted]')
try:
c.chat([{'role':'user','content':'hi'}])
except ModelError as e:
print(type(e).__name__, str(e))
PY
ModelError Ollama Cloud rejected the API key. Check OLLAMA_API_KEY.
{"todos": [{"content": "Implement provider-aware Ollama client with cloud auth and errors", "status": "completed", "priority": "high"}, {"content": "Wire --provider, cloud warning, and doctor output into CLI", "status": "completed", "priority": "high"}, {"content": "Record provider/host in manifests and scout reports", "status": "completed", "priority": "high"}, {"content": "Update exports and model-neutral wording", "status": "completed", "priority": "medium"}, {"content": "Add/adjust unit and CLI tests", "status": "completed", "priority": "high"}, {"content": "Update README/PLAN documentation", "status": "completed", "priority": "medium"}, {"content": "Run ruff and pytest", "status": "comp
… [truncated 29 chars]
[
{
"content": "Implement provider-aware Ollama client with cloud auth and errors",
"status": "completed",
"priority": "high"
},
{
"content": "Wire --provider, cloud warning, and doctor output into CLI",
"status": "completed",
"priority": "high"
},
{
"content": "Record provider/host in manifests and scout reports",
"status": "completed",
"priority": "high"
},
{
"content": "Update exports and model-neutral wording",
"status": "completed",
"priority": "medium"
},
{
"content": "Add/adjust unit and CLI tests",
"status": "com
… [truncated 246 chars]
$ git diff --check
(no output)
Implemented opt-in Ollama Cloud support.
-
--provider cloud/DOXIFY_PROVIDER=clouduseshttps://ollama.comwithOLLAMA_API_KEY; local mode remains the default. - Cloud defaults to
gemma4:31b; local staysgemma3:1b. - Added provider-aware health checks, 401/404/429/5xx error mapping, cloud privacy warning, manifest
provider/model_hostprovenance, doctor rows, README/PLAN docs, and tests.
Usage:
bash
export OLLAMA_API_KEY="..."
uv run doxify doctor --provider cloud
uv run doxify readme /path/to/repo --provider cloud
Verification passed: ruff check, pytest (32 passed), and uv build. No commit made.
GitHub Copilot: you already plan CI. Package doxify verify as a GitHub Action that fails when a README or case study has unsupported claims, and run it on Doxify’s own README. Use Copilot for part of the build, since CI alone is thin.
GitHub Copilot: you already plan CI. Package doxify verify as a GitHub Action that fails when a README or case study has unsupported claims, and run it on Doxify’s own README. Use Copilot for part of the build, since CI alone is thin.
$ git status --short && git log --oneline -8
M PLAN.md
M README.md
M pyproject.toml
M src/doxify/cli.py
M src/doxify/console.py
M src/doxify/errors.py
M src/doxify/exporter.py
M src/doxify/git_repo.py
M src/doxify/models.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/prompts.py
M uv.lock
?? benchmarks/
?? src/doxify/scout.py
?? tests/test_ollama_client.py
?? tests/test_scout.py
f4b1d79 chore(demo): add precomputed nextjs-dashboard compile artifacts
95a5e02 build(ci): add Python 3.10 TOML fallback and package CI
11a198d docs(plan): align roadmap with CLI MVP and README mode
663805d test(readme): cover evidence rendering and temp-session safety
a668d97 feat(core): implement evidence-backed compiler and README output
e845094 Refactor code structure for improved readability and maintainability
$ find .github -type f -maxdepth 3 -print -exec sed -n '1,200p' {} \;
.github/workflows/ci.yml
name: CI
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
test:
name: Lint, test, and build
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Test
run: uv run pytest -q
- name: Build package
run: uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
… [truncated 37 chars]
$ sed -n '1,220p' README.md
# Doxify
Doxify compiles evidence-backed READMEs and portfolio case studies from local
git repositories. It also scouts breaking dependency upgrades against your
code.
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
## Install
Requires Python 3.10+ and git.
```bash
uv sync
uv run doxify --help
```
Or install the package and run `doxify` directly.
## Local mode (default)
Local mode uses Ollama and `gemma3:1b`. Nothing about the repository leaves
your machine.
```bash
ollama serve
ollama pull gemma3:1b
doxify doctor
doxify readme /path/to/repo
```
## Cloud mode (no local Ollama install)
Cloud mode uses Ollama Cloud directly. You d
… [truncated 1345 chars]
$ sed -n '1,260p' src/doxify/verifier.py; sed -n '1,220p' src/doxify/claims.py
from __future__ import annotations
import re
from .models import Claim, ClaimStatus, CompileSummary, EvidenceCard
from .text import content_tokens
NUMBER_RE = re.compile(r"(?<![\w.])(\d[\d,]*(?:\.\d+)?)(?![\w])")
CODE_TOKEN_RE = re.compile(r"`([^`]+)`|\b([a-z]+[A-Z][A-Za-z0-9_]*)\b")
FORBIDDEN_PATTERNS: list[tuple[str, re.Pattern[str], str]] = [
(
"oauth",
re.compile(r"\boauth\b", re.IGNORECASE),
"The cited evidence does not contain an OAuth implementation.",
),
(
"tests pass",
re.compile(r"\b(?:all\s+)?tests?\s+(?:pass|passed|passing)\b", re.IGNORECASE),
"Doxify did not run the project's tests.",
),
(
"deployment",
re.compile(
r"\b(?:deployed|in production|production-ready|live at|shipped t
… [truncated 12471 chars]
$ find output benchmarks -maxdepth 4 -type f | sort | head -80; find output -maxdepth 3 -type d | sort | head -40
benchmarks/scout-react-18-19.json
output/dashboard-scan/evidence.json
output/dashboard-scan/manifest.json
output/next-dashboard/case-study/case-study.md
output/next-dashboard/case-study/claims.json
output/next-dashboard/case-study/defense.json
output/next-dashboard/case-study/defense.md
output/next-dashboard/case-study/evidence.json
output/next-dashboard/case-study/manifest.json
output/next-dashboard/case-study/sections.json
output/next-dashboard/case-study/verification.json
output
output/dashboard-scan
output/next-dashboard
output/next-dashboard/case-study
$ sed -n '1,120p' src/doxify/readme.py; sed -n '300,380p' src/doxify/readme.py
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from .errors import SessionError
from .models import (
Claim,
ClaimStatus,
CompileResult,
EvidenceCard,
EvidenceKind,
RepoSummary,
)
from .text import compact_whitespace
GENERATED_MARKER = "<!-- doxify-generated -->"
CITATION_RE = re.compile(r"\s*\[E\d+\]")
MARKER_RE = re.compile(
r"\s*\[(?:GAP|needs evidence|attested|rejected)\]",
re.IGNORECASE,
)
README_FILENAME = "README.md"
@dataclass
class ReadmeResult:
path: Path
session_dir: Path
content: str
compile_result: CompileResult
omitted_claims: list[Claim]
warnings: list[str]
backup_path: Path | None = None
session_cleaned: bool = False
def buil
… [truncated 4911 chars]
$ sed -n '1,140p' pyproject.toml; sed -n '1,120p' src/doxify/pipeline.py; sed -n '285,380p' src/doxify/pipeline.py
[project]
name = "doxify"
version = "0.1.0"
description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
readme = "README.md"
authors = [
{ name = "Ekomjah Edet", email = "[redacted-email]" }
]
requires-python = ">=3.10"
dependencies = [
"gitpython>=3.2.1",
"ollama>=0.6.3",
"pydantic>=2.14.1",
"rich>=15.0.0",
"sentry-sdk>=2.73.0",
"tomli>=2.5.0",
"typer>=0.27.3",
]
[project.scripts]
doxify = "doxify:main"
[build-system]
requires = ["uv_build>=0.13.1,<0.14.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff>=0.17.2",
]
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "UP", "B", "SIM", "RUF"]
ignore = ["B008"]
f
… [truncated 6576 chars]
$ sed -n '300,400p' src/doxify/cli.py; rg -n "verify|claim|unsupported|needs_evidence|rejected|exit" src/doxify/cli.py src/doxify/console.py src/doxify/readme.py | head -80
"--github-repo",
help="GitHub repository for an unmapped package, for example owner/name.",
),
max_releases: int = typer.Option(
12,
"--max-releases",
help="Maximum number of GitHub releases to analyze.",
),
model: str | None = typer.Option(
None,
help=(
"Model to use. Defaults to gemma3:1b locally or "
"gemma4:31b on Ollama Cloud."
),
),
host: str | None = typer.Option(None, help="Ollama endpoint URL."),
provider: str | None = typer.Option(
None,
"--provider",
help="Inference backend: local (default) or cloud.",
),
force: bool = typer.Option(False, "--force", help="Allow an existing output directory."),
) -> None:
"""Fetch release notes, e
… [truncated 5466 chars]
$ rg -n "doxify verify|GitHub Action|action.yml|composite|verify_session|fail-on|unsupported" . -g '!*.pyc' -g '!uv.lock' -g '!PLAN.md' | head -100
./README.md:70:doxify verify SESSION
./src/doxify/cli.py:30: verify_session,
./src/doxify/cli.py:364: report = verify_session(session)
./src/doxify/pipeline.py:302:def verify_session(output_dir: Path) -> VerificationReport:
$ cat output/next-dashboard/case-study/verification.json | head -80; echo ---; cat output/next-dashboard/case-study/claims.json | head -80
{
"schema_version": "1.0",
"summary": {
"verified": 13,
"attested": 0,
"needs_evidence": 34,
"rejected": 0,
"sections": 6,
"evidence_cards": 23
},
"claims": [
{
"id": "C1",
"text": "Okay, here’s the section text based on the provided evidence cards and goal:",
"section_id": "snapshot",
"evidence_ids": [],
"status": "needs_evidence",
"reason": "No citation token was found.",
"gap_question": "What evidence supports this sentence: Okay, here’s the section text based on the provided evidence cards and goal:?",
"user_answer": null
},
{
"id": "C2",
"text": "“This project, nextjs-dashboard, is a Next.js application focused on creating interactive dashboards.",
"section_id": "snapshot",
… [truncated 6244 chars]
$ sed -n '1,220p' src/doxify/scanner.py; sed -n '1,160p' src/doxify/text.py
from __future__ import annotations
from pathlib import Path
from .evidence import build_evidence
from .git_repo import RepoContext, collect_repo_context, open_repo
from .models import ScanResult
def scan_repository(
path: Path,
*,
max_commits: int = 100,
max_cards: int = 60,
max_file_bytes: int = 120_000,
) -> tuple[RepoContext, ScanResult]:
repo = open_repo(path)
context = collect_repo_context(repo, max_commits=max_commits)
cards = build_evidence(
context,
max_cards=max_cards,
max_file_bytes=max_file_bytes,
)
return context, ScanResult(repo=context.summary, evidence=cards)
from __future__ import annotations
import re
TOKEN_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_+-]*|\d[\d,]*")
STOPWORDS = {
"a",
"an",
"and",
… [truncated 1795 chars]
$ rg -n "doxify-summary|doxify-unresolved|doxify-evidence|doxify-attested|Compiled with Doxify|\[E[0-9]" README.md output/next-dashboard/case-study/case-study.md | head -60
output/next-dashboard/case-study/case-study.md:10:“This project, nextjs-dashboard, is a Next.js application focused on creating interactive dashboards. [needs evidence] It utilizes React and JavaScript for its core functionality. [E3] The repository contains a README at README.md, detailing the project's purpose and setup.”
output/next-dashboard/case-study/case-study.md:12:“The stack of this project consists primarily of React, Node.js, and npm (or yarn). [E6] The top-level structure includes three commits between October 1st, 2026, and October 1st, 2026. These commits represent significant development iterations.” [needs evidence]
output/next-dashboard/case-study/case-study.md:14:“We’ve built a project of approximately 10 files, including the `app` directory, the `public` directory, the
… [truncated 8135 chars]
$ sed -n '120,310p' src/doxify/readme.py; sed -n '380,430p' src/doxify/readme.py
backup_dir = session_dir / "backups"
backup_dir.mkdir(parents=True, exist_ok=True)
backup = backup_dir / target.name
backup.write_bytes(target.read_bytes())
return backup
def citation_free(text: str) -> str:
cleaned = CITATION_RE.sub("", text)
cleaned = MARKER_RE.sub("", cleaned)
return compact_whitespace(cleaned)
def _overview(lines: list[str], repo: RepoSummary, evidence: list[EvidenceCard]) -> None:
cards = [
card
for card in evidence
if card.kind == EvidenceKind.METRIC
and card.title in {"Commit timeline", "Repository size and languages"}
]
body = [card.text for card in cards]
if not body:
body = [
f"{repo.name} contains {repo.tracked_file_count} tracked files"
+ _timelin
… [truncated 5880 chars]
$ sed -n '1,120p' src/doxify/evidence.py; rg -n "def build_evidence|EvidenceCard\(" src/doxify/evidence.py | head -40
from __future__ import annotations
import json
import re
try:
import tomllib
except ModuleNotFoundError: # Python 3.10
import tomli as tomllib
from dataclasses import dataclass
from pathlib import PurePosixPath
from .git_repo import RepoContext
from .models import EvidenceCard, EvidenceKind, EvidenceRef, EvidenceStrength
from .paths import is_manifest, is_readme, is_secret, is_skippable, read_text_limited
from .text import compact_whitespace, keywords_for
JS_SYMBOL_RE = re.compile(
r"^\s*export\s+(?:default\s+)?(?:async\s+)?(?:function|const|class)\s+([A-Za-z_$][\w$]*)",
re.MULTILINE,
)
PY_SYMBOL_RE = re.compile(r"^\s*(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)", re.MULTILINE)
TOOL_NAME_RE = re.compile(r"^\s*name:\s*[\"']([^\"']+)[\"']", re.MULTILINE)
MONGOOSE_MODEL_RE
… [truncated 2388 chars]
$ sed -n '1,120p' src/doxify/exporter.py
from __future__ import annotations
from pathlib import Path
from .models import (
CaseStudySection,
Claim,
ClaimsDocument,
ClaimStatus,
CompileSummary,
DefenseDocument,
DefenseQuestion,
EvidenceCard,
RepoSummary,
SectionsDocument,
VerificationReport,
)
from .session import write_json, write_text
def write_case_study(
output: Path,
repo: RepoSummary,
sections: list[CaseStudySection],
evidence: list[EvidenceCard],
summary: CompileSummary,
claims: list[Claim] | None = None,
) -> Path:
path = output / "case-study.md"
lines = [
f"# {repo.name}: an evidence-backed case study",
"",
"> Compiled with Doxify. Citations point to the evidence appendix.",
"> Unverified statements are marked [
… [truncated 2943 chars]
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.scanner import scan_repository
ctx, scan = scan_repository(Path('.'))
print(scan.repo.name, scan.repo.commit_count, scan.repo.tracked_file_count)
for c in scan.evidence:
print(c.id, c.kind.value, '|', c.title, '|', c.text[:220])
PY
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
File "~/Desktop/repos/doxify/src/doxify/scanner.py", line 17, in scan_repository
repo = open_repo(path)
File "~/Desktop/repos/doxify/src/doxify/git_repo.py", line 45, in open_repo
raise RepoError("Refusing to scan Doxify's own repository.")
doxify.errors.RepoError: Refusing to scan Doxify's own repository.
$ sed -n '1,130p' src/doxify/git_repo.py; rg -n "allow_tool_root|Refusing to scan" src tests
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path, PurePosixPath
from git import InvalidGitRepositoryError, NoSuchPathError, Repo
from .errors import RepoError
from .models import CommitInfo, RepoSummary
from .paths import (
is_deploy,
is_manifest,
is_readme,
is_skippable,
is_test,
language_for,
)
TOOL_ROOT = Path(__file__).resolve().parents[2]
@dataclass
class RepoContext:
repo: Repo
root: Path
summary: RepoSummary
commits: list[CommitInfo]
tracked_files: list[str]
def open_repo(path: Path, *, allow_tool_root: bool = False) -> Repo:
try:
root = path.expanduser().resolve(strict=True)
except OSError as exc:
raise RepoError(f"Reposito
… [truncated 3639 chars]
$ sed -n '1,80p' src/doxify/paths.py; rg -n "self|tool_root|doxify" src/doxify/paths.py src/doxify/git_repo.py
from __future__ import annotations
from pathlib import Path, PurePosixPath
SKIP_DIRS = {
".git",
".hg",
".svn",
".venv",
"venv",
"env",
"node_modules",
"__pycache__",
".pytest_cache",
".mypy_cache",
".ruff_cache",
".next",
"dist",
"build",
"coverage",
"vendor",
".idea",
".vscode",
}
SECRET_SUFFIXES = {".pem", ".key", ".p12", ".pfx", ".der"}
SECRET_PARTS = ("secret", "credential", "password", "token", "apikey", "api_key")
LANGUAGE_BY_SUFFIX = {
".py": "Python",
".js": "JavaScript",
".jsx": "JavaScript",
".mjs": "JavaScript",
".cjs": "JavaScript",
".ts": "TypeScript",
".tsx": "TypeScript",
".go": "Go",
".rs": "Rust",
".java": "Java",
".rb": "Ruby",
".php": "PHP",
".
… [truncated 844 chars]
$ command -v ollama || true; curl -sS --max-time 2 http://127.0.0.1:11434/api/tags | head -c 200 || true; echo; ls -la ~/.ollama 2>/dev/null | head || true
/usr/local/bin/ollama
{"models":[{"name":"gemma3:1b","model":"gemma3:1b","modified_at":"2026-10-22T16:15:47.205965216+01:00","size":806067407,"digest":"97558784bdc9175075a72655bf360b15da17df6b3b9e5182af9f8c0c8e58ba7c","det
total 24
drwxr-xr-x 4 [user] [user] 4096 Oct 22 14:20 .
drwxr-x--- 46 [user] [user] 4096 Oct 24 08:57 ..
drwxr-xr-x 2 [user] [user] 4096 Oct 24 00:11 cache
-rw------- 1 [user] [user] 387 Oct 22 14:20 id_ed25519
-rw-r--r-- 1 [user] [user] 81 Oct 22 14:20 id_ed25519.pub
drwxr-xr-x 6 [user] [user] 4096 Oct 22 16:26 models
$ git ls-files | rg 'README|case-study|claims|evidence|action|workflows'
.github/workflows/ci.yml
README.md
output/dashboard-scan/evidence.json
output/next-dashboard/case-study/case-study.md
output/next-dashboard/case-study/claims.json
output/next-dashboard/case-study/defense.json
output/next-dashboard/case-study/defense.md
output/next-dashboard/case-study/evidence.json
output/next-dashboard/case-study/manifest.json
output/next-dashboard/case-study/sections.json
output/next-dashboard/case-study/verification.json
src/doxify/claims.py
src/doxify/evidence.py
$ rg -n "doxify|verify|CI|GitHub" PLAN.md | head -60
12:> **Implementation status:** The working MVP is a local CLI in `src/doxify` using
17:> Current commands: `doctor`, `scan`, `compile`, `readme`, `scout`, `verify`,
121:None of those claims may be true. Some may be partly true. Some may be impossible to verify from code. The developer then has to defend them in a hiring conversation.
135:Many developers cannot share the repository at all. Their best work lives behind a company login. Cloud portfolio tools ask them to connect GitHub or upload code, which is often not allowed.
248:The submission repository should be something like `doxify` and should contain all new code, README, license, tests, and demo artifacts.
260:- GitShowcase — GitHub activity, case studies, career copy.
262:- Jobr AI Portfolio Builder — GitHub repos to portfolio sit
… [truncated 3220 chars]
[session context compacted]
Objective
- Package
doxify verifyas a reusable GitHub Action that fails when a README or case study contains unsupported claims. - Run the action against Doxify’s own
README.mdand incorporate GitHub Copilot into part of the build.
Important Details
- Current
doxify verify SESSIONonly verifies a compile session viaverify_session(); it does not accept a README/case-study file and does not exit nonzero forneeds_evidenceorrejectedclaims. -
README.mdis hand-written and has no[E1]citation tokens or Doxify-generated markers, so direct claim verification currently lacks provenance. - Existing sample session
output/next-dashboard/case-study/has13 verified,34 needs_evidence,0 rejectedinverification.json; likely useful as a fixture but would fail strict gating. -
.github/workflows/ci.ymlcurrently only runs lint, pytest, build, and artifact upload; noaction.ymlor reusable verification workflow exists. - “Use Copilot for part of the build” is ambiguous and no GitHub Copilot tool is available in this environment; do not claim Copilot was used.
- Cloud support is already implemented:
--provider cloud/DOXIFY_PROVIDER=cloud,OLLAMA_API_KEY, cloud hosthttps://ollama.com, local defaultgemma3:1b, cloud defaultgemma4:31b. - Working tree is dirty with uncommitted cloud changes plus concurrent scout/sentry edits; preserve untracked
src/doxify/scout.py,tests/test_scout.py,tests/test_ollama_client.py, andbenchmarks/. - Last verified state:
uv run ruff check .passed,uv run pytest -qpassed with 32 tests,uv buildsucceeded.
Work State
Completed
- Implemented provider-aware
OllamaClientwith cloud auth, health/error mapping, cloud warning, and local/cloud defaults. - Added
--provider, optional--model, doctor provider/API-key rows, manifestprovider/model_host, and model-neutral wording. - Added
tests/test_ollama_client.pycovering provider resolution, cloud auth errors, doctor behavior, and resolved cloud model wiring. - Updated
README.mdandPLAN.mdfor local-first plus opt-in Ollama Cloud.
Active
- Investigating design for the new GitHub Action and for verifying Doxify’s own hand-written README.
- Read
src/doxify/verifier.py,src/doxify/claims.py,src/doxify/scanner.py,src/doxify/readme.py,src/doxify/pipeline.py,src/doxify/cli.py, CI config, README, and precomputed sessions. - No code changes made yet for the GitHub Action request.
Blocked
-
doxify verifylacks file-oriented verification and non-zero failure semantics for unsupported claims. - Doxify’s README has no evidence/citation metadata; a new matching mode or a committed generated session is needed to make CI verification meaningful.
- Copilot usage requirement is unresolved because Copilot tooling is unavailable here.
- Concurrent edits may still be landing in
src/doxify/prompts.pyandsrc/doxify/scout.py.
Next Move
- Define the verification contract: extend
doxify verifywith a README/case-study mode and--fail-on-unresolved, or verify a committed Doxify compile session instead. - Create
action.ymland a workflow job that runs the chosen verification, then clarify how “use Copilot” should be represented.
Relevant Files
-
src/doxify/cli.py:verifycommand and CLI wiring; needs new flags/output behavior. -
src/doxify/pipeline.py:verify_session()and session/claim handling. -
src/doxify/verifier.py: claim verification logic and forbidden-pattern checks. -
src/doxify/readme.py: generated README markers and evidence metadata. -
src/doxify/claims.py: claim extraction and citation handling. -
src/doxify/models.py:Claim,VerificationReport,SessionManifest, and new provider fields. -
README.md: hand-written target for the requested CI run. -
.github/workflows/ci.yml: existing CI to extend or reference. -
output/next-dashboard/case-study/: precomputed session with existing unsupported claims. -
tests/test_ollama_client.py: recent provider tests and fake-client patterns. -
PLAN.md: project plan and local-first/cloud documentation.
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
$ sed -n '1,240p' README.md
# Doxify
Doxify compiles evidence-backed READMEs and portfolio case studies from local
git repositories. It also scouts breaking dependency upgrades against your
code.
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
## Install
Requires Python 3.10+ and git.
```bash
uv sync
uv run doxify --help
```
Or install the package and run `doxify` directly.
## Local mode (default)
Local mode uses Ollama and `gemma3:1b`. Nothing about the repository leaves
your machine.
```bash
ollama serve
ollama pull gemma3:1b
doxify doctor
doxify readme /path/to/repo
```
## Cloud mode (no local Ollama install)
Cloud mode uses Ollama Cloud directly. You d
… [truncated 1345 chars]
$ sed -n '1,260p' src/doxify/claims.py
from __future__ import annotations
import re
from .models import Claim, ClaimStatus
from .text import compact_whitespace
CITATION_RE = re.compile(r"\[(E\d+)\]")
GAP_RE = re.compile(r"\[GAP\]", re.IGNORECASE)
SENTENCE_SPLIT_RE = re.compile(r"(?<=[.!?])((?:\s*\[E\d+\])*)\s+(?=[A-Z0-9\"“']|\[GAP\])")
PREAMBLE_RE = re.compile(
r"^(?:okay|sure|certainly|of course|here(?:['\u2019]s| is)|based on|the following)\b",
re.IGNORECASE,
)
HEADING_RE = re.compile(r"^\s*#{1,6}\s+")
BULLET_RE = re.compile(r"^\s*(?:[-*+]|\d+[.)])\s+")
def extract_claims(section_id: str, body: str, *, start_index: int = 1) -> list[Claim]:
claims: list[Claim] = []
for paragraph in _paragraphs(body):
for sentence in _sentences(paragraph):
text = compact_whitespace(sentence)
… [truncated 2936 chars]
$ sed -n '1,260p' src/doxify/verifier.py
from __future__ import annotations
import re
from .models import Claim, ClaimStatus, CompileSummary, EvidenceCard
from .text import content_tokens
NUMBER_RE = re.compile(r"(?<![\w.])(\d[\d,]*(?:\.\d+)?)(?![\w])")
CODE_TOKEN_RE = re.compile(r"`([^`]+)`|\b([a-z]+[A-Z][A-Za-z0-9_]*)\b")
FORBIDDEN_PATTERNS: list[tuple[str, re.Pattern[str], str]] = [
(
"oauth",
re.compile(r"\boauth\b", re.IGNORECASE),
"The cited evidence does not contain an OAuth implementation.",
),
(
"tests pass",
re.compile(r"\b(?:all\s+)?tests?\s+(?:pass|passed|passing)\b", re.IGNORECASE),
"Doxify did not run the project's tests.",
),
(
"deployment",
re.compile(
r"\b(?:deployed|in production|production-ready|live at|shipped t
… [truncated 8735 chars]
$ cat src/doxify/scanner.py; sed -n '1,120p' src/doxify/models.py; sed -n '300,390p' src/doxify/pipeline.py
from __future__ import annotations
from pathlib import Path
from .evidence import build_evidence
from .git_repo import RepoContext, collect_repo_context, open_repo
from .models import ScanResult
def scan_repository(
path: Path,
*,
max_commits: int = 100,
max_cards: int = 60,
max_file_bytes: int = 120_000,
) -> tuple[RepoContext, ScanResult]:
repo = open_repo(path)
context = collect_repo_context(repo, max_commits=max_commits)
cards = build_evidence(
context,
max_cards=max_cards,
max_file_bytes=max_file_bytes,
)
return context, ScanResult(repo=context.summary, evidence=cards)
from __future__ import annotations
from enum import Enum
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
SCHEMA_VERSION = "
… [truncated 5384 chars]
$ sed -n '1,180p' output/next-dashboard/case-study/case-study.md; echo '--- claims sample'; sed -n '1,80p' output/next-dashboard/case-study/claims.json
# nextjs-dashboard: an evidence-backed case study
> Compiled locally with Doxify. Citations point to the evidence appendix.
> Unverified statements are marked [needs evidence]; author context is marked [attested].
## Snapshot
Okay, here’s the section text based on the provided evidence cards and goal: [needs evidence]
“This project, nextjs-dashboard, is a Next.js application focused on creating interactive dashboards. [needs evidence] It utilizes React and JavaScript for its core functionality. [E3] The repository contains a README at README.md, detailing the project's purpose and setup.”
“The stack of this project consists primarily of React, Node.js, and npm (or yarn). [E6] The top-level structure includes three commits between October 1st, 2026, and October 1st, 2026. These commi
… [truncated 15550 chars]
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
repo = open_repo(Path('.'), allow_tool_root=True)
ctx = collect_repo_context(repo)
cards = build_evidence(ctx)
print(ctx.summary.name, ctx.summary.commit_count, ctx.summary.tracked_file_count, len(cards))
for c in cards:
print(c.id, c.kind.value, '|', c.title, '|', c.text[:180])
PY
doxify 6 45 26
E1 metric | Commit timeline | doxify has 6 commits between 2026-10-23 and 2026-10-23.
E2 metric | Repository size and languages | The repository tracks 45 files, with languages Python (24), JSON (12), Markdown (4), TOML (1), YAML (1).
E3 readme | README present | The repository contains a README at README.md.
E4 stack | Declared dependencies | The project declares runtime dependencies including gitpython, ollama, pydantic, rich, sentry-sdk, tomli, typer.
E5 file | Top-level structure | The repository is organized into these top-level paths: src (23), output (10), .obsidian (4), .github (1), .gitignore (1), .python-version (1), PLAN.md (1), README.md (1).
E6 test | 1 test-related files | The repository contains test-related files: tests/test_readme.py.
E7 deploy | Deployment
… [truncated 2850 chars]
$ rg -n "def _readme_card|def _source_excerpt_cards|def _test_card|def _deploy_card" src/doxify/evidence.py; sed -n '330,520p' src/doxify/evidence.py
161:def _readme_card(factory: _Factory, context: RepoContext, max_file_bytes: int) -> None:
394:def _test_card(factory: _Factory, context: RepoContext) -> None:
409:def _deploy_card(factory: _Factory, context: RepoContext) -> None:
438:def _source_excerpt_cards(factory: _Factory, sources: list[_SourceFile]) -> None:
found.append((source.relative, match.group(1), _line_number(source.text, match.start())))
if not found:
return
names = [name for _, name, _ in found]
refs = _dedupe_refs(
[EvidenceRef(path=path, lines=str(line)) for path, _, line in found]
)
factory.add(
kind=EvidenceKind.CODE,
title=f"{len(names)} database models",
text=f"The repository defines {len(names)} Mongoose models: {', '.join(names)}.",
r
… [truncated 6412 chars]
$ sed -n '1,260p' src/doxify/cli.py
from __future__ import annotations
import re
import shutil
from datetime import datetime
from pathlib import Path
import typer
from rich.table import Table
from .console import (
console,
error,
print_compile_summary,
print_readme_summary,
print_scan_summary,
print_scout_summary,
stage_progress,
)
from .errors import DoxifyError
from .monitoring import init_monitoring
from .models import CompileSummary, ScanResult
from .ollama_client import DEFAULT_MODEL, OllamaClient
from .pipeline import (
attest_session,
compile_repo,
export_session,
readme_repo,
scan_only,
verify_session,
)
from .scout import scout_repo
app = typer.Typer(
no_args_is_help=True,
help=(
"Doxify compiles evidence-backed READMEs and portfolio case studi
… [truncated 7658 chars]
$ cat .github/workflows/ci.yml; echo '--- pyproject'; sed -n '1,140p' pyproject.toml
name: CI
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
test:
name: Lint, test, and build
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Test
run: uv run pytest -q
- name: Build package
run: uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-f
… [truncated 825 chars]
$ sed -n '150,230p' src/doxify/evidence.py; sed -n '260,380p' src/doxify/cli.py
)
factory.add(
kind=EvidenceKind.METRIC,
title="Repository size and languages",
text=language_text,
strength=EvidenceStrength.DETERMINISTIC,
keywords=["files", "languages", "repository", "size"],
meta={"tracked_file_count": summary.tracked_file_count},
)
def _readme_card(factory: _Factory, context: RepoContext, max_file_bytes: int) -> None:
readme = context.summary.readme_path
if not readme:
factory.add(
kind=EvidenceKind.METRIC,
title="No README",
text="No README file was found among the tracked files.",
strength=EvidenceStrength.DETERMINISTIC,
keywords=["readme", "documentation", "missing"],
)
return
source = _read_source(context,
… [truncated 6173 chars]
$ sed -n '1,180p' src/doxify/pipeline.py; sed -n '180,330p' src/doxify/pipeline.py
from __future__ import annotations
import re
import shutil
import tempfile
from collections.abc import Callable
from pathlib import Path
from .claims import extract_claims
from .defense import build_defense
from .errors import SessionError
from .exporter import (
write_case_study,
write_claims_json,
write_defense,
write_defense_json,
write_evidence_json,
write_sections_json,
write_verification_json,
)
from .generator import GeneratedSection, generate_section, template_section
from .models import (
CaseStudySection,
CaseStudySentence,
ClaimsDocument,
ClaimStatus,
CompileResult,
DefenseDocument,
EvidenceCard,
RepoSummary,
ScanResult,
SectionsDocument,
SessionManifest,
VerificationReport,
)
from .ollama_client impor
… [truncated 9218 chars]
$ rg -n "def _section|def build_readme|doxify-evidence|doxify-generated" src/doxify/readme.py tests/test_readme.py; sed -n '1,130p' src/doxify/readme.py; sed -n '310,400p' src/doxify/readme.py
tests/test_readme.py:195: assert "<!-- doxify-evidence: E1 E2 -->" in content
src/doxify/readme.py:19:GENERATED_MARKER = "<!-- doxify-generated -->"
src/doxify/readme.py:40:def build_readme(
src/doxify/readme.py:334:def _section(
src/doxify/readme.py:344: lines.extend(["", f"<!-- doxify-evidence: {ids} -->"])
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from .errors import SessionError
from .models import (
Claim,
ClaimStatus,
CompileResult,
EvidenceCard,
EvidenceKind,
RepoSummary,
)
from .text import compact_whitespace
GENERATED_MARKER = "<!-- doxify-generated -->"
CITATION_RE = re.compile(r"\s*\[E\d+\]")
MARKER_RE = re.compile(
r"\s*\[(?:GAP|needs evidence|attested|r
… [truncated 5790 chars]
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.claims import extract_claims
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
from doxify.generator import template_section
from doxify.models import CaseStudySection, CaseStudySentence, CompileResult, SessionManifest
from doxify.readme import build_readme
from doxify.retrieval import SECTION_SPECS, retrieve_evidence
from doxify.verifier import summarize_claims, verify_claims
repo = open_repo(Path('.'), allow_tool_root=True)
ctx = collect_repo_context(repo)
evidence = build_evidence(ctx)
sections=[]; claims=[]
for spec in SECTION_SPECS:
cards = retrieve_evidence(evidence, spec, limit=3)
body = template_section(spec, cards)
sc = extract_claims(spec.id, body, start_index=len(claims)+1)
claims.extend(sc)
sections.append(CaseStudySection(id=spec.id, title=spec.title, body=body, sentences=[CaseStudySentence(text=c.text, claim_id=c.id, evidence_ids=c.evidence_ids) for c in sc]))
claims = verify_claims(claims, evidence)
summary = summarize_claims(claims, section_count=len(sections), evidence_count=len(evidence))
manifest = SessionManifest(created_at='x', updated_at='x', repo=ctx.summary, model='none', provider='none', status='done')
result = CompileResult(manifest=manifest, evidence=evidence, sections=sections, claims=claims, defense=None, summary=summary)
print('SUMMARY', summary)
print(build_readme(result))
PY
Traceback (most recent call last):
File "<stdin>", line 24, in <module>
File "~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/pydantic/main.py", line 280, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for CompileResult
defense
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.14/v/list_type
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.claims import extract_claims
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
from doxify.generator import template_section
from doxify.models import CaseStudySection, CaseStudySentence, CompileResult, SessionManifest
from doxify.readme import build_readme
from doxify.retrieval import SECTION_SPECS, retrieve_evidence
from doxify.verifier import summarize_claims, verify_claims
repo = open_repo(Path('.'), allow_tool_root=True)
ctx = collect_repo_context(repo)
evidence = build_evidence(ctx)
sections=[]; claims=[]
for spec in SECTION_SPECS:
cards = retrieve_evidence(evidence, spec, limit=3)
body = template_section(spec, cards)
sc = extract_claims(spec.id, body, start_index=len(claims)+1)
claims.extend(sc)
sections.append(CaseStudySection(id=spec.id, title=spec.title, body=body, sentences=[CaseStudySentence(text=c.text, claim_id=c.id, evidence_ids=c.evidence_ids) for c in sc]))
claims = verify_claims(claims, evidence)
summary = summarize_claims(claims, section_count=len(sections), evidence_count=len(evidence))
manifest = SessionManifest(created_at='x', updated_at='x', repo=ctx.summary, model='none', provider='none', status='done')
result = CompileResult(manifest=manifest, evidence=evidence, sections=sections, claims=claims, defense=[], summary=summary)
print('SUMMARY', summary)
print(build_readme(result))
PY
SUMMARY verified=19 attested=0 needs_evidence=4 rejected=0 sections=6 evidence_cards=26
<!-- doxify-generated -->
<!-- doxify-summary: 19 verified, 0 attested, 4 needing evidence, 0 rejected -->
# doxify
## Overview
doxify has 6 commits between 2026-10-23 and 2026-10-23.
The repository tracks 45 files, with languages Python (24), JSON (12), Markdown (4), TOML (1), YAML (1).
<!-- doxify-evidence: E1 E2 -->
## Tech stack
Languages detected: Python (24), JSON (12), Markdown (4), TOML (1), YAML (1).
Declared runtime dependencies:
- `gitpython`
- `ollama`
- `pydantic`
- `rich`
- `sentry-sdk`
- `tomli`
- `typer`
Manifests: pyproject.toml.
<!-- doxify-evidence: E2 E4 -->
## Project layout
Top-level paths detected in the repository:
```text
src/ (23 files)
output/ (10 files)
.obsidian/
… [truncated 848 chars]
$ ls tests; rg -n "verify|readme|cli" tests | head -80
__pycache__
test_ollama_client.py
test_readme.py
test_scout.py
tests/test_readme.py:8:import doxify.cli as cli
tests/test_readme.py:21:from doxify.readme import (
tests/test_readme.py:24: build_readme,
tests/test_readme.py:25: resolve_readme_target,
tests/test_readme.py:186:def test_build_readme_uses_evidence_and_hides_unresolved(tmp_path: Path) -> None:
tests/test_readme.py:188: content = build_readme(result, session_dir=tmp_path / "session")
tests/test_readme.py:205:def test_resolve_readme_target_defaults_to_repo_root(tmp_path: Path) -> None:
tests/test_readme.py:206: target = resolve_readme_target(tmp_path, None)
tests/test_readme.py:210:def test_resolve_readme_target_refuses_existing_readme(tmp_path: Path) -> None:
tests/test_readme.py:215: resolve_readme_target(tmp_
… [truncated 5322 chars]
$ sed -n '1,260p' tests/test_cli.py 2>/dev/null; sed -n '1,220p' tests/test_pipeline.py 2>/dev/null
(no output)
$ sed -n '1,260p' tests/test_readme.py
from __future__ import annotations
from pathlib import Path
import pytest
from typer.testing import CliRunner
import doxify.cli as cli
import doxify.pipeline as pipeline
from doxify.models import (
Claim,
ClaimStatus,
CompileResult,
CompileSummary,
EvidenceCard,
EvidenceKind,
EvidenceStrength,
RepoSummary,
SessionManifest,
)
from doxify.readme import (
GENERATED_MARKER,
ReadmeResult,
build_readme,
resolve_readme_target,
)
def _card(
card_id: str,
kind: EvidenceKind,
title: str,
text: str,
*,
meta: dict[str, object] | None = None,
strength: EvidenceStrength = EvidenceStrength.DETERMINISTIC,
) -> EvidenceCard:
return EvidenceCard(
id=card_id,
kind=kind,
title=title,
text=t
… [truncated 7058 chars]
$ rg -n -C 4 "verify|GitHub Action|CI|action" PLAN.md | sed -n '1,220p'
13-> Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. Local inference is
14-> still the default; `--provider cloud` routes model calls to Ollama Cloud for
15-> users who do not want to install Ollama locally. The FastAPI and Next.js
16-> sections below describe the deferred hosted UI, not the current build.
17:> Current commands: `doctor`, `scan`, `compile`, `readme`, `scout`, `verify`,
18-> `attest`, and `export`.
19-
20----
21-
--
117-- “Reduced latency by 40%.”
118-- “Led a team of five engineers.”
119-- “Implemented OAuth authentication.”
120-
121:None of those claims may be true. Some may be partly true. Some may be impossible to verify from code. The developer then has to defend them in a hiring conversation.
122-
123-The failure is structural:
124-
125-- the model was give
… [truncated 6100 chars]
$ command -v copilot || true; command -v gh || true; gh copilot --help 2>&1 | head -20 || true; command -v github-copilot-cli || true
/snap/bin/gh
Runs the GitHub Copilot CLI.
Executing the Copilot CLI through `gh` is currently in preview and subject to change.
If already installed, `gh` will execute the Copilot CLI found in your `PATH`.
If the Copilot CLI is not installed, it will be downloaded to ~/.local/share/gh/copilot.
Use `--remove` to remove the downloaded Copilot CLI.
This command is only supported on Windows, Linux, and Darwin, on amd64/x64
or arm64 architectures.
To prevent `gh` from interpreting flags intended for Copilot,
use `--` before Copilot flags and args.
Learn more at https://gh.io/copilot-cli
USAGE
gh copilot [flags] [args]
$ env | rg -i 'copilot|github|gh_' | sed 's/=.*/=<set>/' || true
(no output)
$ git status --short; git log --oneline -8
M PLAN.md
M README.md
M pyproject.toml
M src/doxify/cli.py
M src/doxify/console.py
M src/doxify/errors.py
M src/doxify/exporter.py
M src/doxify/git_repo.py
M src/doxify/models.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/prompts.py
M uv.lock
?? benchmarks/
?? src/doxify/monitoring.py
?? src/doxify/scout.py
?? tests/test_ollama_client.py
?? tests/test_scout.py
f4b1d79 chore(demo): add precomputed nextjs-dashboard compile artifacts
95a5e02 build(ci): add Python 3.10 TOML fallback and package CI
11a198d docs(plan): align roadmap with CLI MVP and README mode
663805d test(readme): cover evidence rendering and temp-session safety
a668d97 feat(core): implement evidence-backed compiler and README output
e845094 Refactor code structure for improved readability
… [truncated 21 chars]
$ gh auth status 2>&1 | head -40
github.com
✓ Logged in to github.com account Ekomjah (keyring)
- Active account: true
- Git operations protocol: ssh
- Token: gho_************************************
- Token scopes: 'admin:public_key', 'gist', 'read:org', 'repo'
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
import re
from pathlib import Path
from doxify.claims import extract_claims
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
from doxify.generator import template_section
from doxify.models import CaseStudySection, CaseStudySentence, CompileResult, SessionManifest, ClaimStatus
from doxify.readme import build_readme
from doxify.retrieval import SECTION_SPECS, retrieve_evidence
from doxify.verifier import summarize_claims, verify_claims
repo = open_repo(Path('.'), allow_tool_root=True)
ctx = collect_repo_context(repo)
evidence = build_evidence(ctx)
sections=[]; claims=[]
for spec in SECTION_SPECS:
cards = retrieve_evidence(evidence, spec, limit=3)
body = template_section(spec, cards)
sc = extract_claims(spec.id, body, start_index=len(claims)+1)
claims.extend(sc)
sections.append(CaseStudySection(id=spec.id, title=spec.title, body=body, sentences=[CaseStudySentence(text=c.text, claim_id=c.id, evidence_ids=c.evidence_ids) for c in sc]))
claims = verify_claims(claims, evidence)
summary = summarize_claims(claims, section_count=len(sections), evidence_count=len(evidence))
manifest = SessionManifest(created_at='x', updated_at='x', repo=ctx.summary, model='none', provider='none', status='done')
result = CompileResult(manifest=manifest, evidence=evidence, sections=sections, claims=claims, defense=[], summary=summary)
content = build_readme(result)
# parse sections
parts = re.split(r'^## ', content, flags=re.M)
for part in parts[1:]:
title, _, body = part.partition('\n')
ids = re.findall(r'<!-- doxify-evidence: ([^>]+) -->', body)
if not ids:
continue
ev_ids = ids[0].split()
clean = re.sub(r'<!--.*?-->', '', body, flags=re.S)
clean = re.sub(r'```.*?```', '', clean, flags=re.S)
sc = extract_claims(title.strip().lower(), clean)
for c in sc:
c.evidence_ids = ev_ids
verified = verify_claims(sc, evidence)
print('SECTION', title, 'ids', ev_ids, 'claims', len(verified))
for c in verified:
print(' ', c.status.value, '|', c.text[:100], '|', c.reason)
PY
SECTION Overview ids ['E1', 'E2'] claims 2
verified | doxify has 6 commits between 2026-10-23 and 2026-10-23. | Supported by cited repository evidence.
verified | The repository tracks 45 files, with languages Python (24), JSON (12), Markdown (4), TOML (1), YAML | Supported by cited repository evidence.
SECTION Tech stack ids ['E2', 'E4'] claims 3
verified | Languages detected: Python (24), JSON (12), Markdown (4), TOML (1), YAML (1). | Supported by cited repository evidence.
verified | Declared runtime dependencies: `gitpython` `ollama` `pydantic` `rich` `sentry-sdk` `tomli` `typer` | Supported by cited repository evidence.
verified | Manifests: pyproject.toml. | Supported by cited repository evidence.
SECTION Project layout ids ['E5'] claims 1
verified | Top-level paths dete
… [truncated 201 chars]
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.claims import extract_claims
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
from doxify.verifier import verify_claims
repo = open_repo(Path('.'), allow_tool_root=True)
ctx = collect_repo_context(repo)
evidence = build_evidence(ctx)
text = """Doxify compiles evidence-backed READMEs and portfolio case studies from local git repositories. It also scouts breaking dependency upgrades against your code. The core pipeline is deterministic: scan the repository, build evidence cards, generate prose from that evidence, verify every claim, and export the result. The model only writes; the compiler checks."""
for ids in [['E3'], ['E14'], ['E18'], ['E14','E18','E24'], ['E3','E14','E18','E24']]:
claims = extract_claims('overview', text)
for c in claims:
c.evidence_ids = ids
out = verify_claims(claims, evidence)
print('IDS', ids)
for c in out:
print(' ', c.status.value, '|', c.text[:80], '|', c.reason)
PY
IDS ['E3']
verified | Doxify compiles evidence-backed READMEs and portfolio case studies from local gi | Supported by cited repository evidence.
verified | It also scouts breaking dependency upgrades against your code. | Supported by cited repository evidence.
verified | The core pipeline is deterministic: scan the repository, build evidence cards, g | Supported by cited repository evidence.
verified | The model only writes; the compiler checks. | Supported by cited repository evidence.
IDS ['E14']
verified | Doxify compiles evidence-backed READMEs and portfolio case studies from local gi | Supported by cited repository evidence.
needs_evidence | It also scouts breaking dependency upgrades against your code. | Citation does not overlap with the claim's key terms.
verified | T
… [truncated 1858 chars]
$ command -v actionlint || true; command -v act || true; command -v yamllint || true; command -v uv
~/.local/bin/uv
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.claims import extract_claims
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
from doxify.verifier import verify_claims
repo = open_repo(Path('.'), allow_tool_root=True)
ctx = collect_repo_context(repo)
evidence = build_evidence(ctx)
sections = {
'Project facts': ('E2 E4', 'Doxify is written in Python. The project declares runtime dependencies including gitpython, ollama, pydantic, rich, sentry-sdk, tomli, and typer.'),
'Project layout': ('E5', 'The repository is organized into top-level paths including src, tests, output, and .github.'),
'Testing and CI': ('E6 E7', 'The repository contains test-related files at tests/test_readme.py. CI is defined in .github/workflows/ci.yml.'),
}
for title, (ids, body) in sections.items():
claims = extract_claims(title.lower(), body)
for c in claims:
c.evidence_ids = ids.split()
out = verify_claims(claims, evidence)
print('SECTION', title, ids)
for c in out:
print(' ', c.status.value, '|', c.text, '|', c.reason)
PY
SECTION Project facts E2 E4
verified | Doxify is written in Python. | Supported by cited repository evidence.
verified | The project declares runtime dependencies including gitpython, ollama, pydantic, rich, sentry-sdk, tomli, and typer. | Supported by cited repository evidence.
SECTION Project layout E5
verified | The repository is organized into top-level paths including src, tests, output, and .github. | Supported by cited repository evidence.
SECTION Testing and CI E6 E7
verified | The repository contains test-related files at tests/test_readme.py. | Supported by cited repository evidence.
verified | CI is defined in .github/workflows/ci.yml. | Supported by cited repository evidence.
$ rg -n "def _print_verification|def print_verification|def print_compile_summary" -A 30 src/doxify/cli.py src/doxify/console.py
src/doxify/cli.py:406:def _print_verification(summary: CompileSummary) -> None:
src/doxify/cli.py-407- table = Table(title="Verification", show_header=False)
src/doxify/cli.py-408- table.add_column("Status", style="cyan")
src/doxify/cli.py-409- table.add_column("Count", justify="right")
src/doxify/cli.py-410- table.add_row("Verified", str(summary.verified))
src/doxify/cli.py-411- table.add_row("Attested", str(summary.attested))
src/doxify/cli.py-412- table.add_row("Needs evidence", str(summary.needs_evidence))
src/doxify/cli.py-413- table.add_row("Rejected", str(summary.rejected))
src/doxify/cli.py-414- console.print(table)
src/doxify/cli.py-415-
src/doxify/cli.py-416-
src/doxify/cli.py-417-def main() -> None:
src/doxify/cli.py-418- if init_monitoring():
src/doxi
… [truncated 2469 chars]
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B -c "import yaml; print(yaml.__version__)" 2>&1 | head -5
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'yaml'
$ command -v ruby || true; command -v node || true; command -v python3
~/.nvm/versions/node/v24.18.1/bin/node
/usr/bin/python3
$ python3 -c "import yaml; print(yaml.__version__)" 2>&1 | head -3; node -e "try{require('yaml');console.log('yaml yes')}catch(e){console.log('no yaml')}"
6.0.3
no yaml
$ cat src/doxify/monitoring.py; sed -n '1,200p' src/doxify/session.py
from __future__ import annotations
import os
import sentry_sdk
DSN = "[redacted-sentry-dsn]"
_client = None
def init_monitoring() -> bool:
"""Initialize Sentry error monitoring and tracing.
The DSN may be overridden with SENTRY_DSN. Returns True when monitoring
is active.
"""
global _client
dsn = os.environ.get("SENTRY_DSN", DSN)
if not dsn:
return False
_client = sentry_sdk.init(
dsn=dsn,
traces_sample_rate=1.0,
release=_guess_release(),
environment=os.environ.get("SENTRY_ENVIRONMENT", "development"),
project_root=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
)
return _client is not None
def report_excepti
… [truncated 2507 chars]
$ rg -n "verify_session\(" -g '*.py' .
./src/doxify/cli.py:370: report = verify_session(session)
./src/doxify/pipeline.py:302:def verify_session(output_dir: Path) -> VerificationReport:
I found a workable path: audit only evidence-marked README sections, classify setup/privacy sections as author-attested, and use the precomputed case study as the expected-failure test. One product choice remains before I finalize the plan.
{"questions": [{"question": "How should we make Doxify's own docs pass the action while preserving the useful install/cloud docs?", "header": "Self-audit target", "options": [{"label": "Root README markers (Recommended)", "description": "Keep the hand-written README, add an evidence-marked 'Project facts' section plus attested markers for setup/privacy prose, commit evidence JSON, and audit the root README in CI."}, {"label": "Dedicated self-audit README", "description": "Generate docs/self-audit/README.md from Doxify's evidence and audit that file; leave the root README untouched."}, {"label": "Replace root README", "description": "Use the generated README as the real README; simplest to verify but loses detailed install, cloud, and command docs."}, {"label": "Synthetic fixture", "description": "Use a small deterministic fixture repo/README for the positive test instead of auditing Doxify's own docs."}]}, {"question": "Which part should GitHub Copilot build?", "header": "Copilot scope", "options": [{"label": "Action YAML (Recommended)", "description": "Use gh copilot to draft the composite action and workflow, then verify the commands and YAML locally."}, {"label": "Tests and fixt
… [truncated 374 chars]
User has answered your questions: "How should we make Doxify's own docs pass the action while preserving the useful install/cloud docs?"="Root README markers (Recommended)", "Which part should GitHub Copilot build?"="Action YAML (Recommended)". You can now continue with the user's answers in mind.
$ PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.claims import extract_claims
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
from doxify.verifier import verify_claims
repo = open_repo(Path('.'), allow_tool_root=True)
ctx = collect_repo_context(repo)
evidence = build_evidence(ctx)
body = """Doxify is written in Python. The project declares runtime dependencies including gitpython, ollama, pydantic, rich, sentry-sdk, tomli, and typer.
The repository is organized into top-level paths including `src`, `tests`, `output`, and `.github`.
The repository contains test-related files at `tests/test_readme.py`. CI is defined in `.github/workflows/ci.yml`."""
claims = extract_claims('project-facts', body)
for c in claims:
c.evidence_ids = ['E2','E4','E5','E6','E7']
out = verify_claims(claims, evidence)
for c in out:
print(c.status.value, '|', c.text, '|', c.reason)
PY
verified | Doxify is written in Python. | Supported by cited repository evidence.
verified | The project declares runtime dependencies including gitpython, ollama, pydantic, rich, sentry-sdk, tomli, and typer. | Supported by cited repository evidence.
verified | The repository is organized into top-level paths including `src`, `tests`, `output`, and `.github`. | Supported by cited repository evidence.
verified | The repository contains test-related files at `tests/test_readme.py`. | Supported by cited repository evidence.
verified | CI is defined in `.github/workflows/ci.yml`. | Supported by cited repository evidence.
$ cat src/doxify/errors.py; sed -n '1,120p' src/doxify/console.py; sed -n '1,120p' src/doxify/models.py | tail -20
class DoxifyError(Exception):
"""Base class for expected Doxify failures."""
class RepoError(DoxifyError):
"""The selected path is not a usable local git repository."""
class SessionError(DoxifyError):
"""A session directory is missing or invalid."""
class ModelError(DoxifyError):
"""The configured Ollama local or cloud endpoint is unavailable."""
class GenerationError(DoxifyError):
"""A section could not be generated or parsed."""
class ScoutError(DoxifyError):
"""Release notes could not be fetched, extracted, or matched."""
from __future__ import annotations
from pathlib import Path
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
from rich.table import Table
from .models import CompileR
… [truncated 4922 chars]
$ rg -n "def write_evidence_json" -A 12 src/doxify/exporter.py
140:def write_evidence_json(
141- output: Path, repo: RepoSummary, evidence: list[EvidenceCard]
142-) -> Path:
143- path = output / "evidence.json"
144- write_json(
145- path,
146- {
147- "schema_version": "1.0",
148- "repo": repo.model_dump(mode="json"),
149- "evidence": [card.model_dump(mode="json") for card in evidence],
150- },
151- )
152- return path
Plan: doxify verify GitHub Action + self-auditing README
Decisions
- Keep the hand-written root README and add an evidence-marked
## Project factssection plusdoxify-attestedmarkers for setup/privacy prose. - Audit only explicitly classified sections:
doxify-evidencesections are semantically verified;doxify-attestedsections are author-asserted; unmarked sections fail in strict mode. - Use the precomputed
next-dashboardcase study as the expected-failure test. - Use
gh copilot suggestto draft the composite action’s runner command, then review and test it manually.
1. Add artifact auditing
Create src/doxify/audit.py:
- Parse Markdown sections, including preamble.
- Recognize <!-- doxify-evidence: E1 E2 --> and <!-- doxify-attested: reason -->.
- Strip hidden comments and fenced code blocks before claim extraction.
- Reuse extract_claims() and verify_claims().
- Validate evidence IDs against the supplied evidence.json.
- For case studies, detect visible [needs evidence], [rejected], and [GAP] markers; skip “Verification summary” and “Evidence appendix”.
- Return a report with verified/attested/needs-evidence/rejected counts and section details.
2. Extend the CLI
Modify src/doxify/cli.py and src/doxify/pipeline.py:
doxify verify [SESSION] \
[--artifact PATH] \
[--evidence PATH] \
[--fail-on needs_evidence,rejected] \
[--check]
-
SESSIONbecomes optional. - Session-only: re-verify claims;
--checkprevents rewriting artifacts. - Artifact-only: requires
--evidence; audits README/case study without a session. - Session + artifact: verify both.
-
--fail-ondefaults toneeds_evidence,rejected;nonedisables failure. - Exit
1if any selected status appears. - Print failed claim text, reason, and evidence IDs.
- Add
verify_session(..., write=...)and an evidence-file loader.
3. Make the root README self-auditing
Modify README.md:
- Add attested markers to intro, install, local mode, cloud mode, commands, CI usage, and development sections.
- Add a generated, stable ## Project facts section:
- Python language evidence.
- Runtime dependencies.
- Top-level layout.
- Test file presence.
- CI workflow presence.
- Mark it with <!-- doxify-evidence: E2 E4 E5 E6 E7 -->.
- Add a ## Verify in CI section with action usage.
- Keep all existing install/cloud documentation.
4. Add self-audit evidence
Create docs/self-audit/evidence.json:
- Generate from Doxify’s own repository using open_repo(..., allow_tool_root=True) and build_evidence().
- Sanitize the absolute repo path.
- Verify the root README against this evidence before committing.
5. Add the composite action
Create root action.yml:
- Inputs: session, artifact, evidence, fail-on, python-version, working-directory.
- Install Doxify with uv sync --frozen --no-dev --project "$GITHUB_ACTION_PATH".
- Run uv run --project "$GITHUB_ACTION_PATH" --no-sync doxify verify ... --check.
- Set SENTRY_DSN: "" for the verify step.
- Use shell arrays so empty inputs and paths with spaces are handled safely.
- Use Copilot to draft the runner command, then validate the final YAML.
6. Add CI coverage
Modify .github/workflows/ci.yml with a verify job:
- Positive check: root README.md + docs/self-audit/evidence.json should pass.
- Negative check: output/next-dashboard/case-study/case-study.md + its evidence should fail.
- Wrap the negative step with continue-on-error: true and assert steps.negative.outcome == 'failure'.
- Set SENTRY_DSN: "" in the job env.
7. Tests
Add:
- tests/test_audit.py: annotated README pass/fail, unmarked section, attested section, unknown evidence ID, case-study markers.
- tests/test_verify_cli.py: exit codes, artifact-only mode, session-only mode, --check does not rewrite.
- tests/test_self_audit.py: scan the current repo fresh and assert the root README verifies.
- Keep existing tests green.
Verification
Run:
```bash
SENTRY_DSN="" uv run doxify verify --artifact README.md \
--evidence docs/self-audit/evidence.json \
--fail-on needs_evidence,rejected --check
expected exit 0
SENTRY_DSN="" uv run doxify verify \
--artifact output/next-dashboard/case-study/case-study.md \
--evidence output/next-dashboard/case-study/evidence.json \
--fail-on needs_evidence,rejected --check
expected exit 1
uv run ruff check .
uv run pytest -q
uv build
python3 -c "import yaml; yaml.safe_load(open('action.yml')); yaml.safe_load(open('.github/workflows/ci.yml'))"
```
Notes / risks
-
src/doxify/monitoring.pycurrently defaults to a hardcoded Sentry DSN. The action will disable it withSENTRY_DSN=""; I will not change the default in this task, but it contradicts the README’s local-privacy claim and deserves a follow-up. -
doxify-attestedsections are explicit author-owned prose, not code-verified claims. Every README section will be classified so strict mode catches new unmarked sections. - The committed evidence is a snapshot; the self-audit test uses a fresh scan to catch drift.
No files changed yet.
go
{
"todos": [
{
"content": "Implement artifact audit engine and tests",
"status": "in_progress",
"priority": "high"
},
{
"content": "Extend verify CLI/pipeline with fail-on, artifact, evidence, check",
"status": "pending",
"priority": "high"
},
{
"content": "Add self-audit evidence and README markers/docs",
"status": "pending",
"priority": "high"
},
{
"content": "Use Copilot to draft action runner command; add action.yml",
"status": "pending",
"priority": "medium"
},
{
"content": "Add CI workflow positive/negative verification",
"status": "pending",
"priority": "medium"
},
{
"content": "Run ruff, pytest, build, and action command checks",
"status": "pending",
"priority": "high"
}
]
}
[
{
"content": "Implement artifact audit engine and tests",
"status": "in_progress",
"priority": "high"
},
{
"content": "Extend verify CLI/pipeline with fail-on, artifact, evidence, check",
"status": "pending",
"priority": "high"
},
{
"content": "Add self-audit evidence and README markers/docs",
"status": "pending",
"priority": "high"
},
{
"content": "Use Copilot to draft action runner command; add action.yml",
"status": "pending",
"priority": "medium"
},
{
"content": "Add CI workflow positive/negative verification",
"st
… [truncated 174 chars]
$ cat src/doxify/text.py
from __future__ import annotations
import re
TOKEN_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_+-]*|\d[\d,]*")
STOPWORDS = {
"a",
"an",
"and",
"are",
"as",
"at",
"be",
"because",
"by",
"for",
"from",
"has",
"have",
"i",
"in",
"is",
"it",
"its",
"of",
"on",
"or",
"that",
"the",
"their",
"this",
"to",
"was",
"were",
"which",
"with",
"project",
"repository",
"repo",
"code",
"file",
"files",
"using",
"used",
"uses",
"use",
"built",
"build",
}
def tokenize(text: str) -> list[str]:
return [match.group(0).lower() for match in TOKEN_RE.finditer(text)]
def stem(token: str) -> str:
if len(token) <= 4 or token.isdigit():
… [truncated 1146 chars]
$ sed -n '1,120p' src/doxify/models.py | tail -40; rg -n "class CompileSummary|class VerificationReport|class ScanResult" -A 20 src/doxify/models.py
languages: dict[str, int] = Field(default_factory=dict)
readme_path: str | None = None
manifests: list[str] = Field(default_factory=list)
test_files: list[str] = Field(default_factory=list)
deploy_files: list[str] = Field(default_factory=list)
class ScanResult(BaseModel):
model_config = ConfigDict(extra="forbid")
schema_version: str = SCHEMA_VERSION
repo: RepoSummary
evidence: list[EvidenceCard] = Field(default_factory=list)
class Claim(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str
text: str
section_id: str
evidence_ids: list[str] = Field(default_factory=list)
status: ClaimStatus = ClaimStatus.NEEDS_EVIDENCE
reason: str | None = None
gap_question: str | None = None
user_answer: str | None = None
… [truncated 1838 chars]
file: ~/Desktop/repos/doxify/src/doxify/audit.py
content:
from __future__ import annotations
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable
from .claims import CITATION_RE, extract_claims
from .errors import SessionError
from .models import Claim, ClaimStatus, CompileSummary, EvidenceCard
from .readme import GENERATED_MARKER
from .verifier import summarize_claims, verify_claims
EVIDENCE_MARKER_RE = re.compile(
r"<!--\s*doxify-evidence:\s*([^>]*?)\s*-->",
re.IGNORECASE | re.DOTALL,
)
ATTESTED_MARKER_RE = re.compile(
r"<!--\s*doxify-attested:\s*([^>]*?)\s*-->",
re.IGNORECASE | re.DOTALL,
)
COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
VISIBLE_MARKER_RE = re.compile(r"\[(n
… [truncated 7409 chars]
Wrote file successfully.
$ uv run ruff check src/doxify/audit.py
UP035 [*] Import from `collections.abc` instead: `Iterable`
--> src/doxify/audit.py:6:1
|
4 | from dataclasses import dataclass, field
5 | from pathlib import Path
6 | from typing import Iterable
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
7 |
8 | from .claims import CITATION_RE, extract_claims
|
help: Import from `collections.abc`
|
5 | from pathlib import Path
- from typing import Iterable
6 + from collections.abc import Iterable
7 |
|
Found 1 error.
[*] 1 fixable with `--fix`.
file: ~/Desktop/repos/doxify/src/doxify/audit.py
old:
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable
new:
import re
from collections.abc import Iterable
from dataclasses import dataclass, field
from pathlib import Path
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/pipeline.py
old:
def verify_session(output_dir: Path) -> VerificationReport:
output = output_dir.expanduser().resolve()
scan, sections, claims_doc, defense, manifest = load_session(output)
claims = verify_
… [truncated 696 chars]
new:
def load_evidence_file(path: Path) -> ScanResult:
return read_model(path.expanduser().resolve(), ScanResult)
def verify_session(output_dir: Path, *, write: bool = True) -> VerificationReport:
output = output_dir.expanduser().resolve()
scan, sections, claims_doc, defense, manifest = load_session(output)
claims = verify_claims(claims_doc.claims, scan.evidence)
summary = summari
… [truncated 675 chars]
Edit applied successfully.
$ rg -n "from \.session import" -A 12 src/doxify/pipeline.py
45:from .session import (
46- prepare_output_dir,
47- read_model,
48- utc_now,
49- write_json,
50- write_manifest,
51- write_text,
52-)
53-from .verifier import attest_claim as mark_attested
54-from .verifier import summarize_claims, verify_claims
55-
56-StageCallback = Callable[[str, str], None]
57-
$ sed -n '1,45p' src/doxify/cli.py; sed -n '350,420p' src/doxify/cli.py
from __future__ import annotations
import re
import shutil
from datetime import datetime
from pathlib import Path
import typer
from rich.table import Table
from .console import (
console,
error,
print_compile_summary,
print_readme_summary,
print_scan_summary,
print_scout_summary,
stage_progress,
)
from .errors import DoxifyError
from .models import CompileSummary, ScanResult
from .monitoring import init_monitoring, report_exception
from .ollama_client import DEFAULT_MODEL, OllamaClient
from .pipeline import (
attest_session,
compile_repo,
export_session,
readme_repo,
scan_only,
verify_session,
)
from .scout import scout_repo
app = typer.Typer(
no_args_is_help=True,
help=(
"Doxify compiles evidence-backed READMEs and po
… [truncated 2636 chars]
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
import typer
from rich.table import Table
from .console import (
console,
error,
print_compile_summary,
print_readme_summary,
print_scan_summary,
print_scout_summary,
stag
… [truncated 371 chars]
new:
import typer
from rich.table import Table
from .audit import (
ArtifactReport,
audit_artifact,
failing_claims,
parse_fail_on,
)
from .console import (
console,
error,
print_compile_summary,
print_readme_summary,
print_scan_summary,
print_scout_summary,
stage_progress,
)
from .errors import DoxifyError
from .models import ClaimStatus, CompileSummary, Sca
… [truncated 310 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
@app.command()
def verify(
session: Path = typer.Argument(..., exists=True, file_okay=False, resolve_path=True),
) -> None:
"""Re-run deterministic verification on an existing session."""
… [truncated 186 chars]
new:
@app.command()
def verify(
session: Path | None = typer.Argument(
None,
exists=True,
file_okay=False,
resolve_path=True,
help="Compile session directory. Omit when auditing an artifact directly.",
),
artifact: Path | None = typer.Option(
None,
"--artifact",
exists=True,
file_okay=True,
dir_okay=False,
… [truncated 2497 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
def _print_verification(summary: CompileSummary) -> None:
table = Table(title="Verification", show_header=False)
table.add_column("Status", style="cyan")
table.add_column("Count", justify=
… [truncated 257 chars]
new:
def _print_verification(summary: CompileSummary) -> None:
table = Table(title="Verification", show_header=False)
table.add_column("Status", style="cyan")
table.add_column("Count", justify="right")
table.add_row("Verified", str(summary.verified))
table.add_row("Attested", str(summary.attested))
table.add_row("Needs evidence", str(summary.needs_evidence))
table.add_row("R
… [truncated 1227 chars]
Edit applied successfully.
$ uv run ruff check src/doxify/audit.py src/doxify/cli.py src/doxify/pipeline.py
Building doxify @ file://~/Desktop/repos/doxify
Built doxify @ file://~/Desktop/repos/doxify
Uninstalled 1 package in 8ms
Installed 1 package in 19ms
All checks passed!
$ SENTRY_DSN="" uv run doxify verify --help
Usage: doxify verify [OPTIONS] [session]
Re-run deterministic verification on a session, README, or case study.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ session <directory> Compile session directory. Omit when auditing an │
│ artifact directly. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────
… [truncated 740 chars]
$ SENTRY_DSN="" uv run doxify verify --artifact output/next-dashboard/case-study/case-study.md --evidence output/next-dashboard/case-study/evidence.json --fail-on needs_evidence,rejected --check; echo EXIT:$?
Artifact: case-study.md
┌───────────────────┬──────────────────────────────────────────────────────────┐
│ Mode │ case_study │
│ Sections │ 6 │
│ Attested sections │ 0 │
│ Claims │ 13 verified, 0 attested, 71 needing evidence, 0 rejected │
└───────────────────┴──────────────────────────────────────────────────────────┘
C1 needs_evidence “This project, nextjs-dashboard, is a Next.js application
focused on creating interactive dashboards. It utilizes React and JavaScript
for its core functionality. [E3]
Citation does not overlap with the claim's
… [truncated 1731 chars]
file: ~/Desktop/repos/doxify/tests/test_audit.py
content:
from __future__ import annotations
from pathlib import Path
import pytest
from doxify.audit import audit_artifact, failing_claims, parse_fail_on
from doxify.errors import SessionError
from doxify.models import ClaimStatus, EvidenceCard, EvidenceKind, EvidenceStrength
def _card(
card_id: str = "E1",
text: str = "Demo has 3 files.",
) -> EvidenceCard:
return EvidenceCard(
id=card_id,
kind=EvidenceKind.METRIC,
title="Repository facts",
text=text,
strength=EvidenceStrength.DETERMINISTIC,
)
def test_annotated_readme_verifies_evidence_and_attested_sections(tmp_path: Path) -> None:
readme = tmp_path / "README.md"
readme.write_te
… [truncated 3440 chars]
Wrote file successfully.
$ SENTRY_DSN="" uv run pytest -q tests/test_audit.py
.FF..... [100%]
=================================== FAILURES ===================================
________________ test_annotated_readme_flags_unsupported_claims ________________
tmp_path = PosixPath('/tmp/pytest-of-[user]/pytest-28/test_annotated_readme_flags_un0')
def test_annotated_readme_flags_unsupported_claims(tmp_path: Path) -> None:
readme = tmp_path / "README.md"
readme.write_text(
"""# Demo
## Facts
Demo has 99 users.
<!-- doxify-evidence: E1 -->
""",
encoding="utf-8",
)
report = audit_artifact(readme, [_card()])
> assert report.summary.needs_evidence == 1
E AssertionError: assert 2 == 1
E + wher
… [truncated 1732 chars]
file: ~/Desktop/repos/doxify/tests/test_audit.py
old:
readme.write_text(
"""# Demo
## Facts
Demo has 99 users.
<!-- doxify-evidence: E1 -->
""",
encoding="utf-8",
)
new:
readme.write_text(
"""# Demo
<!-- doxify-attested: title -->
## Facts
Demo has 99 users.
<!-- doxify-evidence: E1 -->
""",
encoding="utf-8",
)
Edit applied successfully.
file: ~/Desktop/repos/doxify/tests/test_audit.py
old:
## Facts
Demo has 3 files.
## Unclassified
new:
## Facts
Demo has 3 files.
<!-- doxify-evidence: E1 -->
## Unclassified
Edit applied successfully.
$ SENTRY_DSN="" uv run pytest -q tests/test_audit.py
........ [100%]
8 passed in 0.69s
file: ~/Desktop/repos/doxify/tests/test_verify_cli.py
content:
from __future__ import annotations
import shutil
from pathlib import Path
from typer.testing import CliRunner
import doxify.cli as cli
from doxify.exporter import write_evidence_json
from doxify.models import (
EvidenceCard,
EvidenceKind,
EvidenceStrength,
RepoSummary,
)
ROOT = Path(__file__).resolve().parents[1]
CASE_STUDY = ROOT / "output" / "next-dashboard" / "case-study"
def _card() -> EvidenceCard:
return EvidenceCard(
id="E1",
kind=EvidenceKind.METRIC,
title="Repository facts",
text="Demo has 3 files.",
strength=EvidenceStrength.DETERMINISTIC,
)
def _evidence_file(tmp_path: Path) -> Path:
repo = RepoSummary(
… [truncated 2837 chars]
Wrote file successfully.
$ SENTRY_DSN="" uv run pytest -q tests/test_verify_cli.py
F.... [100%]
=================================== FAILURES ===================================
_______________ test_verify_artifact_passes_with_evidence_marker _______________
tmp_path = PosixPath('/tmp/pytest-of-[user]/pytest-30/test_verify_artifact_passes_wi0')
def test_verify_artifact_passes_with_evidence_marker(tmp_path: Path) -> None:
evidence = _evidence_file(tmp_path)
readme = tmp_path / "README.md"
readme.write_text(
"""# Demo
## Facts
Demo has 3 files.
<!-- doxify-evidence: E1 -->
""",
encoding="utf-8",
)
result = CliRunner().invoke(
cli.app,
[
"verify",
"
… [truncated 1513 chars]
file: ~/Desktop/repos/doxify/tests/test_verify_cli.py
old:
readme.write_text(
"""# Demo
## Facts
Demo has 3 files.
<!-- doxify-evidence: E1 -->
""",
encoding="utf-8",
)
result = CliRunner().invoke(
cli.app,
[
… [truncated 276 chars]
new:
readme.write_text(
"""# Demo
<!-- doxify-attested: title -->
## Facts
Demo has 3 files.
<!-- doxify-evidence: E1 -->
""",
encoding="utf-8",
)
result = CliRunner().invoke(
cli.app,
[
"verify",
"--artifact",
str(readme),
"--evidence",
str(evidence),
"--fail-on",
"needs
… [truncated 109 chars]
Edit applied successfully.
$ SENTRY_DSN="" uv run pytest -q tests/test_verify_cli.py
..... [100%]
5 passed in 1.66s
file: ~/Desktop/repos/doxify/README.md
<path>~/Desktop/repos/doxify/README.md</path>
<type>file</type>
<content>
1: # Doxify
2:
3: Doxify compiles evidence-backed READMEs and portfolio case studies from local
4: git repositories. It also scouts breaking dependency upgrades against your
5: code.
6:
7: The core pipeline is deterministic: scan the repository, build evidence cards,
8: generate prose from that evidence, verify every claim, and export the result.
9: The model only writes; the compiler checks.
10:
11: ## Install
12:
13: Requires Python 3.10+ and git.
14:
15: ```bash
16: uv sync
17: uv run doxify --help
18
… [truncated 2546 chars]
file: ~/Desktop/repos/doxify/README.md
old:
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
… [truncated 12 chars]
new:
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
<!-- doxify-attested: project description and positioning -->
## Install
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
Or install the package and run `doxify` directly.
## Local mode (default)
new:
Or install the package and run `doxify` directly.
<!-- doxify-attested: installation instructions -->
## Local mode (default)
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
doxify readme /path/to/repo
```
## Cloud mode (no local Ollama install)
new:
doxify readme /path/to/repo
```
<!-- doxify-attested: local setup and privacy behavior -->
## Cloud mode (no local Ollama install)
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
Precedence is flag, then environment variable, then default.
## Commands
new:
Precedence is flag, then environment variable, then default.
<!-- doxify-attested: cloud setup, privacy, and billing guidance -->
## Commands
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
doxify scout PACKAGE FROM TO [--repo PATH]
doxify verify SESSION
doxify attest SESSION --claim C1 --answer "..."
doxify export SESSION
```
`scan`, `verify`, `attest`, and `export` never call a model
… [truncated 36 chars]
new:
doxify scout PACKAGE FROM TO [--repo PATH]
doxify verify SESSION [--artifact PATH] [--fail-on STATUSES]
doxify attest SESSION --claim C1 --answer "..."
doxify export SESSION
```
`scan`, `verify`, `attest`, and `export` never call a model and work offline.
`doxify verify` exits non-zero when a claim needs evidence or is rejected. Use
`--fail-on none` to report without failing, `--check` to avoid
… [truncated 603 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
Use `--notes FILE` for offline notes, `--deep` to analyze every fetched release,
and `--expect FILE` to replay a known breaking-change list. Scout writes
`scout.md`, `scout.json`, `release-notes.json`
… [truncated 130 chars]
new:
Use `--notes FILE` for offline notes, `--deep` to analyze every fetched release,
and `--expect FILE` to replay a known breaking-change list. Scout writes
`scout.md`, `scout.json`, `release-notes.json`, and `manifest.json`; it never
modifies the scanned repository.
<!-- doxify-attested: scout usage instructions -->
## Verify in CI
The composite action runs the same deterministic checker. Point i
… [truncated 542 chars]
Edit applied successfully.
$ SENTRY_DSN="" PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.evidence import build_evidence
from doxify.exporter import write_evidence_json
from doxify.git_repo import collect_repo_context, open_repo
root = Path('.').resolve()
repo = open_repo(root, allow_tool_root=True)
ctx = collect_repo_context(repo)
cards = build_evidence(ctx)
summary = ctx.summary.model_copy(update={'path': '.'})
path = write_evidence_json(root / 'docs' / 'self-audit', summary, cards)
print(path)
print(len(cards), 'cards')
PY
~/Desktop/repos/doxify/docs/self-audit/evidence.json
28 cards
$ SENTRY_DSN="" uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check; echo EXIT:$?
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
EXIT:0
$ SENTRY_DSN="" PYTHONDONTWRITEBYTECODE=1 uv run python -B - <<'PY'
from pathlib import Path
from doxify.audit import audit_artifact
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
root = Path('.').resolve()
repo = open_repo(root, allow_tool_root=True)
ctx = collect_repo_context(repo)
evidence = build_evidence(ctx)
report = audit_artifact(root / 'README.md', evidence)
print(report.summary)
print(report.unmarked_sections)
for c in report.claims:
if c.status.value != 'verified' and c.status.value != 'attested':
print(c.id, c.status.value, c.text, c.reason)
PY
verified=5 attested=8 needs_evidence=0 rejected=0 sections=9 evidence_cards=28
[]
file: ~/Desktop/repos/doxify/tests/test_self_audit.py
content:
from __future__ import annotations
from pathlib import Path
from doxify.audit import audit_artifact
from doxify.evidence import build_evidence
from doxify.git_repo import collect_repo_context, open_repo
ROOT = Path(__file__).resolve().parents[1]
def test_doxify_readme_verifies_against_current_repository() -> None:
repo = open_repo(ROOT, allow_tool_root=True)
context = collect_repo_context(repo)
evidence = build_evidence(context)
report = audit_artifact(ROOT / "README.md", evidence)
assert report.unmarked_sections == []
assert report.summary.needs_evidence == 0
assert report.summary.rejected == 0
assert report.summary.verified == 5
assert report.summ
… [truncated 18 chars]
Wrote file successfully.
$ SENTRY_DSN="" uv run pytest -q tests/test_self_audit.py
. [100%]
1 passed in 1.25s
$ gh copilot --help 2>&1 | head -60
Runs the GitHub Copilot CLI.
Executing the Copilot CLI through `gh` is currently in preview and subject to change.
If already installed, `gh` will execute the Copilot CLI found in your `PATH`.
If the Copilot CLI is not installed, it will be downloaded to ~/.local/share/gh/copilot.
Use `--remove` to remove the downloaded Copilot CLI.
This command is only supported on Windows, Linux, and Darwin, on amd64/x64
or arm64 architectures.
To prevent `gh` from interpreting flags intended for Copilot,
use `--` before Copilot flags and args.
Learn more at https://gh.io/copilot-cli
USAGE
gh copilot [flags] [args]
FLAGS
--remove Remove the downloaded Copilot CLI
INHERITED FLAGS
--help Show help for command
EXAMPLES
# Download and run the Copilot CLI
$ gh copilot
… [truncated 509 chars]
$ gh copilot -- --help 2>&1 | head -80
! Copilot CLI not installed
$ gh copilot -p 'Draft a bash snippet for a GitHub composite action that: installs a Python project from $GITHUB_ACTION_PATH with uv sync --frozen --no-dev, then runs its doxify CLI with verify, optional session argument, optional --artifact, optional --evidence, --fail-on, and --check. Return only the bash snippet, no explanation.' 2>&1 | head -120
! Copilot CLI not installed
$ gh copilot 2>&1 | head -60
! Copilot CLI not installed
$ ls -la ~/.local/share/gh/copilot 2>&1 | head -30; gh extension list 2>&1 | head -20; gh copilot --remove 2>&1 | head -20
ls: cannot access '~/.local/share/gh/copilot': No such file or directory
failed to remove Copilot CLI: Copilot CLI not installed through `gh`
$ npx -y @github/copilot --help 2>&1 | head -80
(no output)
<shell_metadata>
shell tool terminated command after exceeding timeout 180000 ms. If this command is expected to take longer and is not waiting for interactive input, retry with a larger timeout value in milliseconds.
</shell_metadata>
$ uv sync --frozen --no-dev --project . 2>&1 | tail -20
Uninstalled 5 packages in 85ms
- iniconfig==2.3.1
- packaging==26.3
- pluggy==1.7.0
- pytest==9.1.1
- ruff==0.17.2
file: ~/Desktop/repos/doxify/action.yml
content:
name: Doxify Verify
description: Verify Doxify sessions or evidence-marked READMEs and fail on unsupported claims.
author: Ekomjah
branding:
icon: shield
color: green
inputs:
session:
description: Compile session directory to verify.
required: false
default: ""
artifact:
description: README or case study file to audit.
required: false
default: ""
evidence:
description: Evidence JSON for artifact-only verification.
required: false
default: ""
fail-on:
description: Comma-separated claim statuses that fail the check.
required: false
default: needs_evidence,rejected
python-version:
description: Python version used to run Doxify.
… [truncated 1264 chars]
Wrote file successfully.
file: ~/Desktop/repos/doxify/.github/workflows/ci.yml
old:
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-found: error
new:
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-found: error
verify:
name: Verify evidence-backed claims
runs-on: ubuntu-latest
env:
SENTRY_DSN: ""
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Verify self-audit README
… [truncated 691 chars]
Edit applied successfully.
$ python3 - <<'PY'
import yaml
from pathlib import Path
for path in [Path('action.yml'), Path('.github/workflows/ci.yml')]:
data = yaml.safe_load(path.read_text())
print(path, 'OK', type(data).__name__)
PY
action.yml OK dict
.github/workflows/ci.yml OK dict
$ SENTRY_DSN="" uv run --project "$PWD" --no-sync doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check; echo POSITIVE_EXIT:$?
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
POSITIVE_EXIT:0
$ SENTRY_DSN="" uv run --project "$PWD" --no-sync doxify verify output/next-dashboard/case-study --artifact output/next-dashboard/case-study/case-study.md --fail-on needs_evidence,rejected --check >/tmp/doxify-negative.out 2>&1; code=$?; tail -5 /tmp/doxify-negative.out; echo NEGATIVE_EXIT:$code
C11 needs_evidence This commit timeline reveals a relatively recent development
cycle, highlighting the need for streamlined workflows.
No citation token was found.
... and 61 more
Verification failed: 105 claim(s) matched --fail-on.
NEGATIVE_EXIT:1
file: ~/Desktop/repos/doxify/.github/workflows/ci.yml
old:
uses: ./
with:
session: output/next-dashboard/case-study
artifact: output/next-dashboard/case-study/case-study.md
fail-on: needs_evidence,rejected
new:
uses: ./
with:
artifact: output/next-dashboard/case-study/case-study.md
evidence: output/next-dashboard/case-study/evidence.json
fail-on: needs_evidence,rejected
Edit applied successfully.
$ ls -lh docs/self-audit/evidence.json; head -30 docs/self-audit/evidence.json
-rw-rw-r-- 1 [user] [user] 30K Oct 24 18:48 docs/self-audit/evidence.json
{
"schema_version": "1.0",
"repo": {
"path": ".",
"name": "doxify",
"head_sha": "2f2bfdd69d0bcb18747d97db147083e1ebc9fe8a",
"branch": "main",
"commit_count": 8,
"first_commit_date": "2026-10-23",
"last_commit_date": "2026-10-24",
"tracked_file_count": 51,
"languages": {
"Python": 28,
"JSON": 13,
"Markdown": 4,
"TOML": 1,
"YAML": 1
},
"readme_path": "README.md",
"manifests": [
"pyproject.toml"
],
"test_files": [
"tests/test_ollama_client.py",
"tests/test_readme.py",
"tests/test_scout.py"
],
"deploy_files": [
".github/workflows/ci.yml"
]
$ git log --oneline -5; git status --short | head -40
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
d5c94b8 feat(scout): add evidence-backed upgrade impact analysis
f4b1d79 chore(demo): add precomputed nextjs-dashboard compile artifacts
95a5e02 build(ci): add Python 3.10 TOML fallback and package CI
11a198d docs(plan): align roadmap with CLI MVP and README mode
M .github/workflows/ci.yml
M README.md
M src/doxify/pipeline.py
?? action.yml
?? docs/
?? src/doxify/audit.py
?? tests/test_audit.py
?? tests/test_self_audit.py
?? tests/test_verify_cli.py
$ git status --short; git diff --stat; rg -n "def verify\(" -A 20 src/doxify/cli.py | head -40; git show HEAD:src/doxify/cli.py | rg -n "def verify\(" -A 10 | head -30
M .github/workflows/ci.yml
M README.md
M src/doxify/pipeline.py
?? action.yml
?? docs/
?? src/doxify/audit.py
?? tests/test_audit.py
?? tests/test_self_audit.py
?? tests/test_verify_cli.py
.github/workflows/ci.yml | 32 ++++++++++++++++++++++++++
README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++-
src/doxify/pipeline.py | 11 +++++++--
3 files changed, 98 insertions(+), 3 deletions(-)
372:def verify(
373- session: Path | None = typer.Argument(
374- None,
375- exists=True,
376- file_okay=False,
377- resolve_path=True,
378- help="Compile session directory. Omit when auditing an artifact directly.",
379- ),
380- artifact: Path | None = typer.Option(
381- None,
382- "--artifact",
383- exists=True
… [truncated 621 chars]
$ SENTRY_DSN="" uv run pytest -q
................................................... [100%]
51 passed in 5.57s
$ uv run ruff check .
All checks passed!
$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
$ python3 - <<'PY'
import yaml
from pathlib import Path
for path in [Path('action.yml'), Path('.github/workflows/ci.yml')]:
yaml.safe_load(path.read_text())
print(path, 'OK')
PY
action.yml OK
.github/workflows/ci.yml OK
$ SENTRY_DSN="" uv run --project "$PWD" --no-sync doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check >/tmp/doxify-positive.out 2>&1; code=$?; tail -8 /tmp/doxify-positive.out; echo POSITIVE_EXIT:$code
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
POSITIVE_EXIT:0
$ SENTRY_DSN="" uv run --project "$PWD" --no-sync doxify verify --artifact output/next-dashboard/case-study/case-study.md --evidence output/next-dashboard/case-study/evidence.json --fail-on needs_evidence,rejected --check >/tmp/doxify-negative2.out 2>&1; code=$?; tail -6 /tmp/doxify-negative2.out; echo NEGATIVE_EXIT:$code
Citation does not overlap with the claim's key terms.
C11 needs_evidence This commit timeline reveals a relatively recent development
cycle, highlighting the need for streamlined workflows.
No citation token was found.
... and 61 more
Verification failed: 71 claim(s) matched --fail-on.
NEGATIVE_EXIT:1
$ mkdir -p /tmp/doxify-action-caller && cp README.md /tmp/doxify-action-caller/README.md && mkdir -p /tmp/doxify-action-caller/docs/self-audit && cp docs/self-audit/evidence.json /tmp/doxify-action-caller/docs/self-audit/evidence.json && printf '[project]\nname="caller"\nversion="0.0.0"\nrequires-python=">=3.10"\n' > /tmp/doxify-action-caller/pyproject.toml
(no output)
$ SENTRY_DSN="" uv run --project ~/Desktop/repos/doxify --no-sync doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check; echo EXIT:$?
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
EXIT:0
$ rm -rf /tmp/doxify-action-caller /tmp/doxify-positive.out /tmp/doxify-negative.out /tmp/doxify-negative2.out
(no output)
$ GITHUB_ACTION_PATH="$PWD" INPUT_SESSION="" INPUT_ARTIFACT="README.md" INPUT_EVIDENCE="docs/self-audit/evidence.json" INPUT_FAIL_ON="needs_evidence,rejected" SENTRY_DSN="" bash -euo pipefail -c '
uv sync --frozen --no-dev --project "$GITHUB_ACTION_PATH" >/tmp/doxify-action-sync.log 2>&1
args=(verify)
if [[ -n "$INPUT_SESSION" ]]; then args+=("$INPUT_SESSION"); fi
if [[ -n "$INPUT_ARTIFACT" ]]; then args+=(--artifact "$INPUT_ARTIFACT"); fi
if [[ -n "$INPUT_EVIDENCE" ]]; then args+=(--evidence "$INPUT_EVIDENCE"); fi
args+=(--fail-on "$INPUT_FAIL_ON" --check)
uv run --project "$GITHUB_ACTION_PATH" --no-sync doxify "${args[@]}"
'; echo ACTION_EXIT:$?; tail -5 /tmp/doxify-action-sync.log
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
ACTION_EXIT:0
- iniconfig==2.3.1
- packaging==26.3
- pluggy==1.7.0
- pytest==9.1.1
- ruff==0.17.2
$ git status --short; git diff --stat; git diff -- .github/workflows/ci.yml README.md src/doxify/pipeline.py | sed -n '1,220p'
M .github/workflows/ci.yml
M README.md
M src/doxify/pipeline.py
?? action.yml
?? docs/
?? src/doxify/audit.py
?? tests/test_audit.py
?? tests/test_self_audit.py
?? tests/test_verify_cli.py
.github/workflows/ci.yml | 32 ++++++++++++++++++++++++++
README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++-
src/doxify/pipeline.py | 11 +++++++--
3 files changed, 98 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2704633..74e0a7d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,3 +41,35 @@ jobs:
name: dist
path: dist/
if-no-files-found: error
+
+ verify:
+ name: Verify evidence-backed claims
+ runs-on: ubuntu-latest
+ env:
+ SENTRY_DSN: ""
+
… [truncated 5472 chars]
$ uv sync --frozen >/tmp/doxify-sync.log 2>&1; tail -5 /tmp/doxify-sync.log; uv run pytest -q >/tmp/doxify-pytest.log 2>&1; code=$?; tail -5 /tmp/doxify-pytest.log; echo PYTEST_EXIT:$code; rm -f /tmp/doxify-sync.log /tmp/doxify-pytest.log /tmp/doxify-action-sync.log
+ iniconfig==2.3.1
+ packaging==26.3
+ pluggy==1.7.0
+ pytest==9.1.1
+ ruff==0.17.2
................................................... [100%]
51 passed in 4.04s
PYTEST_EXIT:0
$ uv run ruff check .
All checks passed!
$ SENTRY_DSN="" uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check >/tmp/doxify-check.log 2>&1; code=$?; tail -6 /tmp/doxify-check.log; echo POSITIVE_EXIT:$code; rm -f /tmp/doxify-check.log
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
POSITIVE_EXIT:0
$ SENTRY_DSN="" uv run doxify verify --artifact output/next-dashboard/case-study/case-study.md --evidence output/next-dashboard/case-study/evidence.json --fail-on needs_evidence,rejected --check >/tmp/doxify-check-neg.log 2>&1; code=$?; tail -4 /tmp/doxify-check-neg.log; echo NEGATIVE_EXIT:$code; rm -f /tmp/doxify-check-neg.log
cycle, highlighting the need for streamlined workflows.
No citation token was found.
... and 61 more
Verification failed: 71 claim(s) matched --fail-on.
NEGATIVE_EXIT:1
$ uv build >/tmp/doxify-build.log 2>&1; code=$?; tail -6 /tmp/doxify-build.log; echo BUILD_EXIT:$code; rm -f /tmp/doxify-build.log
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
BUILD_EXIT:0
{
"todos": [
{
"content": "Implement artifact audit engine and tests",
"status": "completed",
"priority": "high"
},
{
"content": "Extend verify CLI/pipeline with fail-on, artifact, evidence, check",
"status": "completed",
"priority": "high"
},
{
"content": "Add self-audit evidence and README markers/docs",
"status": "completed",
"priority": "high"
},
{
"content": "Use Copilot to draft action runner command; add action.yml",
"status": "completed",
"priority": "medium"
},
{
"content": "Add CI workflow positive/negative verification",
"status": "completed",
"priority": "medium"
},
{
"content": "Run ruff, pytest, build, and action command checks",
"status": "completed",
"priority": "high"
}
]
}
[
{
"content": "Implement artifact audit engine and tests",
"status": "completed",
"priority": "high"
},
{
"content": "Extend verify CLI/pipeline with fail-on, artifact, evidence, check",
"status": "completed",
"priority": "high"
},
{
"content": "Add self-audit evidence and README markers/docs",
"status": "completed",
"priority": "high"
},
{
"content": "Use Copilot to draft action runner command; add action.yml",
"status": "completed",
"priority": "medium"
},
{
"content": "Add CI workflow positive/negative verification",
… [truncated 182 chars]
{
"todos": [
{
"content": "Implement artifact audit engine and tests",
"status": "completed",
"priority": "high"
},
{
"content": "Extend verify CLI/pipeline with fail-on, artifact, evidence, check",
"status": "completed",
"priority": "high"
},
{
"content": "Add self-audit evidence and README markers/docs",
"status": "completed",
"priority": "high"
},
{
"content": "Attempt Copilot CLI (unavailable); add action.yml manually",
"status": "completed",
"priority": "medium"
},
{
"content": "Add CI workflow positive/negative verification",
"status": "completed",
"priority": "medium"
},
{
"content": "Run ruff, pytest, build, and action command checks",
"status": "completed",
"priority": "high"
}
]
}
[
{
"content": "Implement artifact audit engine and tests",
"status": "completed",
"priority": "high"
},
{
"content": "Extend verify CLI/pipeline with fail-on, artifact, evidence, check",
"status": "completed",
"priority": "high"
},
{
"content": "Add self-audit evidence and README markers/docs",
"status": "completed",
"priority": "high"
},
{
"content": "Attempt Copilot CLI (unavailable); add action.yml manually",
"status": "completed",
"priority": "medium"
},
{
"content": "Add CI workflow positive/negative verification",
… [truncated 182 chars]
$ rg -n "/home/|OLLAMA_API_KEY|secret|token" docs/self-audit/evidence.json | head -20 || true; git diff --check
(no output)
file: ~/Desktop/repos/doxify/README.md
old:
## Verify in CI
The composite action runs the same deterministic checker. Point it at an
evidence-marked README:
new:
## Verify in CI
The composite action runs the same deterministic checker. Unmarked sections
fail; sections marked `doxify-attested` are author-owned and pass unless
`attested` is added to `fail-on`. Point it at an evidence-marked README:
Edit applied successfully.
$ SENTRY_DSN="" uv run pytest -q tests/test_self_audit.py tests/test_audit.py tests/test_verify_cli.py
.............. [100%]
14 passed in 2.70s
$ SENTRY_DSN="" uv run pytest -q
................................................... [100%]
51 passed in 3.14s
$ uv run ruff check .
All checks passed!
$ python3 - <<'PY'
import yaml
for path in ('action.yml', '.github/workflows/ci.yml'):
yaml.safe_load(open(path))
print(path, 'OK')
PY
action.yml OK
.github/workflows/ci.yml OK
$ git status --short; git diff --check; git diff --stat
M .github/workflows/ci.yml
M README.md
M src/doxify/pipeline.py
?? action.yml
?? docs/
?? src/doxify/audit.py
?? tests/test_audit.py
?? tests/test_self_audit.py
?? tests/test_verify_cli.py
.github/workflows/ci.yml | 32 ++++++++++++++++++++++++++
README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++-
src/doxify/pipeline.py | 11 +++++++--
3 files changed, 99 insertions(+), 3 deletions(-)
$ uv build >/tmp/doxify-build.log 2>&1; code=$?; tail -4 /tmp/doxify-build.log; echo BUILD_EXIT:$code; rm -f /tmp/doxify-build.log
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
BUILD_EXIT:0
$ SENTRY_DSN="" uv run doxify verify 2>&1 | tail -8; echo EXIT:${pipestatus[1]:-$?}
Usage: doxify verify [OPTIONS] [session]
Try 'doxify verify --help' for help.
╭─ Error ──────────────────────────────────────────────────────────────────────╮
│ Invalid value: Provide SESSION or --artifact. │
╰──────────────────────────────────────────────────────────────────────────────╯
EXIT:2
$ git diff --check; SENTRY_DSN="" uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check >/tmp/final-check.log 2>&1; code=$?; tail -4 /tmp/final-check.log; echo FINAL_CHECK_EXIT:$code; rm -f /tmp/final-check.log
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
FINAL_CHECK_EXIT:0
Implemented:
-
src/doxify/audit.py: verifies evidence-marked README sections, allows explicitdoxify-attestedsections, audits case-study markers, and rejects unknown evidence IDs. -
doxify verify: supports--artifact,--evidence,--fail-on, and--check; exits non-zero on selected claim statuses. -
README.md: added attested markers, a verifiedProject factssection, and CI usage docs. -
docs/self-audit/evidence.json: committed evidence snapshot for the README. -
action.yml+.github/workflows/ci.yml: composite action plus positive self-audit and expected-failure case-study checks.
Verified:
- uv run pytest -q → 51 passed
- uv run ruff check . → pass
- uv build → pass
- Self-audit → 5 verified, 8 attested, 0 unresolved
- Precomputed case study → exit 1 as expected
Notes: Copilot CLI was unavailable (gh copilot reports not installed; npx @github/copilot timed out), so action.yml was written manually. The action disables Sentry with SENTRY_DSN=""; the local CLI’s default Sentry DSN remains a separate privacy issue.
now for the meat of the project, it's peak,is writing all about the proect. write it in the fromat:This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
What I Built
<!-- What does it do, and what effort does it save? Who gets to be a little lazier because of it? -->
Demo
<!-- Share a deployed link or a video demo. -->
Code
<!-- Show us the code! You can embed a GitHub repo directly into your post. -->
How I Built It
<!-- Which open-source AI did you use (open-weight models, agent harnesses, frameworks, local inference), and how is your project built around it? -->
Why Does Open Innovation Matter?
<!-- Why does open innovation matter for what you built? What did it make possible that a closed API wouldn't? -->
My Agent Session
<!-- Optional, but judges love it. Save your session with DevRelay and embed it with the agent_session tag (see the challenge page), or link to it. -->
The Lazy Payoff
<!-- Optional, but this is where you can make your post stand out. How much time or effort does it actually save? Did you (or someone else) use it this week? If not, remove this section. -->
Prize Categories
<!-- Which partner categories are you entering? List every one that applies, or remove this section. -->
<!-- Team Submissions: Please pick one member to publish the submission and credit teammates by listing their DEV usernames directly in the body of the post. -->
<!-- Thanks for participating! --> note that you may have a shitty project but a great writeup will surely sell you so put alot of effort into it. write on what is already impleented and wired or in the process. create a new file submission.md for this process. you can get stuff from @PLAN.md for this. but ofcourse, not a full copy. clearly define what exaclty this project is, and who it is for. add some core code snippets. remember readme and scout are at the core of doxify, not bolted on top; but it also is a case study tool too
Called the Read tool with the following input: {"filePath":"~/Desktop/repos/doxify/PLAN.md"}
<path>~/Desktop/repos/doxify/PLAN.md</path>
<type>file</type>
<content>
1: # Doxify — Hacktoberfest Week 3 Project Plan
2:
3: > Working title: Doxify
4: > Tagline: Portfolio case studies you can defend.
5: > Status: CLI MVP implemented; Scout upgrade analysis implemented; opt-in Ollama Cloud inference added; hosted mode planned.
6: > Plan date: 2026-10-22.
7: > Last implementation update: 2026-10-24.
8: > Hackathon deadline: 2026-10-25, 11:59 PM PDT = 2026-10-26, 7:59 AM WAT.
9: > Target category: Best Use of Gemma.
10: > Demo input repo: ~/Desktop/repos/scholarship-mcp.
11:
12: > Implementation status: The working MVP is a local CLI in src/doxify using
13: > Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. Local inference is
14: > still the default; --provider cloud routes model calls to Ollama Cloud for
15: > users who do not want to install Ollama locally. The FastAPI and Next.js
16: > sections below describe the deferred hosted UI, not the current build.
17: > Current commands: doctor, scan, compile, readme, scout, verify,
18: > attest, and export.
19:
20: ---
21:
22: ## 0. How to use this document
23:
24: This document is the single source of truth for the project. It is intentionally verbose so that implementation decisions do not have to be re-derived later.
25:
26: Read it in this order:
27:
28: 1. Sections 1–6 — why this project exists and why the demo repo was chosen.
29: 2. Sections 7–16 — what the product does, how it looks, and how it is built.
30: 3. Sections 17–21 — the execution plan, tests, demo, and submission.
31: 4. Sections 22–25 — risks, cut lines, open decisions, and first commands.
32: 5. Appendices — exact evidence from the demo repo, sample content, and references.
33:
34: If time is short, the non-negotiable core is:
35:
36: - scan a local repo,
37: - build evidence cards,
38: - generate a case study where every sentence cites evidence,
39: - flag unsupported or contradicted claims,
40: - generate an interview defense pack,
41: - export Markdown,
42: - run fully locally with Gemma via Ollama.
43:
44: Everything else is optional.
45:
46: ---
47:
48: ## 1. One-page summary
49:
50: Doxify is a local-first portfolio case study compiler.
51:
52: It takes a finished software project and turns it into:
53:
54: - a portfolio-ready case study,
55: - an evidence ledger,
56: - an interview defense pack,
57: - and an optional evidence-backed README.
58:
59: The important difference is that it does not simply ask a language model to write a story about a repository. It first extracts evidence from the repository, then writes only from that evidence, then verifies that every claim is either:
60:
61: - supported by code or git history,
62: - explicitly attested by the user,
63: - or flagged as missing evidence.
64:
65: The product thesis is simple:
66:
67: > Most AI portfolio tools generate confidence. Doxify generates defensibility.
68:
69: The demo project is scholarship-mcp, a working MCP server with 20 commits, 9 tools, 3 database models, 26 seeded scholarship records, and no README. That is the perfect before/after story: a real project with a real technical arc, but no written portfolio narrative.
70:
71: The current implementation is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, and a local gemma3:1b model through Ollama. Local inference remains the default: it reads a local repository, writes compile artifacts to a local session directory, and calls only the local Ollama server. Users without a local Ollama install can explicitly opt into Ollama Cloud with --provider cloud and OLLAMA_API_KEY; in that mode evidence cards are sent to Ollama Cloud's hosted model instead. The tool has no database, no telemetry, and no account system of its own.
72:
73: The hackathon submission is a brand-new repository. scholarship-mcp is only the input used for the demonstration; it is not the submitted project.
74:
75: ---
76:
77: ## 2. The problem
78:
79: ### 2.1 The user problem
80:
81: Developers finish projects and then fail to write them up.
82:
83: The work already exists:
84:
85: - commits,
86: - code,
87: - tests,
88: - deployment files,
89: - design decisions,
90: - bug fixes,
91: - refactors,
92: - README fragments,
93: - notes,
94: - screenshots.
95:
96: But turning that material into a portfolio case study is a separate, dreaded task. It requires:
97:
98: - remembering why decisions were made,
99: - reconstructing a timeline,
100: - deciding what matters,
101: - writing for recruiters,
102: - writing for interviewers,
103: - checking that every claim is true,
104: - and making the result sound like a person, not a template.
105:
106: Most people postpone it. Their portfolio stays stale or empty.
107:
108: ### 2.2 The AI problem
109:
110: The obvious solution is “ask an LLM to write the case study.”
111:
112: That creates a new problem: AI slop that collapses in an interview.
113:
114: A generic model will happily write:
115:
116: - “Built a scalable platform used by thousands of users.”
117: - “Reduced latency by 40%.”
118: - “Led a team of five engineers.”
119: - “Implemented OAuth authentication.”
120:
121: None of those claims may be true. Some may be partly true. Some may be impossible to verify from code. The developer then has to defend them in a hiring conversation.
122:
123: The failure is structural:
124:
125: - the model was given a repository,
126: - it was asked for a narrative,
127: - and it optimized for a confident story rather than a true one.
128:
129: Prompting more carefully does not fix this. The missing ingredient is evidence with a verification step.
130:
131: ### 2.3 The private-work problem
132:
133: The problem gets worse for private or NDA work.
134:
135: Many developers cannot share the repository at all. Their best work lives behind a company login. Cloud portfolio tools ask them to conne
… [truncated 52134 chars]
[attached file: PLAN.md]
$ rg -n "def .*quote|quote|verified|match|search|extract|fallback" src/doxify/scout.py | head -80
41:SERPAPI_URL = "https://serpapi.com/search.json"
164:def _api_match_keys(api: str) -> set[str]:
193: quote: str,
200: self.quote = quote
235: match = _SEMVER_RE.search(value.strip())
236: if not match:
238: major = int(match.group(1))
239: minor = int(match.group(2) or 0)
240: patch = int(match.group(3) or 0)
241: precision = 3 if match.group(3) is not None else 2 if match.group(2) is not None else 1
242: suffix = match.group(4) or ""
261:def _tag_matches_package(tag: str, package: str) -> bool:
286: if note.version in seen or not _tag_matches_package(note.version, package):
324: if _BREAKING_RE.search(note.body):
543: warnings.append(f"SerpApi fallback failed: {exc}")
549: "set SERPAPI_API_KEY for the SerpApi fallback, o
… [truncated 3694 chars]
$ sed -n '1,180p' src/doxify/scout.py
from __future__ import annotations
import html
import json
import os
import re
from collections.abc import Callable
from dataclasses import dataclass
from html.parser import HTMLParser
from pathlib import Path, PurePosixPath
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from .errors import ModelError, ScoutError
from .git_repo import RepoContext, collect_repo_context, open_repo
from .models import (
BreakingChange,
CodeMatch,
ReleaseNote,
RepoSummary,
ScoutReport,
ScoutScore,
ScoutSource,
ScoutSummary,
SessionManifest,
)
from .ollama_client import OllamaClient
from .paths import is_secret, is_skippable, read_text_limited
from .prompts import scout_batch
… [truncated 3315 chars]
$ sed -n '180,420p' src/doxify/scout.py
report: ScoutReport
output_dir: Path
markdown_path: Path
json_path: Path
notes_path: Path
manifest_path: Path
class _RawChange:
def __init__(
self,
*,
title: str,
quote: str,
release: str | None,
apis: list[str],
migration: str | None,
severity: str,
) -> None:
self.title = title
self.quote = quote
self.release = release
self.apis = apis
self.migration = migration
self.severity = severity
class _HTMLTextExtractor(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.parts: list[str] = []
self._skip = 0
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
if tag in
… [truncated 7184 chars]
[session context compacted]
Objective
- Write a high-effort
submission.mdfor the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady using the exact DEV template. - Accurately present Doxify’s implemented, wired, and in-process work, with core code snippets and a clear definition of the product and audience.
- Prior GitHub Action work is complete:
doxify verifyis packaged as a reusable action and runs against Doxify’s own README.
Important Details
- Submission header must be exactly:
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)* - Required/optional sections in order:
What I Built,Demo,Code,How I Built It,Why Does Open Innovation Matter?,My Agent Session,The Lazy Payoff,Prize Categories. - Target prize category: Best Use of Gemma.
- Project definition: Doxify is a local-first evidence compiler that turns repositories into evidence-backed READMEs, portfolio case studies, and interview defense packs, plus scoped dependency-upgrade impact reports via Scout.
- Primary audience: developers with finished projects but no portfolio write-up, especially private/NDA work, interview prep, and people who dislike writing about themselves. Secondary: bootcamp grads, career switchers, OSS maintainers, students, freelancers.
- Core thesis: “Most AI portfolio tools generate confidence. Doxify generates defensibility.”
- README and Scout are core features, not add-ons; the case-study/evidence/defense workflow is also core.
- Implemented:
doctor,scan,compile,readme,scout,verify,attest,export; local Ollamagemma3:1b; opt-in cloud via--provider cloud/OLLAMA_API_KEYwithgemma4:31bdefault; evidence cards; deterministic verification; README hidden evidence markers; case study; defense pack; Scout release-note extraction, exact-quote validation, deterministic fallback, and code matching. - In-process/planned, must not be claimed as shipped: FastAPI hosted UI, Next.js review UI, job API, session storage UI.
- Demo input
scholarship-mcpis only input data, not the submitted project. Known facts: 20 commits, 9 MCP tools, 3 Mongoose models, 26 seeded scholarship records, no README. - Do not claim deployment, production readiness, real users, performance gains, test-pass status, revenue, team size, awards, or “first/only” without evidence.
- Demo section: no deployed URL or video is known; use local CLI reproduction/repo link or clearly mark a demo placeholder.
- Code section: likely repo
https://github.com/Ekomjah/doxify; action examples useEkomjah/doxify@main. -
My Agent Sessionis optional; no DevRelay/agent_sessionartifact is available. -
The Lazy Payoffmust not fabricate usage; frame as designed time savings or clearly labeled estimate. - Suggested code snippets to include: evidence extraction/verification (
src/doxify/evidence.py,verifier.py,audit.py), README provenance (readme.py), Scout exact-quote/code-match pipeline (scout.py), and compile/session flow (pipeline.py). - GitHub Action implementation:
-
action.ymlinputs:session,artifact,evidence,fail-on,python-version,working-directory. - Runs
uv sync --frozen --no-dev --project "$GITHUB_ACTION_PATH"thendoxify verify ... --check. - Sets
SENTRY_DSN="". -
doxify verifynow supports optionalSESSION,--artifact,--evidence,--fail-on,--check; default failures areneeds_evidence,rejected. - Root README uses
<!-- doxify-evidence: E2 E4 E5 E6 E7 -->and<!-- doxify-attested: ... -->markers. - Positive self-audit: 5 verified, 8 attested, 0 unresolved.
- Negative component fixture: precomputed
output/next-dashboard/case-studyreports 71 failing artifact claims and exits 1.
-
- Copilot requirement is unresolved:
gh copilotreports “Copilot CLI not installed”;npx @github/copilottimed out.action.ymlwas written manually. - Sentry caveat:
src/doxify/monitoring.pystill defaults to a hardcoded DSN for local CLI runs; the action disables it withSENTRY_DSN="". - Working tree is dirty.
src/doxify/cli.pyverify changes appear present/committed;src/doxify/audit.py,tests/test_audit.py,tests/test_verify_cli.py,tests/test_self_audit.py,action.yml, anddocs/self-audit/evidence.jsonare untracked.README.md,src/doxify/pipeline.py, and.github/workflows/ci.ymlare modified. - No
submission.mdhas been created yet. - No commits should be made unless the user asks.
Work State
Completed
- Provider-aware
OllamaClientwith local/cloud auth, provider defaults, health checks, cloud warnings, manifests, and tests. - Scout implementation and tests;
benchmarks/scout-react-18-19.json; concurrent commits landed Scout and Sentry monitoring. - README output mode with hidden evidence comments, unresolved-claim omission, temp sessions, backups, and tests.
- Case-study compiler, claims verification, defense-pack generation, and export.
- New artifact audit engine in
src/doxify/audit.py: evidence-marked sections, attested sections, strict unmarked-section failures, case-study visible markers, unknown evidence ID rejection. - CLI/pipeline verification extensions:
--artifact,--evidence,--fail-on,--check; read-only session verification. - Root README self-audit markers and
Project factssection;docs/self-audit/evidence.json. - Root
action.yml; CIverifyjob with positive self-audit and expected-failure case study assertion. - Verified:
uv run pytest -q→ 51 passed;uv run ruff check .→ pass;uv build→ pass; action-style positive step exit 0; negative case study exit 1; YAML parses with systempython3PyYAML.
Active
- Drafting
submission.mdwith the exact challenge template, strong narrative, clear audience definition, core code snippets, and honest implemented/in-process distinction.
Blocked
- Copilot CLI is unavailable in this environment, so Copilot … [truncated 2375 chars]
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
$ sed -n '620,700p' src/doxify/scout.py && sed -n '900,980p' src/doxify/scout.py && sed -n '1080,1160p' src/doxify/scout.py
def quote_in_notes(quote: str, body: str) -> bool:
normalized = _normalize_quote(quote)
return len(normalized) >= 12 and normalized in _normalize_quote(body)
def normalize_api_tokens(values: Any) -> list[str]:
if isinstance(values, str):
raw_values: list[Any] = [values]
elif isinstance(values, list):
raw_values = values
else:
return []
tokens: list[str] = []
seen: set[str] = set()
for raw in raw_values:
if not isinstance(raw, str):
continue
for piece in re.split(r"[,;]\s*", raw):
token = piece.strip().strip("`'\"")
token = token.replace("`", "")
token = re.sub(r"\(.*\)$", "", token)
token = re.sub(r"<.*>$", "", token)
token = token.strip(".,:;
… [truncated 7765 chars]
$ rg -n "def _verify_one|def verify_claim|needs_evidence|rejected|verified" src/doxify/verifier.py | head -60 && sed -n '150,300p' src/doxify/verifier.py
73:def verify_claims(claims: list[Claim], evidence: list[EvidenceCard]) -> list[Claim]:
77: verified: list[Claim] = []
81: claim.reason = "Author attestation; not verified from repository evidence."
83: verified.append(claim)
85: verified.append(
92: return verified
101: summary.verified += 1
105: summary.rejected += 1
107: summary.needs_evidence += 1
116: claim.reason = "Author attestation; not verified from repository evidence."
122:def _verify_one(
status, reason = forbidden
claim.status = status
claim.reason = reason
if status == ClaimStatus.NEEDS_EVIDENCE:
claim.gap_question = _gap_for(claim.text)
return claim
numbers = [normalize_number(value) for va
… [truncated 4703 chars]
$ rg -n "def build_readme|doxify-evidence|unresolved|def compile_repo|verify_claim|def verify_session" src/doxify/readme.py src/doxify/pipeline.py | head -80
src/doxify/pipeline.py:54:from .verifier import summarize_claims, verify_claims
src/doxify/pipeline.py:97:def compile_repo(
src/doxify/pipeline.py:174: claims = verify_claims(claims, scan.evidence)
src/doxify/pipeline.py:306:def verify_session(output_dir: Path, *, write: bool = True) -> VerificationReport:
src/doxify/pipeline.py:309: claims = verify_claims(claims_doc.claims, scan.evidence)
src/doxify/pipeline.py:339: claims = verify_claims(claims_doc.claims, scan.evidence)
src/doxify/readme.py:40:def build_readme(
src/doxify/readme.py:78: f"<!-- doxify-unresolved: {len(omitted)} claim(s) need evidence; "
src/doxify/readme.py:344: lines.extend(["", f"<!-- doxify-evidence: {ids} -->"])
$ sed -n '1,220p' README.md
# Doxify
Doxify compiles evidence-backed READMEs and portfolio case studies from local
git repositories. It also scouts breaking dependency upgrades against your
code.
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
<!-- doxify-attested: project description and positioning -->
## Install
Requires Python 3.10+ and git.
```bash
uv sync
uv run doxify --help
```
Or install the package and run `doxify` directly.
<!-- doxify-attested: installation instructions -->
## Local mode (default)
Local mode uses Ollama and `gemma3:1b`. Nothing about the repository leaves
your machine.
```bash
ollama serve
ollama pull gemma3:1b
doxify doctor
do
… [truncated 3593 chars]
$ sed -n '1,180p' src/doxify/verifier.py
from __future__ import annotations
import re
from .models import Claim, ClaimStatus, CompileSummary, EvidenceCard
from .text import content_tokens
NUMBER_RE = re.compile(r"(?<![\w.])(\d[\d,]*(?:\.\d+)?)(?![\w])")
CODE_TOKEN_RE = re.compile(r"`([^`]+)`|\b([a-z]+[A-Z][A-Za-z0-9_]*)\b")
FORBIDDEN_PATTERNS: list[tuple[str, re.Pattern[str], str]] = [
(
"oauth",
re.compile(r"\boauth\b", re.IGNORECASE),
"The cited evidence does not contain an OAuth implementation.",
),
(
"tests pass",
re.compile(r"\b(?:all\s+)?tests?\s+(?:pass|passed|passing)\b", re.IGNORECASE),
"Doxify did not run the project's tests.",
),
(
"deployment",
re.compile(
r"\b(?:deployed|in production|production-ready|live at|shipped t
… [truncated 5573 chars]
$ sed -n '40,140p' src/doxify/readme.py && sed -n '320,365p' src/doxify/readme.py
def build_readme(
result: CompileResult,
*,
session_dir: Path | None = None,
) -> str:
repo = result.manifest.repo
evidence = [card for card in result.evidence if _usable_card(card) is not None]
claims = result.claims
lines: list[str] = [GENERATED_MARKER]
if session_dir is not None:
lines.append(f"<!-- doxify-compile-session: {session_dir} -->")
lines.extend(
[
f"<!-- doxify-summary: {_summary_text(result)} -->",
"",
f"# {repo.name}",
"",
]
)
_overview(lines, repo, evidence)
_what_it_does(lines, evidence)
_tech_stack(lines, repo, evidence)
_project_layout(lines, evidence)
_getting_started(lines, repo, evidence)
_testing(lines, repo, evidence)
_limitat
… [truncated 3696 chars]
$ sed -n '90,210p' src/doxify/pipeline.py
status="scanned",
)
write_manifest(output / MANIFEST, manifest, [EVIDENCE])
emit("done", f"Evidence written to {output}")
return scan.repo, scan.evidence, output, manifest
def compile_repo(
repo_path: Path,
output_dir: Path,
*,
client: OllamaClient,
model: str,
draft: str | None = None,
role: str | None = None,
on_stage: StageCallback | None = None,
force: bool = False,
) -> CompileResult:
emit = on_stage or (lambda _stage, _detail: None)
emit("validate", "Checking repository and model")
client.ensure_ready()
_context, scan = scan_repository(repo_path)
output = prepare_output_dir(output_dir, force=force)
now = utc_now()
manifest = SessionManifest(
created_at=now,
updated_at=now,
… [truncated 2936 chars]
$ sed -n '1,160p' action.yml && sed -n '1,200p' docs/self-audit/evidence.json
name: Doxify Verify
description: Verify Doxify sessions or evidence-marked READMEs and fail on unsupported claims.
author: Ekomjah
branding:
icon: shield
color: green
inputs:
session:
description: Compile session directory to verify.
required: false
default: ""
artifact:
description: README or case study file to audit.
required: false
default: ""
evidence:
description: Evidence JSON for artifact-only verification.
required: false
default: ""
fail-on:
description: Comma-separated claim statuses that fail the check.
required: false
default: needs_evidence,rejected
python-version:
description: Python version used to run Doxify.
required: false
default: "3.10"
working-directory:
description: Directory used to reso
… [truncated 6359 chars]
$ rg -n "tagline|one-liner|defensib|audience|target user|Who it|who it|success criteria|8GB|under 10|Scholarship|nextjs-dashboard|Open innovation|Best Use|prize|Gemma|submission|DevRelay|Lazy|README|scout|case study|Case study" PLAN.md | head -120
9:> Target category: **Best Use of Gemma**.
17:> Current commands: `doctor`, `scan`, `compile`, `readme`, `scout`, `verify`,
30:3. **Sections 17–21** — the execution plan, tests, demo, and submission.
38:- generate a case study where every sentence cites evidence,
42:- run fully locally with Gemma via Ollama.
50:**Doxify** is a local-first portfolio case study compiler.
54:- a portfolio-ready case study,
57:- and an optional evidence-backed README.
67:> Most AI portfolio tools generate confidence. Doxify generates defensibility.
69:The demo project is `scholarship-mcp`, a working MCP server with 20 commits, 9 tools, 3 database models, 26 seeded scholarship records, and no README. That is the perfect before/after story: a real project with a real technical arc, but no written portfolio na
… [truncated 7678 chars]
$ sed -n '1,220p' PLAN.md
# Doxify — Hacktoberfest Week 3 Project Plan
> Working title: **Doxify**
> Tagline: **Portfolio case studies you can defend.**
> Status: CLI MVP implemented; Scout upgrade analysis implemented; opt-in Ollama Cloud inference added; hosted mode planned.
> Plan date: 2026-10-22.
> Last implementation update: 2026-10-24.
> Hackathon deadline: 2026-10-25, 11:59 PM PDT = 2026-10-26, 7:59 AM WAT.
> Target category: **Best Use of Gemma**.
> Demo input repo: `~/Desktop/repos/scholarship-mcp`.
> **Implementation status:** The working MVP is a local CLI in `src/doxify` using
> Python 3.10, Typer, Rich, Pydantic, GitPython, and Ollama. Local inference is
> still the default; `--provider cloud` routes model calls to Ollama Cloud for
> users who do not want to install Ollama locall
… [truncated 7316 chars]
$ find output/next-dashboard -maxdepth 2 -type f | sort | head -40 && printf '\n--- case study ---\n' && sed -n '1,140p' output/next-dashboard/case-study/case-study.md
output/next-dashboard/case-study/case-study.md
output/next-dashboard/case-study/claims.json
output/next-dashboard/case-study/defense.json
output/next-dashboard/case-study/defense.md
output/next-dashboard/case-study/evidence.json
output/next-dashboard/case-study/manifest.json
output/next-dashboard/case-study/sections.json
output/next-dashboard/case-study/verification.json
--- case study ---
# nextjs-dashboard: an evidence-backed case study
> Compiled locally with Doxify. Citations point to the evidence appendix.
> Unverified statements are marked [needs evidence]; author context is marked [attested].
## Snapshot
Okay, here’s the section text based on the provided evidence cards and goal: [needs evidence]
“This project, nextjs-dashboard, is a Next.js application focused on creating inte
… [truncated 12406 chars]
$ head -5 LICENSE && rg -n "version|requires-python|name =|dependencies" pyproject.toml | head -30
MIT License
Copyright (c) 2026 Ekomjah Edet
Permission is hereby granted, free of charge, to any person obtaining a copy
2:name = "doxify"
3:version = "0.1.0"
8: { name = "Ekomjah Edet", email = "[redacted-email]" }
10:requires-python = ">=3.10"
11:dependencies = [
35:target-version = "py310"
$ sed -n '220,360p' PLAN.md
The tool deliberately refuses to rush the truth. That is the “steady” part.
### 4.3 Why it fits the Gemma category
The product’s privacy promise depends on local inference:
- the repository may be private,
- the career claims may be sensitive,
- the output may mention internal architecture,
- the user may be offline.
Cloud inference is offered only as an explicit opt-in for users who cannot or do not want to install Ollama. The default remains `gemma3:1b` via local Ollama: it is small enough to run on the target laptop and open enough to be inspectable and portable. When `--provider cloud` is selected, the same Gemma-family workflow runs on Ollama Cloud (`gemma4:31b` by default).
The default submission demo should make local mode explicit:
- local model,
- local repo,
- no network c
… [truncated 4257 chars]
$ sed -n '2260,2400p' PLAN.md
- Show `ollama ps` or the local model call.
- Say: “In local mode: no API keys, no upload, no per-token cost.”
**3:40–4:00 — Close**
- Show the export.
- Show the claim ledger.
- Say: “A case study you can defend.”
### 19.2 Recording notes
- Record at 1080p if possible; 720p is acceptable.
- Use a clean browser profile.
- Hide personal notifications.
- Use a large font in the terminal.
- Keep the mouse movements deliberate.
- Do not claim anything the demo does not show.
- If the compile is slow, cut the waiting and add a timestamp overlay.
---
## 20. DEV post
### 20.1 Title options
1. “I had 20 commits and no README. I built a local Gemma tool that writes a portfolio case study—and refuses to lie.”
2. “Most AI portfolio tools write stories. Mine checks the receipts first.”
3. “Dox
… [truncated 2723 chars]
$ cat benchmarks/scout-react-18-19.json | head -80 && printf '\n--- scout tests ---\n' && rg -n "def test_" tests/test_scout.py | head -30
{
"package": "react",
"from": "18",
"to": "19",
"apis": [
"ReactDOM.render",
"ReactDOM.hydrate",
"unmountComponentAtNode",
"findDOMNode",
"renderToNodeStream",
"renderToStaticNodeStream",
"ReactDOM.createFactory",
"react-dom/test-utils",
"propTypes",
"defaultProps",
"contextTypes",
"getChildContext"
],
"notes": "Known React 19 API removals from the React 19 release notes. This is a replay fixture for checking extraction recall; adjust it to the APIs your own application actually uses."
}
--- scout tests ---
98:def test_parse_and_select_versions_across_major_boundary() -> None:
114:def test_select_releases_keeps_first_release_of_each_minor_under_cap() -> None:
125:def test_release_chunks_prefers_breaking_sections() -> None:
138:de
… [truncated 989 chars]
$ rg -n "def test_" tests/test_readme.py tests/test_verify_cli.py tests/test_audit.py tests/test_self_audit.py | head -60
tests/test_self_audit.py:12:def test_doxify_readme_verifies_against_current_repository() -> None:
tests/test_verify_cli.py:40:def test_verify_artifact_passes_with_evidence_marker(tmp_path: Path) -> None:
tests/test_verify_cli.py:74:def test_verify_artifact_fails_on_unsupported_claim(tmp_path: Path) -> None:
tests/test_verify_cli.py:109:def test_verify_artifact_requires_evidence_without_session(tmp_path: Path) -> None:
tests/test_verify_cli.py:121:def test_verify_check_does_not_rewrite_session(tmp_path: Path) -> None:
tests/test_verify_cli.py:144:def test_verify_session_fails_on_unresolved_claims(tmp_path: Path) -> None:
tests/test_audit.py:25:def test_annotated_readme_verifies_evidence_and_attested_sections(tmp_path: Path) -> None:
tests/test_audit.py:53:def test_annotated_readme_flags_uns
… [truncated 1315 chars]
$ rg -n "@app.command|def (compile|readme|scout|verify)|--out|--output|--provider|--model|--force|--notes|--expect|--deep" src/doxify/cli.py | head -120
86:@app.command()
98: "--provider",
134:@app.command()
137: out: Path | None = typer.Option(None, "--out", "-o", help="Output directory."),
139: force: bool = typer.Option(False, "--force", help="Allow an existing output directory."),
156:@app.command()
157:def compile(
159: out: Path | None = typer.Option(None, "--out", "-o", help="Output directory."),
172: "--provider",
175: force: bool = typer.Option(False, "--force", help="Allow an existing output directory."),
206:@app.command()
207:def readme(
211: "--output",
212: "--out",
228: "--provider",
231: force: bool = typer.Option(False, "--force", help="Overwrite an existing README target."),
267:@app.command()
268:def scout(
284: "--out",
290: "--notes",
299: "--expect
… [truncated 222 chars]
$ sed -n '1,180p' src/doxify/cli.py
from __future__ import annotations
import re
import shutil
from datetime import datetime
from pathlib import Path
import typer
from rich.table import Table
from .audit import (
ArtifactReport,
audit_artifact,
failing_claims,
parse_fail_on,
)
from .console import (
console,
error,
print_compile_summary,
print_readme_summary,
print_scan_summary,
print_scout_summary,
stage_progress,
)
from .errors import DoxifyError
from .models import ClaimStatus, CompileSummary, ScanResult
from .monitoring import init_monitoring, report_exception
from .ollama_client import DEFAULT_MODEL, OllamaClient
from .pipeline import (
attest_session,
compile_repo,
export_session,
load_evidence_file,
readme_repo,
scan_only,
verify_session,
)
fro
… [truncated 4993 chars]
$ rg -n "SECTION_SPECS|title=" src/doxify/sections.py src/doxify/pipeline.py src/doxify/models.py | head -40
rg: src/doxify/sections.py: No such file or directory (os error 2)
src/doxify/pipeline.py:43:from .retrieval import SECTION_SPECS, retrieve_evidence
src/doxify/pipeline.py:132: for index, spec in enumerate(SECTION_SPECS, start=1):
src/doxify/pipeline.py:133: emit("generate", f"[{index}/{len(SECTION_SPECS)}] Drafting {spec.title}")
src/doxify/pipeline.py:160: title=spec.title,
$ rg -n "SECTION_SPECS|title" src/doxify/retrieval.py | head -40 && sed -n '1,90p' src/doxify/retrieval.py
6:SECTION_SPECS: list[SectionSpec] = [
9: title="Snapshot",
15: title="The problem",
21: title="The approach",
27: title="The hard part",
33: title="What shipped",
39: title="What I would change",
56: " ".join([spec.title, spec.goal, *spec.keywords])
61: " ".join([card.title, card.text, card.quote or "", *card.keywords])
from __future__ import annotations
from .models import EvidenceCard, SectionSpec
from .text import content_tokens
SECTION_SPECS: list[SectionSpec] = [
SectionSpec(
id="snapshot",
title="Snapshot",
goal="Summarize what the project is, its stack, size, and timeline.",
keywords=["identity", "stack", "languages", "commits", "files", "readme"],
),
SectionSpec(
id="p
… [truncated 2734 chars]
$ sed -n '210,305p' src/doxify/pipeline.py
write_manifest(output / MANIFEST, manifest, artifacts)
emit("done", f"Session complete: {output}")
return CompileResult(
manifest=manifest,
evidence=scan.evidence,
sections=sections,
claims=claims,
defense=defense,
summary=summary,
)
def readme_repo(
repo_path: Path,
*,
client: OllamaClient,
model: str,
output: Path | None = None,
draft: str | None = None,
role: str | None = None,
force: bool = False,
cleanup: bool = False,
on_stage: StageCallback | None = None,
) -> ReadmeResult:
emit = on_stage or (lambda _stage, _detail: None)
safe_name = re.sub(r"[^A-Za-z0-9_.-]+", "-", repo_path.name) or "repository"
session_dir = Path(tempfile.mkdtemp(prefix=f"doxify-readme-{safe_nam
… [truncated 2211 chars]
file: ~/Desktop/repos/doxify/submission.md
content:
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
# Doxify: most AI portfolio tools write stories. This one checks the receipts.
I had a working project, 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
The work existed. The story did not.
Every AI writing tool I tried solved the blank page by making up a confident story. I wanted the opposite: a tool that would tell me what I could actually prove, what I could not, and what I needed to answer before putting a claim on my portfolio.
So I built **Doxify**, a local-first evidence compiler
… [truncated 17502 chars]
Wrote file successfully.
$ uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
$ uv run pytest -q
................................................... [100%]
51 passed in 1.46s
$ uv run ruff check . && uv build
All checks passed!
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: *This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
2:
3: # Doxify: most AI portfolio tools write stories. This one checks the receipts.
4:
5: I had a working project, 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: The work existed. The story did not.
8:
9: Every AI writing tool I tried solved the blank page by making up a confident story. I w
… [truncated 19372 chars]
$ git remote -v && git branch --show-current && git status --short
origin [redacted-email]:Ekomjah/doxify.git (fetch)
origin [redacted-email]:Ekomjah/doxify.git (push)
main
M .github/workflows/ci.yml
M README.md
M src/doxify/pipeline.py
?? action.yml
?? docs/
?? src/doxify/audit.py
?? submission.md
?? tests/test_audit.py
?? tests/test_self_audit.py
?? tests/test_verify_cli.py
$ sed -n '1,240p' src/doxify/claims.py
from __future__ import annotations
import re
from .models import Claim, ClaimStatus
from .text import compact_whitespace
CITATION_RE = re.compile(r"\[(E\d+)\]")
GAP_RE = re.compile(r"\[GAP\]", re.IGNORECASE)
SENTENCE_SPLIT_RE = re.compile(r"(?<=[.!?])((?:\s*\[E\d+\])*)\s+(?=[A-Z0-9\"“']|\[GAP\])")
PREAMBLE_RE = re.compile(
r"^(?:okay|sure|certainly|of course|here(?:['\u2019]s| is)|based on|the following)\b",
re.IGNORECASE,
)
HEADING_RE = re.compile(r"^\s*#{1,6}\s+")
BULLET_RE = re.compile(r"^\s*(?:[-*+]|\d+[.)])\s+")
def extract_claims(section_id: str, body: str, *, start_index: int = 1) -> list[Claim]:
claims: list[Claim] = []
for paragraph in _paragraphs(body):
for sentence in _sentences(paragraph):
text = compact_whitespace(sentence)
… [truncated 2936 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
Every sentence is parsed into a claim. Every `[E#]` is checked against the evidence ledger. A claim without a citation cannot pass. A citation to an evidence ID that does not exist is rejected outrigh
… [truncated 2 chars]
new:
Every substantive sentence is parsed into a claim. Every `[E#]` is checked against the evidence ledger. A generated claim without a citation cannot pass. A citation to an evidence ID that does not exist is rejected outright.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
After that, the verifier checks numbers against the cited evidence, checks code identifiers, checks token overlap, and rejects claims that code cannot prove: deployment, "tests pass," OAuth, user coun
… [truncated 378 chars]
new:
After that, the verifier checks numbers against the cited evidence, checks code identifiers, checks token overlap, and flags claims that code cannot prove: deployment, "tests pass," OAuth, user counts, performance improvements, awards, team size, and "first/only" claims.
If a sentence says a repository has 30 records and the evidence says 26, it is rejected. If it says "reduced latency by 40%" an
… [truncated 168 chars]
Edit applied successfully.
$ git ls-files output/next-dashboard | head -20 && git check-ignore -v submission.md || true
output/next-dashboard/case-study/case-study.md
output/next-dashboard/case-study/claims.json
output/next-dashboard/case-study/defense.json
output/next-dashboard/case-study/defense.md
output/next-dashboard/case-study/evidence.json
output/next-dashboard/case-study/manifest.json
output/next-dashboard/case-study/sections.json
output/next-dashboard/case-study/verification.json
$ wc -w submission.md && rg -n "TODO|PLACEHOLDER|\[.*\]\(" submission.md | head -20
2729 submission.md
1:*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
119:- [Case study](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/case-study.md)
120:- [Claim ledger](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/claims.json)
121:- [Defense pack](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/defense.md)
127:The full project is at [github.com/Ekomjah/doxify](https://github.com/Ekomjah/doxify).
131:- [`src/doxify/pipeline.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/pipeline.py) — the compile, README, verify, and export flows.
132:- [`src/doxify/evidence.py`](https://github.com/
… [truncated 533 chars]
Created submission.md with the exact challenge template, clear audience definition, README/Scout/case-study coverage, core code snippets, Gemma positioning, honest limitations, and artifact links.
Verified: 51 tests pass, Ruff/build pass, self-audit reports 5 verified + 8 attested. No commit made.
One remaining asset: the post currently uses checked-in artifacts as the demo; add a recorded 90-second walkthrough URL if the challenge requires a video.
cool. but you missed out on soe newer imolementaiondetails. i've added sentry and github actions too. explain their usecase, look up te code for them, present them very well. also, add to the Prize Categories, apart from gemma, sentry, serpapi, github copilot and actions and entry(currently undergoing implementaiton but treat as a finsihed feature: look up what they do and write it cohesively. and reframe doxify: it's a tool for sloths. they build the whole project, but then, when their brain is tired after making the last commit, they remmber they have to document their code in the readme to make their repo to any one who comes across it: interviewers, open source develoers, bots, or other fellow sloths. and when they decide to visit the app in a couple of years, after dependencies have gotten neer upgrades and theirs s deprecated, it helps them pinpoint where exactly and how they can immediately make theirs up to date. the case study(json and other md files generated) are just a way to help achieve this core objective. although, you can also preserve the story existing of
Surface Question it answers What it writes
Case study compiler What did I build, and can I defend it? case-study.md, claims.json, defense.md. note that this was the core of the competirion: [Pasted Slow and Steady
Build something with open-source AI at its core.
That can mean running an open-weight model, building on an open-source agent harness or framework, running inference locally, or all three. Whatever you pick, the open pieces should be what makes your project work.
In your post, tell us why open innovation matters for what you built. Does it run on a laptop with no internet? Keep someone's data off a server they don't control? Let you fine-tune, swap models, or change how your agent behaves? Cost nothing to run? Tell us where your open-based approach worked better than a closed one.
This Week's Theme: Slow and Steady
Build something with open-weight models or open-source AI that rewards laziness.
Tomorrow, October 20, is International Sloth Day, and we think sloths have it figured out. They move at their own pace, conserve their energy, and even help moderate our website. This week, we want you to channel your inner sloth and build something that does the work so you don't have to.
That can mean running an open-weight model, building on an open-source agent harness or framework, running inference locally, or all three. Whatever you pick, the open pieces should be what makes your project work.
Your build could help someone relax, automate a task they dread, or just make life a little easier. A few ideas to get you going:
A local agent that reads your group chats overnight and hands you a two-line summary in the morning
A wind-down companion that generates a calming bedtime story or ambient soundscape, running entirely offline
A fridge photo to grocery list tool, so you never have to stand there with the door open again
A file and inbox tidier that automatically sorts, renames, and archives the clutter you keep putting off
In your post, tell us why open innovation matters for what you built. Does running your own model mean your automation works without an internet connection? Keep your personal messages and files on your own machine? Let you set it up once and never pay for it again? Tell us where your open-based approach worked better than a closed one.
Submission Template
Show your work. We'd love to see how you built it. Save your agent session with DevRelay and embed it in your post, or link to it. It's optional, but it helps the judges understand your process.is project should demostrate just how it helps sloth become even mpore slothy. youcould even add in some sloth language into the wrie up to make it yet even more slothy. add more but short and boneshell code snippets that demonstrate some other technical,moving, cdritical parts of the repo and hw they alltie together; but stick to the earlier prescribed template overall
$ rg -n "Sentry|sentry|SERP|Serp|serp|Copilot|copilot|entry|Entry|CODEOWNERS|dependabot|action" -S README.md PLAN.md src tests .github action.yml pyproject.toml docs 2>/dev/null | head -200
(no output)
<shell_metadata>
User aborted the command
</shell_metadata>
cool. but you missed out on soe newer imolementaiondetails. i've added sentry and github actions too. explain their usecase, look up te code for them, present them very well. also, add to the Prize Categories, apart from gemma, sentry, serpapi, github copilot and actions and entry(currently undergoing implementaiton but treat as a finsihed feature: look up what they do and write it cohesively. and reframe doxify: it's a tool for sloths. they build the whole project, but then, when their brain is tired after making the last commit, they remmber they have to document their code in the readme to make their repo to any one who comes across it: interviewers, open source develoers, bots, or other fellow sloths. and when they decide to visit the app in a couple of years, after dependencies have gotten neer upgrades and theirs s deprecated, it helps them pinpoint where exactly and how they can immediately make theirs up to date. the case study(json and other md files generated) are just a way to help achieve this core objective. although, you can also preserve the story existing of
Surface Question it answers What it writes
Case study compiler What did I build, and can I defend it? case-study.md, claims.json, defense.md. note that this was the core of the competirion: [Pasted Slow and Steady
Build something with open-source AI at its core.
That can mean running an open-weight model, building on an open-source agent harness or framework, running inference locally, or all three. Whatever you pick, the open pieces should be what makes your project work.
In your post, tell us why open innovation matters for what you built. Does it run on a laptop with no internet? Keep someone's data off a server they don't control? Let you fine-tune, swap models, or change how your agent behaves? Cost nothing to run? Tell us where your open-based approach worked better than a closed one.
This Week's Theme: Slow and Steady
Build something with open-weight models or open-source AI that rewards laziness.
Tomorrow, October 20, is International Sloth Day, and we think sloths have it figured out. They move at their own pace, conserve their energy, and even help moderate our website. This week, we want you to channel your inner sloth and build something that does the work so you don't have to.
That can mean running an open-weight model, building on an open-source agent harness or framework, running inference locally, or all three. Whatever you pick, the open pieces should be what makes your project work.
Your build could help someone relax, automate a task they dread, or just make life a little easier. A few ideas to get you going:
A local agent that reads your group chats overnight and hands you a two-line summary in the morning
A wind-down companion that generates a calming bedtime story or ambient soundscape, running entirely offline
A fridge photo to grocery list tool, so you never have to stand there with the door open again
A file and inbox tidier that automatically sorts, renames, and archives the clutter you keep putting off
In your post, tell us why open innovation matters for what you built. Does running your own model mean your automation works without an internet connection? Keep your personal messages and files on your own machine? Let you set it up once and never pay for it again? Tell us where your open-based approach worked better than a closed one.
Submission Template
Show your work. We'd love to see how you built it. Save your agent session with DevRelay and embed it in your post, or link to it. It's optional, but it helps the judges understand your process.is project should demostrate just how it helps sloth become even mpore slothy. youcould even add in some sloth language into the wrie up to make it yet even more slothy. add more but short and boneshell code snippets that demonstrate some other technical,moving, cdritical parts of the repo and hw they alltie together; but stick to the earlier prescribed template overall . then, commit the updated @submission.md and push
Called the Read tool with the following input: {"filePath":"~/Desktop/repos/doxify/submission.md"}
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
2:
3: # Doxify: most AI portfolio tools write stories. This one checks the receipts.
4:
5: I had a working project, 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: The work existed. The story did not.
8:
9: Every AI writing tool I tried solved the blank page by making up a confident story. I wanted the opposite: a tool that would tell me what I could actually prove, what I could not, and what I needed to answer before putting a claim on my portfolio.
10:
11: So I built Doxify, a local-first evidence compiler for software work. It scans a local git repository, builds evidence cards, asks a Gemma model to write from those cards using a strict citation grammar, and then runs a deterministic verifier over every sentence.
12:
13: The model writes. The compiler checks.
14:
15: That distinction is the whole project.
16:
17: ## What I Built
18:
19: Doxify turns a finished repository into four things:
20:
21: - a portfolio-ready case study,
22: - a claim ledger with a status for every sentence,
23: - an interview defense pack built from the same evidence,
24: - and an evidence-backed README.
25:
26: It also includes Scout, which answers a narrower and very practical question: what in this dependency upgrade actually touches my repository?
27:
28: Doxify is a new MIT-licensed project built during this challenge. The scholarship-mcp repository is the demo input, not the submitted project.
29:
30: ### Three surfaces, one evidence engine
31:
32: | Surface | Question it answers | What it writes |
33: |---|---|---|
34: | Case study compiler | What did I build, and can I defend it? | case-study.md, claims.json, defense.md |
35: | README compiler | How do I explain this repository without inventing setup or usage? | An evidence-marked README.md |
36: | Scout | What in this upgrade is likely to break my code? | scout.md, scout.json, release notes, code matches |
37:
38: The README and Scout workflows are not bolt-ons. They use the same evidence and verification machinery as the case study. That is why they belong in the same product.
39:
40: ### Who Doxify is for
41:
42: Doxify is for developers who have shipped something but have not written it up:
43:
44: - job seekers with projects but no case studies,
45: - career switchers and bootcamp graduates who need to explain technical work,
46: - developers whose best work is private or under NDA,
47: - maintainers who need to understand a dependency upgrade before starting it,
48: - and teams that want documentation claims checked in CI instead of trusted by vibes.
49:
50: It is not a fully automatic portfolio site and it is not a marketing copy generator. It is a review tool. The useful output is not just a draft; it is the list of claims that did not survive verification.
51:
52: ### The case study workflow
53:
54: doxify compile produces six sections: Snapshot, The problem, The approach, The hard part, What shipped, and What I would change.
55:
56: For each narrative section, Doxify retrieves up to three evidence cards and gives them to Gemma. The model is instructed to write with [E#] citations that point back to the evidence ledger. Then the deterministic verifier checks those citations, numbers, identifiers, and a list of claims that code cannot prove.
57:
58: Unsupported sentences are not deleted silently. They are marked [needs evidence], paired with a gap question, and included in the claim ledger. Author context can be added with doxify attest, and it is visibly labeled as an attestation rather than treated as repository proof.
59:
60: The output is not "here is your finished portfolio." It is "here is the draft, here is the proof, and here are the 34 sentences you need to think about."
61:
62: That is more useful to me than a polished lie.
63:
64: ### The README workflow
65:
66: doxify readme runs the same compile, then renders a clean README from verified and attested material.
67:
68: The visible prose has no citation clutter. The provenance lives in HTML comments:
69:
70: markdown
71: <!-- doxify-evidence: E2 E4 E5 E6 E7 -->
72: <!-- doxify-attested: project description and positioning -->
73:
74:
75: Readers get a normal README. CI gets an audit trail.
76:
77: The README builder omits unresolved claims from visible prose, strips the "No README" evidence card, refuses to overwrite an existing README without --force, and keeps a backup when it does. The temporary compile session can be inspected or cleaned up.
78:
79: ### The Scout workflow
80:
81: doxify scout react 18 19 --repo /path/to/app fetches release notes in the upgrade range, extracts breaking changes with exact quotes, and searches the tracked code for the affected APIs.
82:
83: Scout is deliberately strict about quotes. If the model returns a "quote" that is not actually in the release notes, the change is dropped. If the model fails, a deterministic fallback extracts breaking-change lines instead. The report only shows changes that have a code match, because a breaking change that does not touch this repository is not this repository's problem.
84:
85: It writes scout.md, scout.json, release-notes.json, and manifest.json, and it never modifies the scanned repository.
86:
87: The repository includes a replay fixture for the React 18 to 19 upgrade, so extraction recall can be checked against a known list instead of judged by how convincing the output looks.
88:
89: ## Demo
90:
91: There is no hosted demo, and that is deliberate. Doxify's default workflow is local; the repository under analysis should not have to leave the machine.
92:
93: The motivating before/after is scholarship-mcp: 20 commits, nine MCP to
… [truncated 13972 chars]
[attached file: submission.md]
$ rg -n -i "sentry|serpapi|serp|github copilot|copilot|actions|entry" README.md PLAN.md src tests .github action.yml pyproject.toml docs 2>/dev/null | head -250
README.md:95:`gitpython`, `ollama`, `pydantic`, `rich`, `sentry-sdk`, `tomli`, and `typer`.
src/doxify/verifier.py:168: and any(word in lower for word in ("scholarship", "record", "entry", "entries"))
.github/workflows/ci.yml:17: uses: actions/checkout@v4
.github/workflows/ci.yml:39: uses: actions/upload-artifact@v4
.github/workflows/ci.yml:49: SENTRY_DSN: ""
.github/workflows/ci.yml:52: uses: actions/checkout@v4
src/doxify/monitoring.py:15:import sentry_sdk
src/doxify/monitoring.py:16:import sentry_sdk.logger as sentry_logger
src/doxify/monitoring.py:17:from sentry_sdk import crons
src/doxify/monitoring.py:18:from sentry_sdk.consts import OP, SPANDATA
src/doxify/monitoring.py:19:from sentry_sdk.integrations.dedupe import DedupeIntegration
src/doxify/mo
… [truncated 8167 chars]
$ git status --short && git log --oneline -8
M .github/workflows/ci.yml
M README.md
M src/doxify/cli.py
M src/doxify/monitoring.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/scout.py
A submission.md
?? action.yml
?? docs/
?? src/doxify/audit.py
?? src/doxify/log.py
?? src/doxify/redaction.py
?? src/doxify/selfcheck.py
?? tests/conftest.py
?? tests/test_audit.py
?? tests/test_logging.py
?? tests/test_monitoring.py
?? tests/test_redaction.py
?? tests/test_self_audit.py
?? tests/test_selfcheck.py
?? tests/test_verify_cli.py
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
d5c94b8 feat(scout): add evidence-backed upgrade impact analysis
f4b1d79 chore(demo): add precomputed nextjs-dashboard compile artifacts
95a5e02 build(ci): add Python 3.10 TOML fallback and package CI
11a198d docs(
… [truncated 271 chars]
$ find . -maxdepth 3 -type f | sort | sed -n '1,160p'
./action.yml
./benchmarks/scout-react-18-19.json
./dist/doxify-0.1.0-py3-none-any.whl
./dist/doxify-0.1.0.tar.gz
./dist/.gitignore
./docs/self-audit/evidence.json
./.git/COMMIT_EDITMSG
./.git/config
./.git/description
./.git/FETCH_HEAD
./.git/HEAD
./.git/hooks/applypatch-msg.sample
./.git/hooks/commit-msg.sample
./.git/hooks/fsmonitor-watchman.sample
./.git/hooks/post-update.sample
./.git/hooks/pre-applypatch.sample
./.git/hooks/pre-commit.sample
./.git/hooks/pre-merge-commit.sample
./.git/hooks/prepare-commit-msg.sample
./.git/hooks/pre-push.sample
./.git/hooks/pre-rebase.sample
./.git/hooks/pre-receive.sample
./.git/hooks/push-to-checkout.sample
./.git/hooks/sendemail-validate.sample
./.git/hooks/update.sample
./.github/workflows/ci.yml
./.gitignore
./.git/index
./.git/info/exclude
./.gi
… [truncated 2894 chars]
$ sed -n '1,260p' src/doxify/monitoring.py
from __future__ import annotations
import importlib.metadata
import logging
import os
import threading
import time
from collections.abc import Iterator
from contextlib import contextmanager
from contextvars import ContextVar
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import sentry_sdk
import sentry_sdk.logger as sentry_logger
from sentry_sdk import crons
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations.dedupe import DedupeIntegration
from sentry_sdk.tracing import Span, Transaction
from .redaction import (
SAFE_DATA_COLLECTION,
SAFE_LOG_FIELDS,
redact_text,
redact_value,
safe_url,
sanitize_breadcrumb,
sanitize_event,
sanitize_log,
sanitize_transaction,
)
DEFAULT_MONITOR_SLUG = "doxify-nigh
… [truncated 6692 chars]
$ sed -n '260,520p' src/doxify/monitoring.py
for key, value in fields.items()
if key in SAFE_LOG_FIELDS and isinstance(value, (str, int, float, bool))
}
attributes["doxify.event"] = redact_text(event)
try:
level_name = {"critical": "fatal"}.get(level, level)
logger = getattr(sentry_logger, level_name, sentry_logger.info)
logger(event, attributes=attributes)
except Exception:
_log.debug("Failed to send structured log to Sentry.", exc_info=True)
def set_measurement(name: str, value: float, unit: str = "") -> None:
if not _status.enabled:
return
transaction = _current_transaction.get()
if transaction is None:
return
try:
transaction.set_measurement(name, float(value), unit)
except (TypeError, ValueError):
return
def se
… [truncated 4860 chars]
$ sed -n '1,260p' src/doxify/redaction.py
from __future__ import annotations
import copy
import re
from pathlib import Path
from typing import Any
from urllib.parse import urlsplit
MAX_TEXT_LENGTH = 2048
FILTERED = "[Filtered]"
REDACTED_PATH = "<path>"
SAFE_DATA_COLLECTION: dict[str, Any] = {
"user_info": False,
"cookies": {"mode": "off"},
"http_headers": {"request": {"mode": "off"}},
"http_bodies": [],
"url_query_params": {"mode": "off"},
"graphql": {"document": False, "variables": False},
"gen_ai": {"inputs": False, "outputs": False},
"database_query_data": False,
"queues": False,
"stack_frame_variables": False,
"frame_context_lines": 0,
}
SAFE_TAGS = frozenset(
{
"command",
"environment",
"exit_code",
"model",
"outcome",
"provide
… [truncated 6387 chars]
$ sed -n '1,260p' src/doxify/selfcheck.py
from __future__ import annotations
import importlib.metadata
import json
import shutil
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from .ollama_client import OllamaClient
class SelfCheckError(RuntimeError):
pass
@dataclass(frozen=True)
class Check:
name: str
ok: bool
detail: str
@dataclass(frozen=True)
class SelfCheckResult:
checks: list[Check]
@property
def ok(self) -> bool:
return all(check.ok for check in self.checks)
def to_dict(self) -> dict[str, Any]:
return {
"ok": self.ok,
"checks": [
{"name": check.name, "ok": check.ok, "detail": check.detail}
for check in self.checks
],
}
def to_
… [truncated 1319 chars]
$ sed -n '1,220p' .github/workflows/ci.yml
name: CI
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
test:
name: Lint, test, and build
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Test
run: uv run pytest -q
- name: Build package
run: uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-f
… [truncated 951 chars]
$ sed -n '1,220p' src/doxify/log.py && sed -n '180,260p' src/doxify/cli.py
from __future__ import annotations
import json
import logging
import os
import sys
from datetime import datetime, timezone
from typing import Any
from .redaction import SAFE_LOG_FIELDS, redact_text, redact_value
LOGGER_NAME = "doxify"
_configured = False
logger = logging.getLogger(LOGGER_NAME)
class _RedactingFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
record.msg = redact_text(record.msg)
if record.args:
record.args = redact_value(record.args)
fields = getattr(record, "doxify_fields", None)
if isinstance(fields, dict):
record.doxify_fields = {
key: redact_value(value)
for key, value in fields.items()
if key in SAFE_LOG_FIELDS
}
… [truncated 5270 chars]
$ git diff -- src/doxify/monitoring.py src/doxify/cli.py src/doxify/ollama_client.py src/doxify/scout.py .github/workflows/ci.yml | sed -n '1,260p'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2704633..74e0a7d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,3 +41,35 @@ jobs:
name: dist
path: dist/
if-no-files-found: error
+
+ verify:
+ name: Verify evidence-backed claims
+ runs-on: ubuntu-latest
+ env:
+ SENTRY_DSN: ""
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Verify self-audit README
+ uses: ./
+ with:
+ artifact: README.md
+ evidence: docs/self-audit/evidence.json
+ fail-on: needs_evidence,rejected
+
+ - name: Verify unresolved case study (expected failure)
+ id: negative
+ continue-on-error: true
+ uses: ./
+
… [truncated 7891 chars]
$ rg -n "capture_log|command_transaction|model_span|http_span|cron_monitor|init_monitoring|selfcheck|run_self_check|redact" src/doxify tests | head -200
tests/test_monitoring.py:8:from doxify.redaction import SAFE_DATA_COLLECTION
tests/test_monitoring.py:65: assert monitoring.init_monitoring() is False
tests/test_monitoring.py:72: assert monitoring.init_monitoring() is False
tests/test_monitoring.py:85: assert monitoring.init_monitoring() is True
tests/test_monitoring.py:102: assert monitoring.init_monitoring() is False
tests/test_monitoring.py:114:def test_command_transaction_records_stage_and_measurements(
tests/test_monitoring.py:123: with monitoring.command_transaction("compile"):
tests/test_monitoring.py:136:def test_command_transaction_reports_unexpected_errors(
tests/test_monitoring.py:151: with pytest.raises(RuntimeError, match="boom"), monitoring.command_transaction("compile"):
tests/test_monitoring.py:159:def te
… [truncated 5879 chars]
Hacktoberfest Open-Source AI Challenge Week 3 Slow and Steady prize categories Sentry SerpApi GitHub Copilot Actions
Title: Hacktoberfest Open-Source AI Challenge: Week 3 - DEV Challenge - DEV Community
URL: https://dev.to/challenges/hacktoberfest-week3-2026-10-19
Published: 2026-10-19T07:00:00.000Z
Author: N/A
Highlights:
Challenge 4 of 5 in our Hacktoberfest DEV Challenges. One prompt, a new theme every week
...
Challenge Status: Live Ended
...
Week 3 of our five Hacktoberfest DEV Challenges starts today! Running through October 25, the Hacktoberfest Open-Source AI Challenge: Week 3 gives you a full week to build.
...
Miss ... le ... . Every challenge is a fresh start, so you're ... as
...
This week, we'll select one overall winner and a winner in each of our 20 partner categories. See all five challenges and current partner categories on the HF26 DEV Challenge Hub.
...
- ... : October ... 0
… [truncated 22764 chars]
$ sed -n '600,680p' src/doxify/cli.py && sed -n '100,190p' src/doxify/ollama_client.py && sed -n '430,560p' src/doxify/scout.py
table.add_row("Mode", report.mode)
table.add_row("Sections", str(len(report.sections)))
table.add_row(
"Attested sections",
str(sum(1 for section in report.sections if section.attested)),
)
if report.unmarked_sections:
table.add_row("Unmarked sections", str(len(report.unmarked_sections)))
table.add_row(
"Claims",
f"{summary.verified} verified, {summary.attested} attested, "
f"{summary.needs_evidence} needing evidence, {summary.rejected} rejected",
)
console.print(table)
failures = failing_claims(report.claims, fail_on)
for claim in failures[:10]:
console.print(f"[red]{claim.id} {claim.status.value}[/red] {claim.text}")
if claim.reason:
console.print(f" {claim.reason}")
… [truncated 8288 chars]
$ rg -n "class SessionManifest|class ScoutReport|class VerificationReport|class Claim\b|class EvidenceCard" src/doxify/models.py && sed -n '1,240p' src/doxify/models.py
48:class EvidenceCard(BaseModel):
96:class Claim(BaseModel):
148:class VerificationReport(BaseModel):
178:class SessionManifest(BaseModel):
290:class ScoutReport(BaseModel):
from __future__ import annotations
from enum import Enum
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
SCHEMA_VERSION = "1.0"
TOOL_NAME = "doxify"
class EvidenceKind(str, Enum):
METRIC = "metric"
STACK = "stack"
FILE = "file"
CODE = "code"
COMMIT = "commit"
TEST = "test"
DEPLOY = "deploy"
README = "readme"
ATTESTED = "attested"
class EvidenceStrength(str, Enum):
DETERMINISTIC = "deterministic"
CODE = "code"
COMMIT = "commit"
README = "readme"
ATTESTED = "attested"
class ClaimStatus(str, Enum):
VERIFIED = "verified"
ATTES
… [truncated 5056 chars]
$ uv run pytest --collect-only -q | tail -5
tests/test_verify_cli.py::test_verify_artifact_requires_evidence_without_session
tests/test_verify_cli.py::test_verify_check_does_not_rewrite_session
tests/test_verify_cli.py::test_verify_session_fails_on_unresolved_claims
73 tests collected in 0.12s
$ sed -n '1,120p' tests/test_selfcheck.py && sed -n '1,130p' tests/test_monitoring.py
from __future__ import annotations
import json
from typer.testing import CliRunner
import doxify.cli as cli
from doxify.selfcheck import Check, SelfCheckResult, run_self_check
class FakeClient:
def __init__(self, healthy: bool) -> None:
self.healthy = healthy
def health(self) -> tuple[bool, str]:
return self.healthy, "ok" if self.healthy else "model missing"
def test_self_check_passes_without_model() -> None:
result = run_self_check()
assert result.ok is True
assert {check.name for check in result.checks} == {
"python",
"git",
"package",
"tempdir",
}
def test_self_check_reports_model_failure() -> None:
result = run_self_check(client=FakeClient(healthy=False))
model = next(check for check in result.c
… [truncated 5277 chars]
$ sed -n '1,120p' action.yml
name: Doxify Verify
description: Verify Doxify sessions or evidence-marked READMEs and fail on unsupported claims.
author: Ekomjah
branding:
icon: shield
color: green
inputs:
session:
description: Compile session directory to verify.
required: false
default: ""
artifact:
description: README or case study file to audit.
required: false
default: ""
evidence:
description: Evidence JSON for artifact-only verification.
required: false
default: ""
fail-on:
description: Comma-separated claim statuses that fail the check.
required: false
default: needs_evidence,rejected
python-version:
description: Python version used to run Doxify.
required: false
default: "3.10"
working-directory:
description: Directory used to reso
… [truncated 1164 chars]
$ uv run doxify self-check --json
{
"checks": [
{
"detail": "3.10",
"name": "python",
"ok": true
},
{
"detail": "/usr/bin/git",
"name": "git",
"ok": true
},
{
"detail": "0.1.0",
"name": "package",
"ok": true
},
{
"detail": "read/write ok",
"name": "tempdir",
"ok": true
}
],
"ok": true
}
$ uv run doxify doctor
Doxify doctor
┏━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Check ┃ Status ┃ Detail ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ git │ ok │ /usr/bin/git │
│ Python │ ok │ local CLI runtime │
│ Monitoring │ disabled │ set SENTRY_DSN to opt in │
│ Provider │ Ollama │ http://127.0.0.1:11434 │
│ Model │ gemma3:1b │ local │
│ Endpoint │ ok │ Ollama is reachable and gemma3:1b is available. │
└────────────┴───────────┴────
… [truncated 47 chars]
file: ~/Desktop/repos/doxify/submission.md
content:
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
# Doxify: for sloths who finished the project and forgot the README
I had a working project, 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
The code was done. My brain was a puddle.
If you have ever pushed the last commit and then remembered that a repository needs a front door, you know the feeling. The work exists, but the explanation does not. Interviewers, open-source contributors, bots, and fellow sloths land on the repo, see a blank README, and bounce. Then, two years later, you co
… [truncated 25991 chars]
Wrote file successfully.
$ rg -n "DEFAULT.*MODEL|gemma" src/doxify/ollama_client.py | head -20 && wc -w submission.md
15:DEFAULT_MODEL = "gemma3:1b"
17:DEFAULT_CLOUD_MODEL = "gemma4:31b"
56: or (DEFAULT_CLOUD_MODEL if self.is_cloud else DEFAULT_MODEL)
3950 submission.md
$ uv run pytest -q && uv run ruff check .
........................................................................ [ 98%]
. [100%]
73 passed in 1.41s
All checks passed!
$ uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --fail-on needs_evidence,rejected --check
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
$ sed -n '1,140p' submission.md
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
# Doxify: for sloths who finished the project and forgot the README
I had a working project, 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
The code was done. My brain was a puddle.
If you have ever pushed the last commit and then remembered that a repository needs a front door, you know the feeling. The work exists, but the explanation does not. Interviewers, open-source contributors, bots, and fellow sloths land on the repo, see a blank README, and bounce. Then, two years later, you come back and the dependency tree has moved on without you. The build is deprecated, the upgrade notes
… [truncated 7196 chars]
$ sed -n '140,300p' submission.md
Precomputed artifacts:
- [Case study](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/case-study.md)
- [Claim ledger](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/claims.json)
- [Defense pack](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/defense.md)
A 90-second terminal recording of those commands is the intended video demo. The checked-in output is the same artifact.
## Code
The full project is at [github.com/Ekomjah/doxify](https://github.com/Ekomjah/doxify).
Start with:
- [`src/doxify/pipeline.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/pipeline.py) — the compile, README, verify, and export flows.
- [`src/doxify/evidence.py`](https://github.com/Ekomjah/doxify/blob/main/src/
… [truncated 6260 chars]
$ sed -n '300,460p' submission.md
return len(normalized) >= 12 and normalized in _normalize_quote(body)
```
A breaking change whose quote is not found in the release notes is dropped:
```python
if note is None:
warnings.append(
"Dropped a change because its quote was not found in the release notes."
)
continue
```
Then the deterministic code search looks for the affected APIs in tracked files, with word-boundary matching so `render` does not match `renderToString`:
```python
pattern = re.compile(
rf"(?<![A-Za-z0-9_$]){re.escape(alias)}(?![A-Za-z0-9_$])"
)
```
Scout is a candidate finder, not a type checker. It tells you where to look. It does not promise the migration is complete.
### SerpApi: release notes when GitHub is not enough
Scout prefers an explicit `--notes` file, then GitHub re
… [truncated 6331 chars]
$ sed -n '460,620p' submission.md
The open part is not just the weights. The verifier is open source too. You do not have to trust the model or trust me. You can read the checks, run them against the evidence, and see exactly why a sentence passed or failed. The output is portable Markdown and JSON, not a proprietary dashboard.
Open innovation is also what makes the optional services honest. Sentry tracing, SerpApi, and the GitHub Action are useful, but they are not required for the core loop. No DSN means no tracing. No SerpApi key means `--notes FILE` still works. The local path remains the default because privacy is the feature.
A sloth should not have to choose between a useful tool and a private repository. Local weights and an open verifier are what let the sloth have both.
## My Agent Session
I built Doxify in a
… [truncated 4097 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
Sentry is opt-in: leave `SENTRY_DSN` unset for local-only tracing, or set it to see command, stage, model, and HTTP spans.
new:
Sentry is opt-in: leave `SENTRY_DSN` unset to keep tracing off, or set it to see command, stage, model, and HTTP spans.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**Best Use of GitHub Copilot (with GitHub Actions).** The repository's agent workflow is the GitHub Actions gate: every push and pull request gets lint, 73 tests, a build, and the Doxify evidence chec
… [truncated 169 chars]
new:
**Best Use of GitHub Copilot.** The repository's agent workflow is the GitHub Actions gate: every push and pull request gets lint, 73 tests, a build, and the Doxify evidence check. A Copilot-authored contribution gets the same treatment as mine: the agent can propose, and CI verifies. The Actions half is live; the contribution path is the point.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
- [`src/doxify/monitoring.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/monitoring.py) — opt-in Sentry tracing.
- [`src/doxify/redaction.py`](https://github.com/Ekomjah/doxify/blob/main/
… [truncated 73 chars]
new:
- [`src/doxify/monitoring.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/monitoring.py) — opt-in Sentry tracing.
- [`src/doxify/redaction.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/redaction.py) — the redaction policy that keeps traces boring.
- [`src/doxify/selfcheck.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/selfcheck.py) — the local health checks b
… [truncated 30 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The default monitor is a nightly 3 a.m. UTC check-in with a 10-minute margin and a 15-minute maximum runtime. If the local model or the package install is broken, the sloth finds out before a user doe
… [truncated 2 chars]
new:
The monitor config describes a nightly 3 a.m. UTC check-in with a 10-minute margin and a 15-minute maximum runtime; point a scheduler at `self-check` when you want the sloth to be watched. If the local model or the package install is broken, the sloth finds out before a user does.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
Open-weight Gemma changes the economics and the trust model. `gemma3:1b` is about 806 MB and runs on an 8 GB CPU laptop. That means a student on a cheap machine can use the default workflow offline, w
… [truncated 188 chars]
new:
Open-weight Gemma changes the economics and the trust model. `gemma3:1b` is about 806 MB and runs on an 8 GB CPU laptop. That means a student on a cheap machine can use the default workflow offline, with no API key and no per-token bill. The repository evidence does not go to a model API in local mode. The model is small enough to run on the kind of laptop the target user already owns. And because
… [truncated 106 chars]
Edit applied successfully.
$ git diff --check && wc -w submission.md && git status --short
3994 submission.md
M .github/workflows/ci.yml
M README.md
M src/doxify/cli.py
M src/doxify/monitoring.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/scout.py
AM submission.md
?? action.yml
?? docs/
?? src/doxify/audit.py
?? src/doxify/log.py
?? src/doxify/redaction.py
?? src/doxify/selfcheck.py
?? tests/conftest.py
?? tests/test_audit.py
?? tests/test_logging.py
?? tests/test_monitoring.py
?? tests/test_redaction.py
?? tests/test_self_audit.py
?? tests/test_selfcheck.py
?? tests/test_verify_cli.py
$ rg -n "Best Use of|Sentry|SerpApi|Copilot|Entire|sloth|73 tests|13 verified|34 claims|5 verified|8 author" submission.md
3:# Doxify: for sloths who finished the project and forgot the README
9:If you have ever pushed the last commit and then remembered that a repository needs a front door, you know the feeling. The work exists, but the explanation does not. Interviewers, open-source contributors, bots, and fellow sloths land on the repo, see a blank README, and bounce. Then, two years later, you come back and the dependency tree has moved on without you. The build is deprecated, the upgrade notes are enormous, and you have no idea which lines actually matter.
11:Doxify is a tool for those two sloth moments.
29:The case study compiler was the core of this competition build. It is still the deepest surface: it produces the cited story, the claim ledger, and the defense pack. But the product is shaped for sloth
… [truncated 6780 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
`self-check --monitor-slug` turns the nightly health check into a Sentry cron monitor.
new:
`self-check --monitor-slug` turns a health check into a Sentry cron monitor.
Edit applied successfully.
$ git add submission.md && git diff --cached --name-only && git diff --cached --stat
submission.md
submission.md | 501 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 501 insertions(+)
$ git commit -m "docs(submission): add Hacktoberfest Week 3 post"
[main c1d4a57] docs(submission): add Hacktoberfest Week 3 post
1 file changed, 501 insertions(+)
create mode 100644 submission.md
$ git push origin main
To github.com:Ekomjah/doxify.git
2f2bfdd..c1d4a57 main -> main
Pushed c1d4a57 — reframed Doxify around the sloth workflow, added Sentry/SerpApi/Actions/Copilot/Entire details and code snippets, and updated Prize Categories.
Verified: 73 tests pass, Ruff passes, self-audit 5 verified/8 attested. Note: supporting Sentry/Actions files are still uncommitted, so several GitHub links in the post won’t resolve until those are pushed.
the article is great. but it could use some editing to make the read more enjoyable. here are some great submissions. don't focus on the idea. but on the way the writer was able to make his work presentable through his writing. notice how the user structured it to make the user enjoy the read, didn't use slop english likeyou did here, and he won. and i want to win too!!! Skip to content
DEV Community
Find related posts...
Powered by Algolia
Create Post
1
Stuck in the same dev.to topics? An open model fixed it. - DEV Community
35
Jump to Comments
11
Save
Boost
Cover image for Stuck in the same dev.to topics? An open model fixed it.
Thomas Bonnet
Thomas Bonnet Subscriber Community Curator
Posted on Oct 21
•
AI-assisted
30
11
7
8
7
Stuck in the same dev.to topics? An open model fixed it.
devchallenge
hf26challenge
ai
javascript
Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
What I Built
I built devto-digest, a small open-source script that reads my dev.to account and emails me a weekly digest. It saves me the daily scroll, and it makes me a little lazier in a good way.
Following 119 tags did not make my dev.to feed better. It made it louder.
Every day I scrolled past a hundred posts about things I already knew, and every week I missed the one article that would have changed how I work, because it lived in a tag I had never followed. Two sections, one rule.
For you: the best of the tags I follow.
Discover: popular posts outside my bubble.
Every post is a card: cover, title, two tags, a one-sentence blurb from an open-weight model with the key words in bold, and the author. That is it. No dashboard, no account, no server.
Demo
The digest email side by side in the light theme on the left and the dark theme on the right: a For you section with a card for an article about how Docker works, showing its cover image, #docker and #linux tag pills, a bold-keyword summary and the author
Try it on your own account. It prints the digest and writes a local preview, and sends nothing.
git clone https://github.com/thomasbnt/your-devto-digest.git
cd your-devto-digest
yarn install
cp .env.example .env
Add your dev.to API key to .env, then:
yarn dry-run
Code
GitHub logo thomasbnt / your-devto-digest
A weekly dev.to digest in your inbox: posts from your tags plus a Discover section outside your bubble, summarized by an open-weight model.
devto-digest banner: a sleeping sloth on a Git branch that leads to an email envelope
devto-digest
Digest workflow Docker image Node.js Yarn Open-weight AI License: MIT
A weekly email digest of dev.to articles, built from your own account:
For you: popular articles in the tags you follow.
Discover: popular articles outside your followed tags, to get out of your bubble.
Each article is a card with cover image, title, colored tag pills, author, and a one or two sentence blurb written by an open-weight LLM (Mistral Small by default) explaining why it is worth your time.
The script collects every day and sends once a week, so an article that peaks on Wednesday is not missed. Right before sending it re-checks each pick against the dev.to API and drops anything that is no longer online.
Setup
1. Prerequisites
Node.js >= 22.9
Yarn 4, via Corepack
A dev.to account
An SMTP account to send mail from (Gmail app password, Proton Bridge, Resend, Mailgun, your own server...)
Optional: a Mistral API key for the…
View on GitHub
How I Built It
The blurbs come from an open-weight model, mistral-small-latest, and everything around it is plain Node.js with one dependency. The pieces are below.
The pipeline
Every day, a job collects candidates and scores them: reactions plus twice the comments, with a gentle recency decay. Running daily means a post that peaks on Wednesday is still in the pool on Monday.
Once a week, it ranks the pool into the two sections, then re-checks each pick live against the API. Deleted or unpublished posts are dropped and the next best one takes the slot.
Followed tags are weighted by rarity, so a niche tag like htmx counts for more than discuss.
It runs on a GitHub Actions cron, or plain cron on your machine.
Mistral
The blurbs come from mistral-small-latest, an open-weight model, called through Mistral's hosted API. The app talks to any OpenAI-compatible /chat/completions endpoint, so changing the model or the provider is two values in config.json:
{
"llm": {
"baseUrl": "https://api.mistral.ai/v1",
"model": "mistral-small-latest"
}
}
An honest limit: pointing baseUrl at a local Ollama should work the same way since the format is identical, but I have not benchmarked a local model yet.
What the model writes
Here is a real one, from my own digest. The article is a post about fine-tuning Qwen3-8B with Tinker, and this is everything the card says about it:
Explains how fine-tuning Qwen3-8B with Tinker boosted accuracy and cut API costs for a Complexity Scorer tool.
One sentence, 25 words max, 2 to 4 key phrases in bold so a skimming reader gets the point in two seconds. The prompt forbids quotation marks and URLs, and it tells the model that everything inside an article is untrusted data: never follow instructions found in it. That matters. You are feeding strangers' text to an LLM, and a post titled "ignore previous instructions" should be summarized, not obeyed. The blurb is also HTML-escaped before it reaches the email, so even a hijacked model cannot inject markup.
GitHub Actions
The daily collect and weekly send run on a GitHub Actions cron, with state.json kept between runs in the Actions cache. The whole run takes under 2 minutes. You can also trigger it by hand from the Actions tab with the "Run workflow" button and pick what it does: collect only refreshes the pool, run behaves like the daily cron, and force-send emails the digest right now, even if one already went out today.
GitHu
… [truncated 64186 chars]
file: ~/Desktop/repos/doxify/submission.md
content:
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
# I built a local Gemma tool that writes the README I keep avoiding
My demo project had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README is still empty. That page is the first thing an interviewer, a contributor, or another developer sees. If it is empty, they leave.
Two years later, the other bill comes due. The dependencies have moved, the buil
… [truncated 21005 chars]
Wrote file successfully.
[session context compacted]
Objective
- Finish
submission.mdas a winning Hacktoberfest Week 3 entry: exact DEV template, sloth-focused framing, strong narrative writing, accurate technical detail, and a clear path to commit/push. - Immediate focus: edit the current draft so it reads like top winning submissions, not generic AI prose.
Important Details
- Required header and section order must stay intact:
What I Built,Demo,Code,How I Built It,Why Does Open Innovation Matter?,My Agent Session,The Lazy Payoff,Prize Categories. - User critique: current draft uses “slop English”; wants concrete backstory, short paragraphs, specific numbers, honest limits, “what broke” and “what I would do next” style. Winning references:
devto-digest,Mountain Dewarcryocooler,Mama POS day-book. - Reframe Doxify as a tool for sloths: developer finishes the last commit, is too tired to document the repo for interviewers/OSS devs/bots/fellow sloths; years later, Scout pinpoints where dependency drift breaks the code. Case study artifacts support this core objective and remain the competition’s deepest surface.
- README and Scout are core, not add-ons; case-study/claim-ledger/defense workflow is also core.
- Doxify definition: local-first evidence compiler; scans repo, builds evidence cards, Gemma drafts with
[E#], deterministic verifier checks claims. - Core line: “The model writes. The code checks.”
- Implemented commands:
doctor,scan,compile,readme,scout,verify,attest,export, plusself-check. - Local default
gemma3:1b; opt-in Ollama Cloudgemma4:31bvia--provider cloud/OLLAMA_API_KEY; cloud warns that evidence cards leave the machine. - Sentry is opt-in via
SENTRY_DSN; no hardcoded default.SAFE_DATA_COLLECTIONdisables user info, cookies, headers, bodies, query params, andgen_aiinputs/outputs.redaction.pystrips tokens/API keys/home paths;safe_urlstrips query strings.self-check --monitor-sluguses a Sentry cron monitor. - SerpApi is a Scout fallback after
--notes FILEand GitHub releases;SERPAPI_API_KEY; exact-quote validation still applies;--notespreserves offline use. - GitHub Actions:
testjob runs Ruff, 73 tests, build, upload;verifyjob runs the self-audit viauses: ./, then an unresolvednextjs-dashboardcase study expected to fail.SENTRY_DSN=""disables monitoring in CI. -
action.ymlinputs:session,artifact,evidence,fail-on,python-version,working-directory; runsdoxify verify ... --check. - Prize categories to present: Best Use of Gemma, Best Use of Sentry Agent Tracing, Best Use of SerpApi, Best Use of GitHub Copilot, Best Use of Entire. User wrote “entry”; interpreted as “Entire” from the challenge page.
- Do not overclaim: no actual Copilot session, no actual Entire/DevRelay embed, no hosted demo/video. The Copilot category leans on the GitHub Actions automation path; Entire leans on the session summary +
manifest.json/claims.json. - Verified numbers: 73 tests pass; Ruff/build pass; self-audit = 5 verified, 8 attested;
nextjs-dashboard= 13 verified, 34 needs evidence, 0 rejected. - Demo input
scholarship-mcpis input data only: 20 commits, 9 MCP tools, 3 models, 26 seeded records, no README. - No fabricated user study/time savings; article uses actual local runs on
nextjs-dashboardand Doxify’s own README. - Current latest rewrite title:
# I built a local Gemma tool that writes the README I keep avoiding. - Current latest
submission.mdrewrite is uncommitted; the previously pushed version is commitc1d4a57onmain. - Supporting Sentry/Actions/redaction/selfcheck work is still uncommitted, so several GitHub links in the article will 404 until those files are pushed.
- Uncommitted supporting work includes
action.yml,docs/,src/doxify/{audit,log,redaction,selfcheck}.py, new tests, and modifications to.github/workflows/ci.yml,src/doxify/{cli,monitoring,ollama_client,pipeline,scout}.py, andREADME.md.
Work State
Completed
-
submission.mdcreated and first version committed/pushed asc1d4a57(docs(submission): add Hacktoberfest Week 3 post). - Draft was reframed around sloths, README/Scout workflows, and the returning-developer dependency problem.
- Added Sentry, SerpApi, GitHub Actions, Copilot, and Entire prize-category discussion plus code snippets.
- Read/verified implementation details in
monitoring.py,redaction.py,selfcheck.py,scout.py,cli.py,action.yml, and.github/workflows/ci.yml. - Confirmed 73 tests, Ruff, build, self-audit 5 verified/8 attested, and
nextjs-dashboard13/34 claim results. - Latest style-focused rewrite has been written to
submission.md; it adds “What broke along the way” and “What I would do next” sections and tightens the prose.
Active
- Reviewing the latest uncommitted
submission.mdrewrite for flow, accuracy, and remaining “slop” phrasing. - Deciding whether to add a visual/demo asset; current article is text and artifact links only.
- Preparing to commit/push the updated
submission.mdand, if authorized, the supporting code so GitHub links and CI match the article.
Blocked
- No actual Copilot CLI/session or Entire/DevRelay integration; categories are represented through Actions automation and session/artifact summaries.
- No recorded demo video or screenshots; demo currently relies on CLI commands and checked-in artifacts.
- Supporting code changes remain uncommitted, so links to
action.yml,redaction.py,selfcheck.py, and updatedci.ymlare not live on GitHub. - No real user/time-savings data; “Lazy Payoff” must stay based on actual local runs.
- Latest rewrite has not yet been re-verified or committed.
Next Move
- Proofread the latest
submission.mdagainst the code and remove any remaining over-polished/AI-sounding phrasing; preserve the exact header and section order. - Run
uv run pytest -q,uv run ruff check ., and the README self-aud … [truncated 1276 chars]
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
$ git status --short && git log --oneline -5
M .github/workflows/ci.yml
M PLAN.md
M README.md
M src/doxify/cli.py
M src/doxify/monitoring.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/scout.py
M submission.md
?? .github/workflows/sentry-nightly.yml
?? action.yml
?? docs/
?? src/doxify/audit.py
?? src/doxify/log.py
?? src/doxify/redaction.py
?? src/doxify/selfcheck.py
?? tests/conftest.py
?? tests/test_audit.py
?? tests/test_logging.py
?? tests/test_monitoring.py
?? tests/test_redaction.py
?? tests/test_self_audit.py
?? tests/test_selfcheck.py
?? tests/test_verify_cli.py
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
d5c94b8 feat(scout): add evidence-backed upgrade impact analysis
f4b1d79 chore(demo): add precomputed nextj
… [truncated 94 chars]
$ git diff --stat && git diff -- submission.md | sed -n '1,240p'
.github/workflows/ci.yml | 32 +++
PLAN.md | 14 +-
README.md | 59 +++++-
src/doxify/cli.py | 165 ++++++++++++----
src/doxify/monitoring.py | 463 +++++++++++++++++++++++++++++++++++++++++---
src/doxify/ollama_client.py | 52 ++++-
src/doxify/pipeline.py | 11 +-
src/doxify/scout.py | 5 +-
submission.md | 306 +++++++++++------------------
9 files changed, 834 insertions(+), 273 deletions(-)
diff --git a/submission.md b/submission.md
index d3885cb..ad7d00e 100644
--- a/submission.md
+++ b/submission.md
@@ -1,77 +1,52 @@
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
-# Doxify: for sloths who
… [truncated 16111 chars]
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: *This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
2:
3: # I built a local Gemma tool that writes the README I keep avoiding
4:
5: My demo project had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: I had built the thing. I had not explained it.
8:
9: Every project ends the same way for me. The last commit lands, my brain shuts off, and the RE
… [truncated 23185 chars]
$ rg -n "SAFE_DATA_COLLECTION|gen_ai|database_query_data|before_send_log|cron_monitor|def http_span|def model_span|def command_transaction|monitor_config|schedule|checkin_margin|max_runtime" src/doxify/monitoring.py src/doxify/selfcheck.py src/doxify/ollama_client.py
src/doxify/monitoring.py:23: SAFE_DATA_COLLECTION,
src/doxify/monitoring.py:37: "schedule": {"type": "crontab", "value": "0 3 * * *"},
src/doxify/monitoring.py:38: "checkin_margin": 10,
src/doxify/monitoring.py:39: "max_runtime": 15,
src/doxify/monitoring.py:153: data_collection=SAFE_DATA_COLLECTION,
src/doxify/monitoring.py:160: before_send_log=sanitize_log,
src/doxify/monitoring.py:319:def command_transaction(command: str) -> Iterator[None]:
src/doxify/monitoring.py:385:def model_span(
src/doxify/monitoring.py:427:def http_span(label: str, url: str) -> Iterator[Span | None]:
src/doxify/monitoring.py:438:def cron_monitor(
src/doxify/monitoring.py:440: monitor_config: dict[str, Any] | None = None,
src/doxify/monitoring.py:451: monitor_con
… [truncated 38 chars]
$ rg -n "fetch_.*notes|SERPAPI|serpapi|--notes|release" src/doxify/scout.py src/doxify/cli.py | head -120
src/doxify/cli.py:383: "--notes",
src/doxify/cli.py:388: help="Offline release notes file. Skips GitHub and SerpApi.",
src/doxify/cli.py:404: max_releases: int = typer.Option(
src/doxify/cli.py:406: "--max-releases",
src/doxify/cli.py:407: help="Maximum number of GitHub releases to fetch.",
src/doxify/cli.py:412: help="Analyze every fetched release instead of the first target-line release.",
src/doxify/cli.py:429: """Fetch release notes, extract breaking changes, and search your repo."""
src/doxify/cli.py:452: max_releases=max_releases,
src/doxify/scout.py:41:GITHUB_API = "https://api.github.com/repos/{repo}/releases"
src/doxify/scout.py:42:SERPAPI_URL = "https://serpapi.com/search.json"
src/doxify/scout.py:55:RELEASE_NOTES_JSON = "re
… [truncated 5513 chars]
$ sed -n '1,220p' .github/workflows/ci.yml && printf '\n--- action.yml ---\n' && sed -n '1,200p' action.yml
name: CI
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
test:
name: Lint, test, and build
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Test
run: uv run pytest -q
- name: Build package
run: uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-f
… [truncated 2935 chars]
$ rg -n "extract_claims|verify_claims|retrieve_evidence|GeneratedSection|template_section|SECTION_SPECS" src/doxify/pipeline.py src/doxify/*.py | head -120
src/doxify/audit.py:8:from .claims import CITATION_RE, extract_claims
src/doxify/audit.py:12:from .verifier import summarize_claims, verify_claims
src/doxify/audit.py:109: claims = extract_claims(
src/doxify/audit.py:117: claims = extract_claims(
src/doxify/audit.py:162: verified = verify_claims(extracted, evidence)
src/doxify/pipeline.py:9:from .claims import extract_claims
src/doxify/pipeline.py:21:from .generator import GeneratedSection, generate_section, template_section
src/doxify/pipeline.py:43:from .retrieval import SECTION_SPECS, retrieve_evidence
src/doxify/pipeline.py:54:from .verifier import summarize_claims, verify_claims
src/doxify/pipeline.py:132: for index, spec in enumerate(SECTION_SPECS, start=1):
src/doxify/pipeline.py:133: emit("generat
… [truncated 2468 chars]
$ sed -n '120,190p' src/doxify/pipeline.py && printf '\n--- monitoring 1-90 ---\n' && sed -n '1,90p' src/doxify/monitoring.py && printf '\n--- monitoring 130-180 ---\n' && sed -n '130,180p' src/doxify/monitoring.py && printf '\n--- monitoring 310-470 ---\n' && sed -n '310,470p' src/doxify/monitoring.py
provider=client.provider,
model_host=client.host,
status="running",
)
write_manifest(output / MANIFEST, manifest, [])
write_evidence_json(output, scan.repo, scan.evidence)
emit("scan", f"Collected {len(scan.evidence)} evidence cards")
sections: list[CaseStudySection] = []
claims = []
warnings: list[str] = []
for index, spec in enumerate(SECTION_SPECS, start=1):
emit("generate", f"[{index}/{len(SECTION_SPECS)}] Drafting {spec.title}")
cards = retrieve_evidence(scan.evidence, spec, limit=3)
if spec.id in {"snapshot", "problem", "reflection"}:
generated = GeneratedSection(
body=template_section(spec, cards),
fallback_used=False,
)
else:
… [truncated 10272 chars]
$ sed -n '490,560p' src/doxify/scout.py && printf '\n--- serpapi ---\n' && sed -n '432,490p' src/doxify/scout.py && printf '\n--- quote rule ---\n' && sed -n '940,975p' src/doxify/scout.py
def fetch_release_notes(
package: str,
github_repo: str | None,
from_version: str,
to_version: str,
*,
notes_file: Path | None = None,
github_token: str | None = None,
serpapi_key: str | None = None,
max_releases: int = MAX_RELEASES,
) -> tuple[list[ReleaseNote], list[str]]:
warnings: list[str] = []
if notes_file is not None:
try:
body = notes_file.expanduser().resolve().read_text(encoding="utf-8")
except OSError as exc:
raise ScoutError(f"Could not read release notes file: {exc}") from exc
if not body.strip():
raise ScoutError(f"Release notes file is empty: {notes_file}")
return [
ReleaseNote(
version=f"{from_version}..{to_version}",
t
… [truncated 5185 chars]
$ sed -n '1,220p' src/doxify/redaction.py && printf '\n--- selfcheck ---\n' && sed -n '1,220p' src/doxify/selfcheck.py
from __future__ import annotations
import copy
import re
from pathlib import Path
from typing import Any
from urllib.parse import urlsplit
MAX_TEXT_LENGTH = 2048
FILTERED = "[Filtered]"
REDACTED_PATH = "<path>"
SAFE_DATA_COLLECTION: dict[str, Any] = {
"user_info": False,
"cookies": {"mode": "off"},
"http_headers": {"request": {"mode": "off"}},
"http_bodies": [],
"url_query_params": {"mode": "off"},
"graphql": {"document": False, "variables": False},
"gen_ai": {"inputs": False, "outputs": False},
"database_query_data": False,
"queues": False,
"stack_frame_variables": False,
"frame_context_lines": 0,
}
SAFE_TAGS = frozenset(
{
"command",
"environment",
"exit_code",
"model",
"outcome",
"provide
… [truncated 7139 chars]
$ sed -n '1,140p' src/doxify/generator.py && printf '\n--- claims/verifier snippets ---\n' && sed -n '1,120p' src/doxify/claims.py && sed -n '73,210p' src/doxify/verifier.py
from __future__ import annotations
import re
from dataclasses import dataclass
from .claims import CITATION_RE, GAP_RE
from .errors import GenerationError, ModelError
from .models import EvidenceCard, SectionSpec
from .ollama_client import OllamaClient
from .prompts import repair_messages, section_messages
@dataclass
class GeneratedSection:
body: str
fallback_used: bool
error: str | None = None
def generate_section(
client: OllamaClient,
spec: SectionSpec,
cards: list[EvidenceCard],
*,
repo_name: str,
role: str | None = None,
draft: str | None = None,
) -> GeneratedSection:
messages = section_messages(
spec,
cards,
repo_name=repo_name,
role=role,
draft=draft,
)
try:
body = _clean(clien
… [truncated 12425 chars]
$ rg -n "def .*quote|quote_in|_normalize_quote|word-boundary|re.compile" src/doxify/scout.py | head -60 && rg -n "deploy|production|tests pass|OAuth|award|first|only" src/doxify/verifier.py | head -80
142:_SEMVER_RE = re.compile(
147:_HEADING_RE = re.compile(r"(?m)^(#{1,6})[ \t]+(.+?)[ \t]*$")
148:_BREAKING_RE = re.compile(
152:_API_RE = re.compile(r"[^A-Za-z0-9_$@./:<>-]+")
618:def _normalize_quote(value: str) -> str:
625:def quote_in_notes(quote: str, body: str) -> bool:
626: normalized = _normalize_quote(quote)
627: return len(normalized) >= 12 and normalized in _normalize_quote(body)
669:_MULTIPART_API_RE = re.compile(
672:_SIMPLE_CODE_RE = re.compile(r"`([A-Za-z_$][A-Za-z0-9_$]*)`")
675:def extract_apis_from_quote(quote: str) -> list[str]:
695:def _declared_api_text(quote: str) -> str:
911: if version_matches and quote_in_notes(raw.quote, note.body):
914: if quote_in_notes(raw.quote, note.body):
968: key = (note.version, _normalize_quote(raw.quote
… [truncated 1282 chars]
$ jq '{summary: .summary, total: (.claims|length), statuses: (.claims|group_by(.status)|map({(.[0].status): length})|add)}' output/next-dashboard/case-study/claims.json && printf '\n--- self-audit ---\n' && uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --check --json 2>/dev/null | jq '{summary: .summary, total: (.claims|length), statuses: (.claims|group_by(.status)|map({(.[0].status): length})|add)}'
{
"summary": {
"verified": 13,
"attested": 0,
"needs_evidence": 34,
"rejected": 0,
"sections": 6,
"evidence_cards": 23
},
"total": 47,
"statuses": {
"needs_evidence": 34,
"verified": 13
}
}
--- self-audit ---
$ git diff --check && uv run pytest -q
........................................................................ [ 98%]
. [100%]
73 passed in 0.87s
$ uv run ruff check .
All checks passed!
$ uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --check; printf 'exit=%s\n' $?
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
exit=0
$ rg -n "def verify|--json|json_output|fail_on|--check" src/doxify/cli.py | head -80 && sed -n '540,720p' src/doxify/cli.py
17: parse_fail_on,
174: json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
176: False, "--check-model", help="Also check the configured Ollama model."
178: model: str | None = typer.Option(None, help="Model to check with --check-model."),
207: if json_output and result is not None:
213: if json_output:
464:def verify(
490: fail_on: str = typer.Option(
497: "--check",
507: fail_statuses = parse_fail_on(fail_on)
594: fail_on: set[ClaimStatus],
615: failures = failing_claims(report.claims, fail_on)
failed.extend(failing_claims(artifact_report.claims, fail_statuses))
if failed:
console.print(
f"[red]Verification failed:[/red] {len(failed)} claim(s) matched --fail-on."
… [truncated 3237 chars]
$ sed -n '1,260p' src/doxify/audit.py
from __future__ import annotations
import re
from collections.abc import Iterable
from dataclasses import dataclass, field
from pathlib import Path
from .claims import CITATION_RE, extract_claims
from .errors import SessionError
from .models import Claim, ClaimStatus, CompileSummary, EvidenceCard
from .readme import GENERATED_MARKER
from .verifier import summarize_claims, verify_claims
EVIDENCE_MARKER_RE = re.compile(
r"<!--\s*doxify-evidence:\s*([^>]*?)\s*-->",
re.IGNORECASE | re.DOTALL,
)
ATTESTED_MARKER_RE = re.compile(
r"<!--\s*doxify-attested:\s*([^>]*?)\s*-->",
re.IGNORECASE | re.DOTALL,
)
COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
VISIBLE_MARKER_RE = re.compile(r"\[(needs evidence|rejected|GAP)\]", re.IGNORECASE)
SECTION_RE = re.compile(r"^##\s+(.+?)\s*$")
… [truncated 7318 chars]
$ sed -n '1,120p' src/doxify/verifier.py && sed -n '210,300p' src/doxify/verifier.py
from __future__ import annotations
import re
from .models import Claim, ClaimStatus, CompileSummary, EvidenceCard
from .text import content_tokens
NUMBER_RE = re.compile(r"(?<![\w.])(\d[\d,]*(?:\.\d+)?)(?![\w])")
CODE_TOKEN_RE = re.compile(r"`([^`]+)`|\b([a-z]+[A-Z][A-Za-z0-9_]*)\b")
FORBIDDEN_PATTERNS: list[tuple[str, re.Pattern[str], str]] = [
(
"oauth",
re.compile(r"\boauth\b", re.IGNORECASE),
"The cited evidence does not contain an OAuth implementation.",
),
(
"tests pass",
re.compile(r"\b(?:all\s+)?tests?\s+(?:pass|passed|passing)\b", re.IGNORECASE),
"Doxify did not run the project's tests.",
),
(
"deployment",
re.compile(
r"\b(?:deployed|in production|production-ready|live at|shipped t
… [truncated 6023 chars]
$ uv run python - <<'PY'
import inspect, sentry_sdk
print(sentry_sdk.VERSION)
sig = inspect.signature(sentry_sdk.init)
print('data_collection' in sig.parameters)
print([p for p in sig.parameters if 'collect' in p or 'send' in p or 'body' in p or 'log' in p])
PY
2.73.0
False
[]
$ uv run python - <<'PY'
from pathlib import Path
p=Path('README.md')
text=p.read_text()
print(text.count('<!-- doxify-evidence:'), text.count('<!-- doxify-attested:'))
print([line for line in text.splitlines() if 'doxify-evidence' in line or 'doxify-attested' in line])
PY
1 8
['<!-- doxify-attested: project description and positioning -->', '<!-- doxify-attested: installation instructions -->', '<!-- doxify-attested: local setup and privacy behavior -->', '<!-- doxify-attested: cloud setup, privacy, and billing guidance -->', '<!-- doxify-attested: command reference -->', '<!-- doxify-evidence: E2 E4 E5 E6 E7 -->', '<!-- doxify-attested: scout usage instructions -->', 'fail; sections marked `doxify-attested` are author-owned and pass unless', '<!-- doxify-attested: CI usage instructions -->', '<!-- doxify-attested: contributor instructions -->']
$ uv run python - <<'PY'
import json
from collections import Counter
from pathlib import Path
for f in ['output/next-dashboard/case-study/claims.json']:
data=json.loads(Path(f).read_text())
print(f, data['summary'])
print(Counter(c['status'] for c in data['claims']))
PY
output/next-dashboard/case-study/claims.json {'verified': 13, 'attested': 0, 'needs_evidence': 34, 'rejected': 0, 'sections': 6, 'evidence_cards': 23}
Counter({'needs_evidence': 34, 'verified': 13})
$ git diff -- .github/workflows/ci.yml README.md PLAN.md src/doxify/cli.py src/doxify/monitoring.py src/doxify/ollama_client.py src/doxify/pipeline.py src/doxify/scout.py | sed -n '1,260p'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2704633..74e0a7d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,3 +41,35 @@ jobs:
name: dist
path: dist/
if-no-files-found: error
+
+ verify:
+ name: Verify evidence-backed claims
+ runs-on: ubuntu-latest
+ env:
+ SENTRY_DSN: ""
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Verify self-audit README
+ uses: ./
+ with:
+ artifact: README.md
+ evidence: docs/self-audit/evidence.json
+ fail-on: needs_evidence,rejected
+
+ - name: Verify unresolved case study (expected failure)
+ id: negative
+ continue-on-error: true
+ uses: ./
+
… [truncated 8941 chars]
$ uv run python - <<'PY'
import inspect, sentry_sdk
print(inspect.signature(sentry_sdk.init))
print(inspect.getsource(sentry_sdk.init)[:2000])
PY
(*args: 'Optional[str]', **kwargs: 'Any') -> 'ContextManager[Any]'
def _init(*args: "Optional[str]", **kwargs: "Any") -> "ContextManager[Any]":
"""Initializes the SDK and optionally integrations.
This takes the same arguments as the client constructor.
"""
client = sentry_sdk.Client(*args, **kwargs)
sentry_sdk.get_global_scope().set_client(client)
_check_python_deprecations()
rv = _InitGuard(client)
return rv
$ uv run python - <<'PY'
import sentry_sdk, inspect
from sentry_sdk.client import Client
src=inspect.getsource(Client)
for term in ['data_collection','gen_ai','http_bodies','url_query_params']:
print(term, term in src)
idx=src.find('data_collection')
print(src[idx-1200:idx+2200] if idx>=0 else 'not found')
PY
data_collection True
gen_ai True
http_bodies False
url_query_params False
transport.record_lost_event(
reason=reason,
data_category=data_category,
item=item,
quantity=quantity,
)
try:
_client_init_debug.set(self.options["debug"])
self.transport = make_transport(self.options)
self.monitor = None
if self.transport:
if self.options["enable_backpressure_handling"]:
self.monitor = Monitor(self.transport)
# Setup Spotlight before creating batchers so _capture_envelope can use it.
# setup_spotlight handles all config/env var resolution per the SDK spec.
from sentry_sdk.spot
… [truncated 2675 chars]
$ rg -n "data_collection|SAFE_DATA_COLLECTION|before_send_log|enable_logs" .venv/lib/python*/site-packages/sentry_sdk 2>/dev/null | head -80
(no output)
$ uv run python - <<'PY'
import inspect
import sentry_sdk.client as c
src=inspect.getsource(c)
for term in ['_map_from_send_default_pii','data_collection','url_query_params','http_bodies','graphql','queues']:
print(term, src.count(term))
idx=src.find('def _map_from_send_default_pii')
print(src[idx:idx+3500])
PY
_map_from_send_default_pii 2
data_collection 13
url_query_params 0
http_bodies 0
graphql 0
queues 0
$ uv run python - <<'PY'
import sentry_sdk, pathlib
p=pathlib.Path(sentry_sdk.__file__).parent
print(p)
for f in p.rglob('*.py'):
t=f.read_text(errors='ignore')
if 'data_collection' in t:
print(f)
PY
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk/client.py
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk/tracing_utils.py
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk/scope.py
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk/consts.py
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk/data_collection.py
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk/utils.py
~/Desktop/repos/doxify/.venv/lib/python3.10/site-packages/sentry_sdk/integrations/pymongo.py
~/Desktop/repos/doxify/.venv/lib/python3.1
… [truncated 5286 chars]
$ uv run python - <<'PY'
from sentry_sdk.client import _map_from_send_default_pii
import inspect
print(inspect.getsource(_map_from_send_default_pii))
PY
def _map_from_send_default_pii(
*,
send_default_pii: bool,
include_local_variables: bool,
include_source_context: bool,
) -> "DataCollection":
"""
Build a fully-resolved ``DataCollection`` dict that mirrors the data
``send_default_pii`` collects today. Used when ``data_collection`` is not
provided explicitly.
"""
terms = [] if send_default_pii else ["forwarded", "-ip", "remote-", "via", "-user"]
return {
"provided_by_user": False,
"user_info": send_default_pii,
"cookies": {"mode": "denylist", "terms": terms},
# Headers are collected in both PII modes today (sensitive ones filtered
# when PII is off), so this never maps to "off".
"http_headers": {
"request": {"mode": "denylist", "terms":
… [truncated 668 chars]
$ uv run python - <<'PY'
from sentry_sdk.consts import DEFAULT_OPTIONS
import pprint
pprint.pp(DEFAULT_OPTIONS.get('data_collection'))
PY
None
$ ollama list 2>&1 | head -20; printf '\n--- show ---\n'; ollama show gemma3:1b 2>&1 | head -40
Error: could not connect to ollama server, run 'ollama serve' to start it
--- show ---
Error: could not connect to ollama server, run 'ollama serve' to start it
$ rg -n "806|800|815|8 GB|8GB|model size|gemma3:1b" README.md docs src tests PLAN.md | head -60
README.md:28:Local mode uses Ollama and `gemma3:1b`. Nothing about the repository leaves
README.md:33:ollama pull gemma3:1b
README.md:62:| Model | `--model` | `DOXIFY_MODEL` | `gemma3:1b` local, `gemma4:31b` cloud |
PLAN.md:71:The current implementation is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, and a local `gemma3:1b` model through Ollama. Local inference remains the default: it reads a local repository, writes compile artifacts to a local session directory, and calls only the local Ollama server. Users without a local Ollama install can explicitly opt into Ollama Cloud with `--provider cloud` and `OLLAMA_API_KEY`; in that mode evidence cards are sent to Ollama Cloud's hosted model instead. The tool has no database and no account system of its own. Telemetry is off
… [truncated 2713 chars]
$ rg -n "self-check|verification summary|attested|<!-- doxify" README.md | head -50 && sed -n '1,220p' docs/self-audit/evidence.json | head -80
11:<!-- doxify-attested: project description and positioning -->
24:<!-- doxify-attested: installation instructions -->
38:<!-- doxify-attested: local setup and privacy behavior -->
68:<!-- doxify-attested: cloud setup, privacy, and billing guidance -->
90:<!-- doxify-attested: command reference -->
103:<!-- doxify-evidence: E2 E4 E5 E6 E7 -->
119:<!-- doxify-attested: scout usage instructions -->
124:fail; sections marked `doxify-attested` are author-owned and pass unless
125:`attested` is added to `fail-on`. Point it at an evidence-marked README:
144:<!-- doxify-attested: CI usage instructions -->
153:<!-- doxify-attested: contributor instructions -->
{
"schema_version": "1.0",
"repo": {
"path": ".",
"name": "doxify",
"head_sha": "2f2bfdd69d0bcb18747d97db147083e1ebc9fe8a"
… [truncated 1701 chars]
$ uv run doxify verify --artifact output/next-dashboard/case-study/case-study.md --evidence output/next-dashboard/case-study/evidence.json --fail-on needs_evidence,rejected --check >/tmp/doxify-negative.out 2>&1; code=$?; sed -n '1,30p' /tmp/doxify-negative.out; echo "exit=$code"
Artifact: case-study.md
┌───────────────────┬──────────────────────────────────────────────────────────┐
│ Mode │ case_study │
│ Sections │ 6 │
│ Attested sections │ 0 │
│ Claims │ 13 verified, 0 attested, 71 needing evidence, 0 rejected │
└───────────────────┴──────────────────────────────────────────────────────────┘
C1 needs_evidence “This project, nextjs-dashboard, is a Next.js application
focused on creating interactive dashboards. It utilizes React and JavaScript
for its core functionality. [E3]
Citation does not overlap with the claim's
… [truncated 1029 chars]
$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
$ rg -n "E2|E4|E5|E6|E7" docs/self-audit/evidence.json | head -20 && rg -n "self-check" src/doxify/cli.py | head -30
61: "id": "E2",
110: "id": "E4",
146: "id": "E5",
177: "id": "E6",
222: "id": "E7",
635: "id": "E20",
676: "id": "E21",
718: "id": "E22",
753: "id": "E23",
794: "id": "E24",
839: "id": "E25",
882: "id": "E26",
921: "id": "E27",
963: "id": "E28",
172:@app.command(name="self-check")
217: table = Table(title="Doxify self-check", show_header=True)
$ git rev-list --count HEAD && git log --oneline | wc -l && jq '.repo.commit_count, .repo.head_sha' docs/self-audit/evidence.json
9
9
8
"2f2bfdd69d0bcb18747d97db147083e1ebc9fe8a"
$ git show --stat --oneline c1d4a57 && git show c1d4a57:submission.md | wc -l && wc -l submission.md
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
submission.md | 501 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 501 insertions(+)
501
411 submission.md
$ git diff -- submission.md | sed -n '240,520p'
-
-The hard part was not getting a model to write. The hard part was making a 1B model trustworthy enough to be useful.
-
-A 1B model cannot hold a whole repository in context. Doxify does not ask it to. It gives the model a small set of precise evidence cards and a citation grammar. The model's job is to turn those cards into readable prose. The compiler's job is to decide whether the prose is allowed to survive.
+A 1B model cannot hold a whole repository. Doxify does not ask it to. It gives the model a small set of precise evidence cards and a citation grammar. The model turns those cards into prose. The compiler decides whether the prose is allowed to survive.
### The compile loop
-The pipeline does not let the model write the whole document and then hope. It builds the case study
… [truncated 16401 chars]
$ jq '[.claims[] | select(.status=="needs_evidence")][0:3] | map({id,text,status,reason,gap_question})' output/next-dashboard/case-study/claims.json
[
{
"id": "C1",
"text": "Okay, here’s the section text based on the provided evidence cards and goal:",
"status": "needs_evidence",
"reason": "No citation token was found.",
"gap_question": "What evidence supports this sentence: Okay, here’s the section text based on the provided evidence cards and goal:?"
},
{
"id": "C2",
"text": "“This project, nextjs-dashboard, is a Next.js application focused on creating interactive dashboards.",
"status": "needs_evidence",
"reason": "No citation token was found.",
"gap_question": "What evidence supports this sentence: “This project, nextjs-dashboard, is a Next.js application focused on creating interactive dashboards.?"
},
{
"id": "C4",
"text": "“The stack of this project consists primarily of
… [truncated 448 chars]
$ jq '[.claims[] | select(.status=="verified")][0:3] | map({id,text,status,reason})' output/next-dashboard/case-study/claims.json
[
{
"id": "C3",
"text": "It utilizes React and JavaScript for its core functionality. [E3] The repository contains a README at README.md, detailing the project's purpose and setup.”",
"status": "verified",
"reason": "Supported by cited repository evidence."
},
{
"id": "C11",
"text": "We’ve observed that maintaining consistent code structure and clear documentation is crucial for efficient development and collaboration. [E3] The README provides a foundational overview of the project, outlining its purpose and key features – a vital starting point for understanding the codebase. [E6] This commit timeline reveals a relatively recent development cycle, highlighting the need for streamlined workflows.",
"status": "verified",
"reason": "Supported by cited re
… [truncated 463 chars]
$ sed -n '1,120p' output/next-dashboard/case-study/case-study.md
# nextjs-dashboard: an evidence-backed case study
> Compiled locally with Doxify. Citations point to the evidence appendix.
> Unverified statements are marked [needs evidence]; author context is marked [attested].
## Snapshot
Okay, here’s the section text based on the provided evidence cards and goal: [needs evidence]
“This project, nextjs-dashboard, is a Next.js application focused on creating interactive dashboards. [needs evidence] It utilizes React and JavaScript for its core functionality. [E3] The repository contains a README at README.md, detailing the project's purpose and setup.”
“The stack of this project consists primarily of React, Node.js, and npm (or yarn). [E6] The top-level structure includes three commits between October 1st, 2026, and October 1st, 2026. These commi
… [truncated 12012 chars]
$ sed -n '160,240p' src/doxify/cli.py
"API key",
"set" if client.api_key else "missing",
"OLLAMA_API_KEY (checked on first call)",
)
healthy, detail = client.health()
table.add_row("Endpoint", "ok" if healthy else "missing", detail)
console.print(table)
if not git_path or not healthy:
raise typer.Exit(code=1)
@app.command(name="self-check")
def self_check(
json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
check_model: bool = typer.Option(
False, "--check-model", help="Also check the configured Ollama model."
),
model: str | None = typer.Option(None, help="Model to check with --check-model."),
host: str | None = typer.Option(None, help="Ollama endpoint URL."),
provider: str | None = typer.
… [truncated 2122 chars]
$ sed -n '1070,1120p' src/doxify/scout.py && sed -n '600,640p' src/doxify/scout.py
if relative.suffix.lower() not in SEARCHABLE_SUFFIXES:
continue
path = root / Path(relative)
try:
resolved = path.resolve(strict=True)
resolved.relative_to(root)
except (OSError, ValueError):
continue
if resolved.is_file() and not resolved.is_symlink():
candidates.append((resolved, relative))
return candidates
def search_repository(
root: Path,
changes: list[BreakingChange],
*,
tracked_files: list[str] | None = None,
max_matches: int = MAX_MATCHES,
) -> tuple[list[BreakingChange], int]:
patterns: dict[str, re.Pattern[str]] = {}
change_ids: dict[str, list[str]] = {}
for change in changes:
change.matches = []
for api in change.apis:
… [truncated 2364 chars]
$ rg -n "model_span|record_model_usage|http_span|begin_stage|end_stage" src/doxify/ollama_client.py src/doxify/scout.py src/doxify/cli.py | head -80 && sed -n '80,180p' src/doxify/ollama_client.py
src/doxify/cli.py:32: begin_stage,
src/doxify/cli.py:90: begin_stage(stage)
src/doxify/scout.py:29:from .monitoring import http_span
src/doxify/scout.py:351: with http_span(label, url) as span, urlopen(request, timeout=timeout) as response:
src/doxify/ollama_client.py:12:from .monitoring import model_span, record_model_usage
src/doxify/ollama_client.py:136: with model_span(
src/doxify/ollama_client.py:151: record_model_usage(
return False, str(exc)
if self.model in models:
return True, f"{self.label} is reachable and {self.model} is available."
if self.is_cloud:
return False, (
f"{self.model} is not available on Ollama Cloud. "
"Choose another model with --model or
… [truncated 3364 chars]
$ sed -n '80,110p' src/doxify/cli.py && rg -n "begin_stage\(|stage\(" src/doxify/cli.py src/doxify/pipeline.py | head -80
for argument in sys.argv[1:]:
if not argument.startswith("-"):
return argument
return "help"
def _stage_callback(progress, task):
def on_stage(stage: str, detail: str) -> None:
progress.update(task, description=detail)
log_event(logging.INFO, "stage.start", stage=stage)
begin_stage(stage)
return on_stage
def _read_draft(path: Path | None) -> str | None:
if path is None:
return None
try:
return path.read_text(encoding="utf-8")
except OSError as exc:
raise typer.BadParameter(f"Could not read draft file: {exc}") from exc
def _warn_cloud(client: OllamaClient) -> None:
if not client.is_cloud:
return
console.print(
"[yellow]Cloud mode sends repository evidence (file path
… [truncated 293 chars]
$ sed -n '1,60p' src/doxify/retrieval.py
from __future__ import annotations
from .models import EvidenceCard, SectionSpec
from .text import content_tokens
SECTION_SPECS: list[SectionSpec] = [
SectionSpec(
id="snapshot",
title="Snapshot",
goal="Summarize what the project is, its stack, size, and timeline.",
keywords=["identity", "stack", "languages", "commits", "files", "readme"],
),
SectionSpec(
id="problem",
title="The problem",
goal="Explain the problem this project addresses and why it matters.",
keywords=["problem", "purpose", "readme", "description", "context"],
),
SectionSpec(
id="approach",
title="The approach",
goal="Describe the main technical approach and major components.",
keywords=["architecture", "st
… [truncated 1199 chars]
$ sed -n '90,150p' src/doxify/monitoring.py
return configured
try:
version = importlib.metadata.version("doxify")
except importlib.metadata.PackageNotFoundError:
version = "0.1.0"
return f"doxify@{version}"
def _environment() -> str:
return os.environ.get("SENTRY_ENVIRONMENT", "development")
def _dsn() -> str | None:
dsn = os.environ.get("SENTRY_DSN", "").strip()
if not dsn:
return None
if not _env_bool("DOXIFY_SENTRY", default=True):
return None
return dsn
def _monitor_slug() -> str | None:
return os.environ.get("SENTRY_MONITOR_SLUG")
def init_monitoring() -> bool:
"""Initialize Sentry only when an explicit DSN is configured.
Initialization failures are non-fatal: the CLI continues without telemetry.
"""
global _initialized, _status
… [truncated 869 chars]
$ sed -n '1,200p' .github/workflows/sentry-nightly.yml && ls docs && find docs -maxdepth 2 -type f | sort
name: Sentry nightly health check
on:
schedule:
- cron: "0 3 * * *"
workflow_dispatch:
permissions:
contents: read
jobs:
self-check:
name: Doxify self-check and cron check-in
runs-on: ubuntu-latest
timeout-minutes: 5
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_ENVIRONMENT: production
SENTRY_TRACES_SAMPLE_RATE: "1.0"
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Run self-check
run: uv run doxify self-check --json --monitor-slug doxify-night
… [truncated 83 chars]
https://dev.to/challenges/hacktoberfest-week3-2026-10-19
Hacktoberfest Open-Source AI Challenge: Week 3 - DEV Challenge - DEV Community
[Skip to content](#main-content)
Navigation menu [](/)
Search [Powered by Algolia Search](https://www.algolia.com/developers/?utm_source=devto&utm_medium=referral)
[Log in](https://dev.to/enter?signup_subforem=1) [Create account](https://dev.to/enter?signup_subforem=1&state=new-user)
## DEV Community
Close
[Challenges](/challenges) → Hacktoberfest Open-Source AI Challenge: Week 3
]*\)//g' | sed -E 's/<[^>]+>//g' | tr -s '\n' | head -200
$200 USD
\+ Exclusive winner badge
### Best Use of Gemma
Use Gemma, Google's open-weight model, in building your project: run it locally, fine-tune it, or serve it through Google Cloud or another provider.
#### Prizes
$200 USD
\+ Exclusive winner badge
### Partner categories
$100 for each winner
### Best Use of Auth0 for AI Agents
Use Auth0 for AI Agents: sign users in to an agent, call tools on a user's behalf with Token Vault, ask a person to approve a risky action before the agent takes it, or limit what a RAG pipeline can retrieve with fine-grained authorization.
#### Prizes
$100 USD
\+ Exclusive winner badge
### Best Use of Backboard
Build with R-CLI, Backboard's open-source terminal coding agent, compare open-weight models through a single Backboard API key, or give an open-source p
… [truncated 2430 chars]
$ sed -n '350,430p' ~/.local/share/opencode/tool-output/tool_16fdb1ed6001A0VLHSt2O6NAVL | sed -E 's/!\[[^]]*\]\(data:[^)]*\)//g' | sed -E 's/<[^>]+>//g' | tr -s '\n' | head -160
#### Prizes
$100 USD
\+ Exclusive winner badge
### Best Use of Tiger Data
Store embeddings with pgvector, run hybrid keyword and vector search for an agent, or let an agent manage a database through Tiger MCP.
#### Prizes
$100 USD
\+ Exclusive winner badge
## How To Participate
Publish a post on DEV using the submission template above. Tell us what you built and what effort it saves, show us a demo, and explain why open innovation matters for what you built. If you can show us the lazy payoff, even better! The template adds the required tags: [#devchallenge](https://dev.to/t/devchallenge) and [#hf26challenge](https://dev.to/t/hf26challenge).
## Helpful Links & Resources
**Need help?** Come hang out and ask questions in the [MLH Discord](https://discord.com/invite/mlh), and read [Everythin
… [truncated 3534 chars]
$ sed -n '500,580p' ~/.local/share/opencode/tool-output/tool_16fdb1ed6001A0VLHSt2O6NAVL | sed -E 's/!\[[^]]*\]\(data:[^)]*\)//g' | sed -E 's/<[^>]+>//g' | tr -s '\n' | head -160
Anyone building with an Arduino UNO Q, whether you got one at an in-person Fest or have your own.
#### How do I claim partner credits?
Several partners are giving participants credits and promo codes, including Tinker, Render, Backboard, and a free .tech domain. Claim yours at [hacktoberfest.com/my/promos](https://hacktoberfest.com/my/promos/?utm_source=dev.to&utm_medium=challenge-page&utm_campaign=hacktoberfest-2026&utm_content=faq-claim-credits).
### Submission
#### Can I submit a pull request to an existing project?
No. **New projects, not pull requests:** each submission is a new project built during that challenge's window. Pull requests to existing projects don't count this year.
#### Can I work on an old project?
No, all submissions and their respective repositories must be started
… [truncated 3958 chars]
$ sed -n '1,240p' docs/observability.md
# Observability
Doxify is local-first. Sentry monitoring is **off by default** and only starts
when you provide an explicit `SENTRY_DSN`. Without a DSN, Doxify writes no
telemetry and does not contact Sentry.
When enabled, monitoring sends a deliberately narrow slice of operational data:
command errors, timings, redacted structured logs, and model/HTTP spans. It does
not send prompts, completions, repository contents, evidence text, environment
variables, user identity, cookies, request bodies, query strings, or local
absolute paths.
## Enable locally
```bash
export SENTRY_DSN="[redacted]"
export SENTRY_ENVIRONMENT="development"
export SENTRY_TRACES_SAMPLE_RATE="1.0"
doxify self-check --json
```
Disable it at any time with `DOXIFY_SENTRY=0` or an empty `SENTRY_DSN`
… [truncated 4031 chars]
$ sed -n '90,115p' README.md && jq '.evidence[] | select(.id=="E2" or .id=="E1" or .id=="E4" or .id=="E5" or .id=="E6" or .id=="E7") | {id,text,meta}' docs/self-audit/evidence.json
<!-- doxify-attested: command reference -->
## Project facts
Doxify is written in Python. The project declares runtime dependencies including
`gitpython`, `ollama`, `pydantic`, `rich`, `sentry-sdk`, `tomli`, and `typer`.
The repository is organized into top-level paths including `src`, `tests`,
`output`, and `.github`.
The repository contains test-related files at `tests/test_readme.py`. CI is
defined in `.github/workflows/ci.yml`.
<!-- doxify-evidence: E2 E4 E5 E6 E7 -->
## Scout upgrades
`doxify scout` fetches release notes between two versions, extracts breaking
changes with exact quotes, and searches your tracked code for affected APIs.
```bash
doxify scout react 18 19 --repo /path/to/app
```
Use `--notes FILE` for offline notes, `--deep` to analyze every fetched release,
and
… [truncated 1742 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
My demo project had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
new:
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The sloth part is the design brief: you should be able to leave the repo running and come back to a review queue.
new:
The sloth test is simple: start it, walk away, and come back to a review queue.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
Scout drops any breaking change whose quote is not actually in the release notes. If the model fails, a deterministic fallback extracts breaking-change lines instead. The report only shows changes tha
… [truncated 116 chars]
new:
Scout drops any breaking change whose quote is not actually in the release notes. If the model fails, a deterministic fallback extracts breaking-change lines instead. The report only shows changes that have a code match, because a breaking change that does not touch this repository is not this repository's problem.
The repository includes a replay fixture for the React 18 to 19 upgrade, so extrac
… [truncated 101 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The checked-in `nextjs-dashboard` run flagged 34 of its 47 claims. That is the product working. The 34 are the review queue.
new:
The checked-in `nextjs-dashboard` run left 34 of its 47 claims needing evidence. That is the product working. The 34 are the review queue.
That artifact is the raw run, not a cleaned-up sample. The `[needs evidence]` tags and the model's awkward openings are the point: the verifier flags them instead of hiding them.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
There is no hosted demo. Doxify is a CLI, and the repository under analysis does not have to leave the machine.
new:
There is no hosted demo. Doxify is a CLI, and the repository under analysis does not have to leave the machine. The walkthrough is the command block below; the checked-in artifacts are its output.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
- [`src/doxify/redaction.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/redaction.py) — the redaction policy.
- [`src/doxify/selfcheck.py`](https://github.com/Ekomjah/doxify/blob/main/src
… [truncated 307 chars]
new:
- [`src/doxify/redaction.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/redaction.py) — the redaction policy.
- [`src/doxify/selfcheck.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/selfcheck.py) — health checks for the Sentry cron monitor.
- [`docs/observability.md`](https://github.com/Ekomjah/doxify/blob/main/docs/observability.md) — Sentry setup, data policy, and dashb
… [truncated 412 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
```python
for spec in SECTION_SPECS:
cards = retrieve_evidence(scan.evidence, spec, limit=3)
if spec.id in {"snapshot", "problem", "reflection"}:
generated = GeneratedSection(body=tem
… [truncated 344 chars]
new:
```python
for spec in SECTION_SPECS:
cards = retrieve_evidence(scan.evidence, spec, limit=3)
if spec.id in {"snapshot", "problem", "reflection"}:
generated = GeneratedSection(
body=template_section(spec, cards),
fallback_used=False,
)
else:
generated = generate_section(
client,
spec,
cards,
… [truncated 291 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The monitor config describes a nightly 3 a.m. UTC check-in with a 10-minute margin and a 15-minute maximum runtime. Point a scheduler at it if you want the sloth to be watched.
new:
The monitor config describes a nightly 3 a.m. UTC check-in with a 10-minute margin and a 15-minute maximum runtime. `.github/workflows/sentry-nightly.yml` runs `doxify self-check` on that schedule, so a broken install shows up as a missed check-in.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**Sentry's defaults were too broad for private repositories.** Request bodies, headers, query strings, user info, and model inputs are now explicitly off. Tokens and paths are redacted. A monitoring f
… [truncated 61 chars]
new:
**Sentry is built for web apps, not private repositories.** Its defaults collect more than a local CLI should. Request bodies, headers, query strings, user info, and model inputs are now explicitly off. Tokens and paths are redacted. A monitoring feature that leaks the repository is worse than no monitoring.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
For `scholarship-mcp`, the input is 20 commits, nine tools, three models, and 26 records with no README. The first compile turns that raw history into a structured draft, a claim ledger, and an interv
… [truncated 59 chars]
new:
`scholarship-mcp` is the motivating input: 20 commits, nine tools, three models, and 26 records with no README. A compile turns that raw history into a structured draft, a claim ledger, and an interview defense pack. The job shifts from writing to verifying.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**Best Use of Sentry Agent Tracing.** Doxify records opt-in command transactions, pipeline stages, model spans, HTTP spans, token counts, and latency, while explicitly disabling prompt/output collecti
… [truncated 112 chars]
new:
**Best Use of Sentry Agent Tracing.** Doxify records opt-in command transactions, pipeline stages, model spans, HTTP spans, token counts, and latency, while explicitly disabling prompt/output collection and redacting secrets and paths. The trace tree above is the shape; `self-check --monitor-slug` and the nightly workflow turn a health check into a Sentry cron monitor.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**Best Use of GitHub Copilot.** The category lists automating a project with GitHub Actions. The whole contribution path is automated: every push and pull request runs lint, 73 tests, a build, and the
… [truncated 109 chars]
new:
**Best Use of GitHub Copilot.** The category includes automating your project with GitHub Actions. The whole contribution path is automated: every push and pull request runs lint, 73 tests, a build, and the Doxify evidence gate. A Copilot-authored PR gets the same check as mine. The agent can propose; CI verifies.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**Best Use of Entire.** The build session is summarized in `My Agent Session`, and every compile writes a `manifest.json` plus `claims.json` so the reasoning trail survives the chat. Doxify makes the
… [truncated 37 chars]
new:
**Best Use of Entire.** The category asks you to share the agent session behind the project. I did that in `My Agent Session`, and every compile writes a `manifest.json` plus `claims.json` so the reasoning trail survives the chat. Doxify makes the output of the agent's work auditable.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: *This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
2:
3: # I built a local Gemma tool that writes the README I keep avoiding
4:
5: The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: I had built the thing. I had not explained it.
8:
9: Every project ends the same way for me. The last commit lands, my brain shuts off, and the REA
… [truncated 24459 chars]
$ git diff -- submission.md | sed -n '1,80p'
diff --git a/submission.md b/submission.md
index d3885cb..28625ea 100644
--- a/submission.md
+++ b/submission.md
@@ -1,77 +1,52 @@
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
-# Doxify: for sloths who finished the project and forgot the README
+# I built a local Gemma tool that writes the README I keep avoiding
-I had a working project, 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
+The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
-The code was done. My brain was a puddle.
+I had built the thing. I had not explained it.
-If you have ever pushed the last com
… [truncated 5362 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. This saves the time of op
… [truncated 55 chars]
new:
Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It saves open-source developers, reviewers, bots, and fellow sloths from reverse-engineering the repo.
Edit applied successfully.
$ sed -n '240,360p' src/doxify/cli.py
model=model,
force=force,
)
if current is not None:
current.set_measurement("doxify.scan.cards", len(evidence))
print_scan_summary(ScanResult(repo=scanned_repo, evidence=evidence), output)
except DoxifyError as exc:
error(str(exc))
raise typer.Exit(code=1) from exc
@app.command()
def compile(
repo: Path = typer.Argument(..., exists=True, file_okay=False, resolve_path=True),
out: Path | None = typer.Option(None, "--out", "-o", help="Output directory."),
draft: Path | None = typer.Option(None, "--draft", help="Optional Markdown notes."),
role: str | None = typer.Option(None, "--role", help="Target role for tone."),
model: str | None = typer.Option(
None,
h
… [truncated 3299 chars]
$ rg -n "omitted|doxify-evidence|VERIFIED|ATTESTED" src/doxify/readme.py | head -60 && sed -n '1,220p' src/doxify/readme.py
34: omitted_claims: list[Claim]
69: omitted = [
72: if claim.status not in {ClaimStatus.VERIFIED, ClaimStatus.ATTESTED}
74: if omitted:
78: f"<!-- doxify-unresolved: {len(omitted)} claim(s) need evidence; "
324: if claim.status == ClaimStatus.ATTESTED and citation_free(claim.text)
344: lines.extend(["", f"<!-- doxify-evidence: {ids} -->"])
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from .errors import SessionError
from .models import (
Claim,
ClaimStatus,
CompileResult,
EvidenceCard,
EvidenceKind,
RepoSummary,
)
from .text import compact_whitespace
GENERATED_MARKER = "<!-- doxify-generated -->"
CITATION_RE = re.compile(r"\s*\[E\d+\]
… [truncated 5967 chars]
$ sed -n '300,355p' src/doxify/readme.py
)
def _limitations(lines: list[str], repo: RepoSummary) -> None:
body: list[str] = []
if not repo.deploy_files:
body.append(
"Doxify did not detect deployment configuration in the tracked files."
)
if repo.test_files:
body.append(
"Test files are listed above; this README does not claim that the tests pass."
)
body.append(
"Add installation, environment, deployment, and licensing details that are not "
"visible in the repository."
)
_section(lines, "Limitations and next steps", body, [])
def _project_notes(lines: list[str], claims: list[Claim]) -> None:
attested = [
claim
for claim in claims
if claim.status == ClaimStatus.ATTESTED and citation_free(claim.text)
… [truncated 909 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It saves open-source deve
… [truncated 77 chars]
new:
Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It saves open-source developers, reviewers, and fellow sloths from reverse-engineering the repo. The bots get a better README too.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
Doxify is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, Ollama, and the Sentry SDK. The default model is `gemma3:1b` running locally. Users who cannot install Ollama can opt into Olla
… [truncated 144 chars]
new:
Doxify is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, Ollama, and the Sentry SDK. The default model is `gemma3:1b` running locally. Users who cannot or do not want to install Ollama can opt into Ollama Cloud with `--provider cloud` and `OLLAMA_API_KEY`; cloud mode defaults to `gemma4:31b` and warns that evidence cards will leave the machine.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
SAFE_DATA_COLLECTION = {
"user_info": False,
"cookies": {"mode": "off"},
"http_headers": {"request": {"mode": "off"}},
"gen_ai": {"inputs": False, "outputs": False},
"database_quer
… [truncated 53 chars]
new:
SAFE_DATA_COLLECTION = {
"user_info": False,
"cookies": {"mode": "off"},
"http_headers": {"request": {"mode": "off"}},
"http_bodies": [],
"url_query_params": {"mode": "off"},
"graphql": {"document": False, "variables": False},
"gen_ai": {"inputs": False, "outputs": False},
"database_query_data": False,
"queues": False,
"stack_frame_variables": False,
"fr
… [truncated 24 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The README run gave me a clean README with hidden provenance that CI can check. The Scout run turns a stack of release notes into a list of breaking changes and file/line matches.
new:
The README run gave me a clean README with hidden provenance that CI can check. Scout turns a stack of release notes into a list of breaking changes and file/line matches.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The design target is a sub-10-minute local compile on an 8 GB CPU laptop. The model is small, the prompts are bounded, and the expensive trust work is deterministic. I would rather ship a 1B model tha
… [truncated 82 chars]
new:
The design target is a sub-10-minute local compile on an 8 GB CPU laptop. The model is small, the prompts are bounded, and the expensive trust work is deterministic. I would rather ship a 1B model plus a verifier that says "I cannot prove that" than a larger model that says everything confidently.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
`#hf26challenge` `#hacktoberfest` `#opensource` `#ai` `#gemma` `#ollama` `#sentry` `#serpapi` `#githubactions` `#copilot` `#entire` `#portfolio` `#career` `#sloth`
new:
`#devchallenge` `#hf26challenge` `#hacktoberfest` `#opensource` `#ai` `#gemma` `#ollama` `#sentry` `#serpapi` `#githubactions` `#copilot` `#entire` `#portfolio` `#career` `#sloth`
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It saves open-source deve
… [truncated 105 chars]
new:
Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It gives open-source developers, reviewers, bots, and fellow sloths their time back.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The repository includes a replay fixture for the React 18 to 19 upgrade, so extraction recall can be checked against a known list instead of judged by how convincing the output looks.
new:
The repository includes a [replay fixture](https://github.com/Ekomjah/doxify/blob/main/benchmarks/scout-react-18-19.json) for the React 18 to 19 upgrade, so extraction recall can be checked against a known list instead of judged by how convincing the output looks.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**Sentry is built for web apps, not private repositories.** Its defaults collect more than a local CLI should. Request bodies, headers, query strings, user info, and model inputs are now explicitly of
… [truncated 109 chars]
new:
**Sentry's defaults are built for web apps, not private repositories.** They collect more than a local CLI should. Request bodies, headers, query strings, user info, and model inputs are now explicitly off. Tokens and paths are redacted. A monitoring feature that leaks the repository is worse than no monitoring.
Edit applied successfully.
$ rg -n "!|actually|really|very|just|simply|seamless|leverage|unlock|game-changer|revolution|effortless|delve|landscape|testament" submission.md
9:Every project ends the same way for me. The last commit lands, my brain shuts off, and the README is still empty. That page is the first thing an interviewer, a contributor, or another developer sees. If it is empty, they leave.
15:Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It gives open-source developers, reviewers, bots, and fellow sloths their time back.
21:Doxify turns a finished repository into the things a tired developer actually needs:
27:| Scout | Which upgrade actually touches my code? | `scout.md`, `scout.json`, release notes, code matches |
32:Doxify is for the developer who just pushed the last commit and cannot face a blank page. It is for the
… [truncated 3542 chars]
$ wc -w submission.md && rg -n '^#{1,3} ' submission.md
3313 submission.md
3:# I built a local Gemma tool that writes the README I keep avoiding
19:## What I Built
38:### README
61:### Scout
71:### Case study
83:## Demo
117:## Code
137:## How I Built It
150:### The compile loop
183:### The verifier
205:### README provenance
226:### Scout's exact-quote rule
250:### SerpApi
267:### Sentry Agent Tracing
339:### GitHub Actions
367:### What broke along the way
377:### What I would do next
383:## Why Does Open Innovation Matter?
398:## My Agent Session
406:## The Lazy Payoff
420:## Prize Categories
$ git diff --check && uv run pytest -q && uv run ruff check .
........................................................................ [ 98%]
. [100%]
73 passed in 1.21s
All checks passed!
$ uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --check && uv build
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
$ uv run python - <<'PY'
from pathlib import Path
text=Path('submission.md').read_text()
required=['## What I Built','## Demo','## Code','## How I Built It','## Why Does Open Innovation Matter?','## My Agent Session','## The Lazy Payoff','## Prize Categories','#devchallenge','#hf26challenge']
for item in required:
print(('OK ' if item in text else 'MISSING ')+item)
print('fences', text.count('```'))
print('words', len(text.split()))
PY
OK ## What I Built
OK ## Demo
OK ## Code
OK ## How I Built It
OK ## Why Does Open Innovation Matter?
OK ## My Agent Session
OK ## The Lazy Payoff
OK ## Prize Categories
OK #devchallenge
OK #hf26challenge
fences 40
words 3317
$ git diff --stat && git status --short
.github/workflows/ci.yml | 32 +++
.obsidian/workspace.json | 12 +-
PLAN.md | 14 +-
README.md | 59 +++++-
src/doxify/cli.py | 165 ++++++++++++----
src/doxify/monitoring.py | 463 +++++++++++++++++++++++++++++++++++++++++---
src/doxify/ollama_client.py | 52 ++++-
src/doxify/pipeline.py | 11 +-
src/doxify/scout.py | 5 +-
submission.md | 301 +++++++++++-----------------
10 files changed, 848 insertions(+), 266 deletions(-)
M .github/workflows/ci.yml
M .obsidian/workspace.json
M PLAN.md
M README.md
M src/doxify/cli.py
M src/doxify/monitoring.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
M src/doxify/scout.py
M submission.md
?? .github/workflows/sentry-nightly.yml
?? action
… [truncated 319 chars]
$ rg -n --hidden -g '!.git' -g '!dist' -g '!*.lock' -e 'SENTRY_DSN\s*=\s*["'"'][^"'"']+' -e 'sk-[A-Za-z0-9_-]{16,}' -e 'gh[pousr]_[A-Za-z0-9]{20,}' -e 'AKIA[0-9A-Z]{16}' -e 'api[_-]?key\s*=\s*["'"'][^"'"']+' . | head -50
zsh:1: unmatched "
$ git diff -- .obsidian/workspace.json | sed -n '1,120p'
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index ca57edf..39f63bd 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
- "file": "PLAN.md",
+ "file": "submission.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
- "title": "PLAN"
+ "title": "submission"
}
}
]
@@ -55,7 +55,7 @@
"state": {
"type": "search",
"state": {
- "query": "happens",
+ "query": "reverse",
"matchingCase": true,
… [truncated 548 chars]
$ git diff -- src/doxify/ollama_client.py src/doxify/pipeline.py src/doxify/scout.py | sed -n '1,260p'
diff --git a/src/doxify/ollama_client.py b/src/doxify/ollama_client.py
index 9476c66..5b09163 100644
--- a/src/doxify/ollama_client.py
+++ b/src/doxify/ollama_client.py
@@ -1,11 +1,15 @@
from __future__ import annotations
+import logging
import os
+import time
from typing import Any, Literal
from ollama import Client, RequestError, ResponseError
from .errors import ModelError
+from .log import log_event
+from .monitoring import model_span, record_model_usage
DEFAULT_HOST = "http://127.0.0.1:11434"
DEFAULT_MODEL = "gemma3:1b"
@@ -127,16 +131,62 @@ class OllamaClient:
}
if not self.is_cloud:
payload["keep_alive"] = "10m"
+ started = time.perf_counter()
try:
- response = self._client.chat(**payload)
+ with mod
… [truncated 4542 chars]
$ rg -n --hidden -g '!.git' -g '!dist' -g '!uv.lock' 'sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|SENTRY_DSN="[redacted]"[A-Za-z0-9]' . || true
./tests/test_redaction.py:16: "Authorization: Bearer [redacted-token] "
./docs/observability.md:16:export SENTRY_DSN="[redacted]"
./docs/observability.md:110:export SENTRY_DSN="[redacted]"
$ git diff -- .github/workflows/ci.yml README.md src/doxify/cli.py | sed -n '1,320p'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2704633..74e0a7d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,3 +41,35 @@ jobs:
name: dist
path: dist/
if-no-files-found: error
+
+ verify:
+ name: Verify evidence-backed claims
+ runs-on: ubuntu-latest
+ env:
+ SENTRY_DSN: ""
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Verify self-audit README
+ uses: ./
+ with:
+ artifact: README.md
+ evidence: docs/self-audit/evidence.json
+ fail-on: needs_evidence,rejected
+
+ - name: Verify unresolved case study (expected failure)
+ id: negative
+ continue-on-error: true
+ uses: ./
+
… [truncated 9086 chars]
$ git diff --name-only && git ls-files --others --exclude-standard | sort
.github/workflows/ci.yml
.obsidian/workspace.json
PLAN.md
README.md
src/doxify/cli.py
src/doxify/monitoring.py
src/doxify/ollama_client.py
src/doxify/pipeline.py
src/doxify/scout.py
submission.md
action.yml
docs/observability.md
docs/self-audit/evidence.json
.github/workflows/sentry-nightly.yml
src/doxify/audit.py
src/doxify/log.py
src/doxify/redaction.py
src/doxify/selfcheck.py
tests/conftest.py
tests/test_audit.py
tests/test_logging.py
tests/test_monitoring.py
tests/test_redaction.py
tests/test_self_audit.py
tests/test_selfcheck.py
tests/test_verify_cli.py
$ uv run doxify self-check --json; echo "exit=$?"
{"checks": [{"detail": "3.10", "name": "python", "ok": true}, {"detail": "/usr/bin/git", "name": "git", "ok": true}, {"detail": "0.1.0", "name": "package", "ok": true}, {"detail": "read/write ok", "name": "tempdir", "ok": true}], "ok": true}
exit=0
$ uv run doxify verify --artifact output/next-dashboard/case-study/case-study.md --evidence output/next-dashboard/case-study/evidence.json --fail-on needs_evidence,rejected --check >/tmp/doxify-negative.out 2>&1; code=$?; tail -5 /tmp/doxify-negative.out; echo "exit=$code"
C11 needs_evidence This commit timeline reveals a relatively recent development
cycle, highlighting the need for streamlined workflows.
No citation token was found.
... and 61 more
Verification failed: 71 claim(s) matched --fail-on.
exit=1
$ uv run python - <<'PY'
from pathlib import Path
text=Path('submission.md').read_text()
assert text.count('```') % 2 == 0
print(text.count('```')//2, 'code blocks')
PY
20 code blocks
$ git add .github/workflows/ci.yml .github/workflows/sentry-nightly.yml PLAN.md README.md action.yml docs src/doxify/audit.py src/doxify/cli.py src/doxify/log.py src/doxify/monitoring.py src/doxify/ollama_client.py src/doxify/pipeline.py src/doxify/redaction.py src/doxify/scout.py src/doxify/selfcheck.py tests/conftest.py tests/test_audit.py tests/test_logging.py tests/test_monitoring.py tests/test_redaction.py tests/test_self_audit.py tests/test_selfcheck.py tests/test_verify_cli.py && git status --short
M .github/workflows/ci.yml
A .github/workflows/sentry-nightly.yml
M .obsidian/workspace.json
M PLAN.md
M README.md
A action.yml
A docs/observability.md
A docs/self-audit/evidence.json
A src/doxify/audit.py
M src/doxify/cli.py
A src/doxify/log.py
M src/doxify/monitoring.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
A src/doxify/redaction.py
M src/doxify/scout.py
A src/doxify/selfcheck.py
M submission.md
A tests/conftest.py
A tests/test_audit.py
A tests/test_logging.py
A tests/test_monitoring.py
A tests/test_redaction.py
A tests/test_self_audit.py
A tests/test_selfcheck.py
A tests/test_verify_cli.py
alright. now, remember that one thing that brought me back from particiapating in the hacktoberfest week 3 when i saw it was due to poor cpu. add this to make my story more confvincing in the @submission.md file
Called the Read tool with the following input: {"filePath":"~/Desktop/repos/doxify/submission.md"}
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
2:
3: # I built a local Gemma tool that writes the README I keep avoiding
4:
5: The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: I had built the thing. I had not explained it.
8:
9: Every project ends the same way for me. The last commit lands, my brain shuts off, and the README is still empty. That page is the first thing an interviewer, a contributor, or another developer sees. If it is empty, they leave.
10:
11: Two years later, the other bill comes due. The dependencies have moved, the build is deprecated, and the release notes are a wall. I do not need a generic summary. I need to know which lines in my repo are affected.
12:
13: I built Doxify for both moments.
14:
15: Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It gives open-source developers, reviewers, bots, and fellow sloths their time back.
16:
17: The model writes. The code checks.
18:
19: ## What I Built
20:
21: Doxify turns a finished repository into the things a tired developer actually needs:
22:
23: | Surface | Question it answers | What it writes |
24: |---|---|---|
25: | Case study compiler | What did I build, and can I defend it? | case-study.md, claims.json, defense.md |
26: | README compiler | What does this repo do, and how do I run it? | An evidence-marked README.md |
27: | Scout | Which upgrade actually touches my code? | scout.md, scout.json, release notes, code matches |
28: | CI gate | Can I trust the docs in this PR? | A pass or fail from the same verifier |
29:
30: The case study came first. It was the core of this build and it is still the deepest part: six sections, a claim ledger, and an interview defense pack. The README and Scout reuse the same evidence engine, so they are not side projects. The case study is the story. The README is what visitors read. Scout is what future-you runs.
31:
32: Doxify is for the developer who just pushed the last commit and cannot face a blank page. It is for the developer whose best work is private or under NDA. It is for the maintainer who comes back to a project after two years of dependency drift. It is also for the open-source maintainer whose README is the first thing contributors and bots read.
33:
34: It is not a portfolio site generator. It is a review tool. The useful output is not just a draft. It is the list of claims that did not survive verification.
35:
36: The sloth test is simple: start it, walk away, and come back to a review queue.
37:
38: ### README
39:
40: doxify readme runs the same compile, then renders a README from verified and attested material.
41:
42: Readers get clean Markdown. CI gets provenance:
43:
44: markdown
45: <!-- doxify-evidence: E2 E4 E5 E6 E7 -->
46: <!-- doxify-attested: project description and positioning -->
47:
48:
49: Unresolved claims are left out of the visible page. The README builder also refuses to overwrite an existing README without --force, and it keeps a backup when it does.
50:
51: python
52: if target.exists() and not force:
53: raise SessionError(
54: f"README target already exists: {target}. "
55: "Pass --force to overwrite it, or use --output to write elsewhere."
56: )
57:
58:
59: The tool does not eat your existing README while you are looking away.
60:
61: ### Scout
62:
63: doxify scout react 18 19 --repo /path/to/app fetches release notes in the upgrade range, extracts breaking changes with exact quotes, and searches the tracked code for the affected APIs.
64:
65: The point is not "here is everything that changed in React 19." The point is "here is the short list of changes that touch this repository, with file and line matches."
66:
67: Scout drops any breaking change whose quote is not actually in the release notes. If the model fails, a deterministic fallback extracts breaking-change lines instead. The report only shows changes that have a code match, because a breaking change that does not touch this repository is not this repository's problem.
68:
69: The repository includes a replay fixture for the React 18 to 19 upgrade, so extraction recall can be checked against a known list instead of judged by how convincing the output looks.
70:
71: ### Case study
72:
73: doxify compile produces six sections: Snapshot, The problem, The approach, The hard part, What shipped, and What I would change.
74:
75: For each narrative section, Doxify retrieves up to three evidence cards and gives them to Gemma. The model writes with [E#] citations. The verifier checks the citations, numbers, identifiers, and a list of claims that code cannot prove.
76:
77: Unsupported sentences are not deleted silently. They are marked [needs evidence], paired with a gap question, and included in the claim ledger. Author context can be added with doxify attest and is labeled as an attestation, not repository proof.
78:
79: The checked-in nextjs-dashboard run left 34 of its 47 claims needing evidence. That is the product working. The 34 are the review queue.
80:
81: That artifact is the raw run, not a cleaned-up sample. The [needs evidence] tags and the model's awkward openings are the point: the verifier flags them instead of hiding them.
82:
83: ## Demo
84:
85: There is no hosted demo. Doxify is a CLI, and the repository under analysis does not have to leave the machine. The walkthrough is the command block below; the checked-in artifacts are its output.
86:
87: The motivating demo is scholarship-mcp: 20 commits, nine MCP tools
… [truncated 19383 chars]
[attached file: submission.md]
file: ~/Desktop/repos/doxify/submission.md
old:
I built Doxify for both moments.
Doxify is a local-first CLI
new:
I built Doxify for both moments.
I almost skipped this challenge because of my CPU. Most AI demos I saw assumed a GPU or a cloud account. My laptop is CPU-only. The thing that brought me back was `gemma3:1b`: small enough to run locally, open-weight, and good enough to draft when a deterministic verifier checks the output. That constraint shaped the whole build.
Doxify is a local-first CLI
file: ~/Desktop/repos/doxify/submission.md
old:
- **It runs on a normal laptop.** `gemma3:1b` is about 806 MB and runs on an 8 GB CPU laptop. After the model download, the default workflow is offline. No API key, no per-token bill.
new:
- **It runs on a normal laptop.** `gemma3:1b` is about 806 MB and runs on the 8 GB CPU-only laptop that almost kept me out of this challenge. After the model download, the default workflow is offline. No API key, no per-token bill.
file: ~/Desktop/repos/doxify/submission.md
old:
I built Doxify for both moments.
Doxify is a local-first CLI
new:
I built Doxify for both moments.
I almost skipped this challenge because my laptop has a weak CPU. Most AI demos I saw assumed a GPU. `gemma3:1b` changed that: a small open-weight model that runs locally on the machine I already have. That constraint shaped the build: a small model, bounded prompts, and a deterministic verifier to carry the trust work.
Doxify is a local-first CLI
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**The first case study was too confident.** The a local demo repo, when tested, produced 34 unsupported claims out of 47.
new:
**The first case study was too confident.** The local demo repo produced 34 unsupported claims out of 47.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The design target is a sub-10-minute local compile on an 8 GB CPU laptop. The model is small, the prompts are bounded, and the expensive trust work is deterministic.
new:
The design target is a sub-10-minute local compile on an 8 GB CPU laptop, because that is the machine I have. The model is small, the prompts are bounded, and the expensive trust work is deterministic.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
- **It runs on a normal laptop.** `gemma3:1b` is about 806 MB and runs on an 8 GB CPU laptop. After the model download, the default workflow is offline. No API key, no per-token bill.
new:
- **It runs on a normal laptop.** The same weak CPU that almost kept me out runs `gemma3:1b`, which is about 806 MB. After the model download, the default workflow is offline. No API key, no per-token bill.
Edit applied successfully.
$ sed -n '1,25p' submission.md && printf '\n--- broke ---\n' && sed -n '365,378p' submission.md && printf '\n--- why ---\n' && sed -n '385,400p' submission.md && printf '\n--- payoff ---\n' && sed -n '410,425p' submission.md
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
# I built a local Gemma tool that writes the README I keep avoiding
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README is still empty. That page is the first thing an interviewer, a contributor, or another developer sees. If it is empty, they leave.
Two years later, the other bill comes due. The dependencies have moved, the build is deprecated, and the release notes are a wall. I do not need a generic summary. I need to know wh
… [truncated 4578 chars]
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: *This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
2:
3: # I built a local Gemma tool that writes the README I keep avoiding
4:
5: The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: I had built the thing. I had not explained it.
8:
9: Every project ends the same way for me. The last commit lands, my brain shuts off, and the REA
… [truncated 1292 chars]
$ sed -n '383,400p' submission.md && sed -n '365,375p' submission.md
After that: semantic verification instead of lexical overlap, more language support in Scout, and a better story for non-semver dependencies.
## Why Does Open Innovation Matter?
Four things were possible because the pieces are open:
- **The repo can stay private.** A developer with an NDA or sensitive customer data cannot upload the code to a hosted writing tool. Local Gemma means the evidence does not go to a model API.
- **It runs on a normal laptop.** The same weak CPU that almost kept me out runs `gemma3:1b`, which is about 806 MB. After the model download, the default workflow is offline. No API key, no per-token bill.
- **The model is a flag.** `--model` or `DOXIFY_MODEL` can swap Gemma for another Ollama model without changing the verifier.
- **The verifier is inspectable.** You
… [truncated 1440 chars]
$ git diff --check && uv run pytest -q && uv run ruff check .
........................................................................ [ 98%]
. [100%]
73 passed in 3.14s
All checks passed!
file: ~/Desktop/repos/doxify/submission.md
old:
I almost skipped this challenge because my laptop has a weak CPU. Most AI demos I saw assumed a GPU. `gemma3:1b` changed that: a small open-weight model that runs locally on the machine I already have
… [truncated 121 chars]
new:
I almost skipped this challenge because my laptop has a weak CPU. Most AI demos I saw assumed a GPU. I did not want to enter a challenge I could not run. `gemma3:1b` changed that: a small open-weight model that runs locally on the machine I already have. That constraint shaped the build: a small model, bounded prompts, and a deterministic verifier to carry the trust work.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
**The first case study was too confident.** The local demo repo produced 34 unsupported claims out of 47.
new:
**The first case study was too confident.** The run against the local demo repo left 34 of its 47 claims needing evidence.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
- **It runs on a normal laptop.** The same weak CPU that almost kept me out runs `gemma3:1b`, which is about 806 MB. After the model download, the default workflow is offline. No API key, no per-token
… [truncated 6 chars]
new:
- **It runs on a normal laptop.** The same weak CPU that almost kept me out is enough for `gemma3:1b`, which is about 806 MB. After the model download, the default workflow is offline. No API key, no per-token bill.
Edit applied successfully.
$ rg -n '\b(?:the a|a a|the the|to to|and and|is is|of of)\b|\bteh\b|\brecieve\b|\bseperate\b' submission.md || true
(no output)
$ git diff -- submission.md | sed -n '1,80p'
diff --git a/submission.md b/submission.md
index d3885cb..77834f8 100644
--- a/submission.md
+++ b/submission.md
@@ -1,77 +1,54 @@
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
-# Doxify: for sloths who finished the project and forgot the README
+# I built a local Gemma tool that writes the README I keep avoiding
-I had a working project, 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
+The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
-The code was done. My brain was a puddle.
+I had built the thing. I had not explained it.
-If you have ever pushed the last com
… [truncated 5582 chars]
$ git status --short
M .github/workflows/ci.yml
A .github/workflows/sentry-nightly.yml
M .obsidian/workspace.json
M PLAN.md
M README.md
A action.yml
A docs/observability.md
A docs/self-audit/evidence.json
A src/doxify/audit.py
M src/doxify/cli.py
A src/doxify/log.py
M src/doxify/monitoring.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
A src/doxify/redaction.py
M src/doxify/scout.py
A src/doxify/selfcheck.py
M submission.md
A tests/conftest.py
A tests/test_audit.py
A tests/test_logging.py
A tests/test_monitoring.py
A tests/test_redaction.py
A tests/test_self_audit.py
A tests/test_selfcheck.py
A tests/test_verify_cli.py
[session context compacted]
Objective
- Finish
submission.mdas a winning Hacktoberfest Week 3 entry with a sloth-focused, concrete narrative and accurate technical claims. - Add the user’s poor-CPU backstory — the constraint that nearly kept them out of the challenge — and then commit/push the article plus supporting code.
Important Details
- Required header and section order must stay intact:
What I Built,Demo,Code,How I Built It,Why Does Open Innovation Matter?,My Agent Session,The Lazy Payoff,Prize Categories. - Current title:
# I built a local Gemma tool that writes the README I keep avoiding. - Current user-attached
submission.mdis 432 lines / ~3317 words / 20 code blocks; all required sections and tags are present, including#devchallengeand#hf26challenge. - User wants the poor-CPU constraint added to make the story more convincing. Keep it honest: align with the existing 8 GB CPU laptop / no useful GPU /
gemma3:1b~806 MB positioning; do not invent benchmark numbers. - Current line 369 has a typo:
**The first case study was too confident.** The a local demo repo, when tested, produced 34 unsupported claims out of 47.Fix this while editing. - Do not overclaim: there is no actual Copilot CLI/session, no Entire/DevRelay embed, no hosted demo, no video, and no screenshots. Copilot category relies on GitHub Actions automation; Entire category relies on
My Agent Sessionplusmanifest.json/claims.json. - No fabricated user study/time-savings data.
The Lazy Payoffmust stay based on actual local runs onnextjs-dashboardand Doxify’s own README. - Verified facts: 73 tests pass, Ruff passes, build passes, self-audit = 5 verified / 8 attested,
nextjs-dashboard= 13 verified / 34 needs evidence / 0 rejected. - Direct artifact verification of
output/next-dashboard/case-study/case-study.mdyields 71 needs-evidence because it re-extracts claims; the article’s 34 refers to the compile/claims.jsonrun. - Sentry SDK 2.73.0 supports the
data_collectionoption; the article’sSAFE_DATA_COLLECTIONblock is now full and matchessrc/doxify/redaction.py. - Sentry is opt-in via
SENTRY_DSN;DOXIFY_SENTRY=0disables it. CI setsSENTRY_DSN="". Prompts/outputs are not recorded; URLs are stripped to scheme/host/path. - SerpApi remains the Scout fallback after
--notes FILEand GitHub releases; exact-quote validation still applies;--notespreserves offline use. - Supporting code/docs are staged except
submission.md;.obsidian/workspace.jsonis dirty and must not be committed. - Staged files include
action.yml,docs/,.github/workflows/sentry-nightly.yml,src/doxify/{audit,log,redaction,selfcheck}.py, new tests, and modifications to.github/workflows/ci.yml,README.md,PLAN.md, and coresrc/doxify/modules. - No commit has been made for the current staged code or the latest
submission.mdrewrite. The previously pushed article is still commitc1d4a57.
Work State
Completed
- Base submission was committed/pushed as
c1d4a57(docs(submission): add Hacktoberfest Week 3 post). - Latest
submission.mdrewrite was completed: new opening, sloth framing, “What broke along the way,” “What I would do next,” README/Scout/case-study details, Sentry/SerpApi/GitHub Actions sections, and prize-category summaries. - Article refinements added:
The repository had...opening,The sloth test..., replay-fixture link, raw-run caveat, demo walkthrough sentence, actual compile-loop code, nightly workflow reference, fullSAFE_DATA_COLLECTIONblock, and#devchallenge. - Verified implementation details against source:
monitoring.py,redaction.py,selfcheck.py,scout.py,pipeline.py,audit.py,action.yml, and.github/workflows/ci.yml. - Ran and passed
uv run pytest -q(73 tests),uv run ruff check .,uv build, and README self-audit. - Staged supporting code/docs/workflows/action/tests; intentionally left
.obsidian/workspace.jsonunstaged. - Current user-provided
submission.mdwas reviewed and is ready for the CPU-story addition plus typo fix.
Active
- Add the poor-CPU hesitation/backstory to
submission.mdin the most convincing place, likely the intro orWhy Does Open Innovation Matter?/The Lazy Payoff. - Fix line 369: replace
The a local demo repo, when tested...with a clean, accurate sentence. - After edits, proofread for consistency and run the verification commands again.
- Stage
submission.md, then commit and push the staged code plus the article per the earlier commit/push request.
Blocked
- No actual Copilot/Entire/DevRelay integration or demo video/screenshots; categories remain represented through GitHub Actions, session summary, and checked-in artifacts.
- Supporting code is staged but not committed/pushed, so new GitHub links in the article will 404 until pushed.
-
.obsidian/workspace.jsonremains modified and must be excluded from commits. - No real measured CPU benchmark; the CPU story should stay a personal constraint aligned with the existing design target.
Next Move
- Edit
submission.mdto add the poor-CPU backstory and fix the line-369 typo without inventing benchmarks. - Re-run
uv run pytest -q,uv run ruff check ., README self-audit, anduv build; then stagesubmission.md, commit staged code and article separately, and pushmainwhile excluding.obsidian/workspace.json.
Relevant Files
-
submission.md: main deliverable; current 432-line version needs CPU story and typo fix. -
PLAN.md: source for 8 GB CPU laptop, no useful GPU, and ~806 MBgemma3:1bpositioning. -
README.md: self-audit markers, product docs, and evidence-backed project facts. -
src/doxify/monitoring.py: opt-in Sentry transactions, model spans, cron monitor. -
src/doxify/redaction.py:SAFE_DATA_COLLECTION, secret/path redaction, safe URL handling. -
src/doxify/selfcheck.py: local health checks for Sentry cron monitor. -
src/doxify/scout.py: release notes, SerpApi fallba … [truncated 807 chars]
[session context compacted]
Objective
- Add the user’s poor-CPU backstory to
submission.mdand keep the Hacktoberfest Week 3 post winning, accurate, and ready to commit/push.
Important Details
- Latest user request: add that poor CPU almost stopped them from entering Hacktoberfest Week 3 and use it to make the story more convincing in
submission.md. - Required section order must stay intact:
What I Built,Demo,Code,How I Built It,Why Does Open Innovation Matter?,My Agent Session,The Lazy Payoff,Prize Categories. - Required tags are
#devchallengeand#hf26challenge; both are currently present. - Core framing: sloth developer finishes the last commit and cannot face the README; years later, dependency drift makes Scout useful. Core line: “The model writes. The code checks.”
- Challenge facts checked from the DEV page: Entire = share agent sessions behind the project; GitHub Copilot includes automating with GitHub Actions; Sentry wants setup/performance/traces; SerpApi = live search/grounding; Gemma must be used locally, fine-tuned, or served.
- No actual Copilot session, Entire/DevRelay embed, hosted demo, or video; the article represents these honestly through GitHub Actions automation, session summaries, CLI commands, and checked-in artifacts.
- Do not fabricate user studies or time savings;
scholarship-mcpis input data only, not a completed checked-in run. - Verified numbers: 73 tests pass; Ruff and build pass; self-audit = 5 verified / 8 attested;
nextjs-dashboard= 13 verified / 34 needs evidence / 0 rejected. - Verifying
output/next-dashboard/case-study/case-study.mdas an artifact yields 71 needs-evidence claims and exits 1; this is expected for the CI negative test. - Current article: 432 lines, ~3317 words, 20 code blocks; code fences are balanced.
- Current article title:
# I built a local Gemma tool that writes the README I keep avoiding. - Style references:
devto-digest,Mountain Dewarcryocooler,Mama POS day-book; concrete details, short paragraphs, honest failures, no generic AI prose. - Deadline context: 2026-10-25 11:59 PM PDT = 2026-10-26 7:59 AM WAT.
- Git state: supporting code/docs are staged;
submission.mdis unstaged;.obsidian/workspace.jsonis modified and must not be committed. - Previous pushed submission is commit
c1d4a57; latest rewrite and supporting code are still uncommitted. - Latest CPU-story edit attempts failed with
Could not find oldString in the file; re-readsubmission.mdbefore editing. - Current line 369 has a typo:
**The first case study was too confident.** The a local demo repo, when tested, produced 34 unsupported claims out of 47. - No Ollama server is running (
ollama listfails), sogemma3:1bwas not re-run this session; performance claims rely onPLAN.mdand existing artifacts.
Work State
Completed
- Latest style rewrite written to
submission.md: stronger sloth intro, README/Scout/case-study sections, Demo, Code, How I Built It, Why Open Innovation Matters, My Agent Session, Lazy Payoff, Prize Categories. - Added “What broke along the way” and “What I would do next.”
- Fixed the compile-loop snippet to match
pipeline.py, includingfallback_used=False. - Expanded the Sentry
SAFE_DATA_COLLECTIONsnippet to the actual 11-key policy. - Added references to
docs/observability.mdand.github/workflows/sentry-nightly.yml. - Added the Scout replay-fixture link to
benchmarks/scout-react-18-19.json. - Added transparency that the
nextjs-dashboardcase study is the raw run with awkward model openings. - Added
#devchallengeto the tag line. - Verified
sentry_sdk2.73.0 supportsdata_collection;SAFE_DATA_COLLECTIONkeys are valid. - Ran
uv run pytest -q→ 73 passed;uv run ruff check .→ clean;uv build→ success; self-audit → 5 verified / 8 attested. - Ran negative CI verification on the unresolved case study → exit 1 as expected.
- Ran
uv run doxify self-check --json→ok: true. - Fetched the DEV challenge page and confirmed category requirements and required tags.
- Performed secret scan: only dummy token in
tests/test_redaction.pyand placeholder DSN indocs/observability.md. - Staged supporting files:
.github/workflows/ci.yml,.github/workflows/sentry-nightly.yml,PLAN.md,README.md,action.yml,docs/,src/doxify/{audit,cli,log,monitoring,ollama_client,pipeline,redaction,scout,selfcheck}.py,tests/*.
Active
- Adding the CPU backstory paragraph after
I built Doxify for both moments.and beforeDoxify is a local-first CLI.... - Updating the
**It runs on a normal laptop.**bullet to call back to the 8 GB CPU-only laptop that almost kept the user out. - Proofreading the full article; fix line 369 typo and any remaining flow issues.
- Deciding commit/push scope: code/docs are staged, but
submission.mdis still unstaged.
Blocked
- Latest CPU-story edits have not been applied because both
oldStringreplacements failed; current file state must be re-read first. - No Ollama server available for a fresh local
gemma3:1brun. - No actual Copilot/Entire/DevRelay integration, demo video, or screenshots.
- GitHub links to
action.yml,redaction.py,selfcheck.py,docs/observability.md, and the new workflows will 404 until supporting code/docs are pushed. - Latest
submission.mdrewrite has not been committed or pushed.
Next Move
- Re-read
submission.md, then add the CPU anecdote afterI built Doxify for both moments.and update theIt runs on a normal laptop.bullet to mention the CPU-only laptop that almost kept the user out; fix the line 369 typo. - Re-run quick checks, then commit/push staged code/docs plus
submission.md, keeping.obsidian/workspace.jsonunstaged.
Relevant Files
-
submission.md: main deliverable; latest rewrite uncommitted; needs CPU story and typo fix. -
PLAN.md: positioning, demo facts, 806 MB / 8 GB laptop target, deadline. -
README.md: product docs, self-audit markers, command details.
… [truncated 1138 chars]
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
Objective
- Add the poor-CPU backstory to
submission.mdso the Hacktoberfest Week 3 story feels personal and convincing, then publish the article with its supporting code.
Important Details
- Latest user request: add that poor CPU almost stopped them from participating in Hacktoberfest Week 3; use it to make the story more convincing in
submission.md. - Current article already contains the CPU story in the intro and a callback in
Why Does Open Innovation Matter?; it is now 434 lines with 20 code blocks. - Added intro text: “I almost skipped this challenge because my laptop has a weak CPU… I did not want to enter a challenge I could not run.
gemma3:1bchanged that…” followed by the “small model, bounded prompts, deterministic verifier” constraint. - Updated bullet: “The same weak CPU that almost kept me out is enough for
gemma3:1b, which is about 806 MB.” - Updated design-target sentence: “The design target is a sub-10-minute local compile on an 8 GB CPU laptop, because that is the machine I have.”
- Fixed typo: current text reads “The run against the local demo repo left 34 of its 47 claims needing evidence.”
- Required header and section order must stay:
What I Built,Demo,Code,How I Built It,Why Does Open Innovation Matter?,My Agent Session,The Lazy Payoff,Prize Categories. - Current title:
# I built a local Gemma tool that writes the README I keep avoiding; required tags#devchallengeand#hf26challengeare present. - Category mapping: Gemma = local
gemma3:1b+ deterministic verifier; Entire =My Agent Session+ checked-inmanifest.json/claims.json; Copilot = GitHub Action/CI/nightly workflow; Sentry = opt-in SDK/traces/cron; SerpApi = Scout fallback. - No actual Copilot/Entire/DevRelay integration, no hosted demo, no video, no screenshots. Represent these honestly through GitHub Actions, session summary, and artifacts.
- No fabricated user study/time savings.
The Lazy Payoffstays based on actual local runs onnextjs-dashboardand Doxify’s own README. - Verified facts: 73 tests pass, Ruff passes, build passes, self-audit = 5 verified / 8 attested,
nextjs-dashboard= 13 verified / 34 needs evidence / 0 rejected. - Direct artifact verification of
output/next-dashboard/case-study/case-study.mdyields 71 needs-evidence because it re-extracts claims; the article’s 34 refers to the compile/claims.jsonrun. - Sentry SDK 2.73.0 supports the
data_collectionoption;SAFE_DATA_COLLECTIONmatchessrc/doxify/redaction.py. Sentry is opt-in viaSENTRY_DSN;DOXIFY_SENTRY=0disables it. - SerpApi remains the Scout fallback after
--notes FILEand GitHub releases; exact-quote validation still applies. - Current git state: supporting code/docs/workflows/tests are staged;
submission.mdis unstaged;.obsidian/workspace.jsonis dirty and must not be committed. - Previous pushed article is still commit
c1d4a57; no commit exists for the current staged code or latest article. - Style references: devto-digest, Mountain Dewar cryocooler, Mama POS day-book; concrete, short paragraphs, honest failures, sloth humor.
- Deadline: 2026-10-25 11:59 PM PDT = 2026-10-26 7:59 AM WAT.
Work State
Completed
- CPU constraint has been successfully added to the intro,
Why Does Open Innovation Matter?, and the design-target sentence. - Line-369 typo was fixed to “The run against the local demo repo left 34 of its 47 claims needing evidence.”
- Latest
submission.mdrewrite completed: new opening, sloth framing, “What broke along the way,” “What I would do next,” README/Scout/case-study details, Sentry/SerpApi/GitHub Actions sections, and prize-category summaries. - Article refinements added:
The repository had...opening,The sloth test..., replay-fixture link, raw-run caveat, demo walkthrough sentence, actual compile-loop code, nightly workflow reference, fullSAFE_DATA_COLLECTIONblock, and#devchallenge. - Verified implementation details against source:
monitoring.py,redaction.py,selfcheck.py,scout.py,pipeline.py,audit.py,action.yml, and.github/workflows/ci.yml. - Ran and passed
uv run pytest -q(73 tests),uv run ruff check .,uv build, README self-audit, andgit diff --check. - Staged supporting code/docs/workflows/action/tests; intentionally left
.obsidian/workspace.jsonunstaged. - Common typo scan in
submission.mdfound no remaining duplicated-word patterns.
Active
- Final proofread of the 434-line
submission.mdand confirmation that the CPU story reads naturally alongside the technical claims. -
submission.mdis still uncommitted; the supporting code/docs remain staged but uncommitted. - Pending publish: commit staged code/docs, stage and commit
submission.md, then pushmain.
Blocked
- No actual Copilot/Entire/DevRelay integration or demo video/screenshots; categories rely on GitHub Actions, session summary, and checked-in artifacts.
- Supporting code is staged but not committed/pushed, so new GitHub links in the article will 404 until pushed.
-
.obsidian/workspace.jsonremains modified and must be excluded from commits. - No real measured CPU benchmark; the CPU story is a personal constraint aligned with the existing 8 GB CPU laptop /
gemma3:1b~806 MB positioning.
Next Move
- Run a final proofread/
git diff --check, then stagesubmission.md, commit staged code/docs and the article separately, and pushmainwhile excluding.obsidian/workspace.json. - Verify the pushed GitHub links resolve and the DEV article renders correctly with both required tags.
Relevant Files
-
submission.md: 434-line main deliverable; CPU story integrated; uncommitted. -
PLAN.md: source for 8 GB CPU laptop, no useful GPU, and ~806 MBgemma3:1bpositioning. -
README.md: self-audit markers, product docs, and evidence-backed project facts. -
src/doxify/monitoring.py: opt-in Sentry transactions, model spans, cron monitor. -
src/doxify/redaction.py: `SAFE_DATA_CO … [truncated 988 chars]
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
$ git status --short && printf '\n--- recent commits ---\n' && git log --oneline -5 && printf '\n--- branch ---\n' && git branch --show-current && git remote -v
M .github/workflows/ci.yml
A .github/workflows/sentry-nightly.yml
M .obsidian/workspace.json
M PLAN.md
M README.md
A action.yml
A docs/observability.md
A docs/self-audit/evidence.json
A src/doxify/audit.py
M src/doxify/cli.py
A src/doxify/log.py
M src/doxify/monitoring.py
M src/doxify/ollama_client.py
M src/doxify/pipeline.py
A src/doxify/redaction.py
M src/doxify/scout.py
A src/doxify/selfcheck.py
M submission.md
A tests/conftest.py
A tests/test_audit.py
A tests/test_logging.py
A tests/test_monitoring.py
A tests/test_redaction.py
A tests/test_self_audit.py
A tests/test_selfcheck.py
A tests/test_verify_cli.py
--- recent commits ---
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
d5c94b
… [truncated 313 chars]
$ sed -n '1,25p' submission.md && printf '\n--- broke ---\n' && sed -n '360,378p' submission.md && printf '\n--- why ---\n' && sed -n '383,400p' submission.md && printf '\n--- payoff ---\n' && sed -n '408,425p' submission.md
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
# I built a local Gemma tool that writes the README I keep avoiding
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README is still empty. That page is the first thing an interviewer, a contributor, or another developer sees. If it is empty, they leave.
Two years later, the other bill comes due. The dependencies have moved, the build is deprecated, and the release notes are a wall. I do not need a generic summary. I need to know wh
… [truncated 4930 chars]
$ rg -n "weak CPU|CPU|8 GB|806|almost|participat" submission.md PLAN.md
PLAN.md:528:- The compile finishes on the 8GB CPU laptop in under 10 minutes.
PLAN.md:982:Compiles can take minutes on a CPU. Do not block a single HTTP request for the whole compile.
PLAN.md:1692:The target laptop has 8GB RAM and no useful GPU. `gemma3:1b` is already installed and is about 806MB. A larger model would be slower and may not fit comfortably.
PLAN.md:2163:Target: under 10 minutes on the 8GB CPU laptop.
submission.md:15:I almost skipped this challenge because my laptop has a weak CPU. Most AI demos I saw assumed a GPU. I did not want to enter a challenge I could not run. `gemma3:1b` changed that: a small open-weight model that runs locally on the machine I already have. That constraint shaped the build: a small model, bounded prompts, and a deterministic verifier to carry the
… [truncated 599 chars]
$ rg -n "self-audit|self-check|verify --|doxify verify|pytest|ruff|uv build" README.md pyproject.toml Makefile 2>/dev/null | head -80
pyproject.toml:30: "pytest>=9.1.1",
pyproject.toml:31: "ruff>=0.17.2",
pyproject.toml:34:[tool.ruff]
pyproject.toml:38:[tool.ruff.lint]
README.md:78:doxify verify SESSION [--artifact PATH] [--fail-on STATUSES]
README.md:85:`doxify verify` exits non-zero when a claim needs evidence or is rejected. Use
README.md:131: evidence: docs/self-audit/evidence.json
README.md:149:uv run pytest -q
README.md:150:uv run ruff check .
$ git diff --cached --stat && printf '\n--- unstaged ---\n' && git diff --stat && printf '\n--- submission stats ---\n' && wc -l -w submission.md && rg -c '^```' submission.md && rg -n '^#devchallenge|#hf26challenge' submission.md
.github/workflows/ci.yml | 32 ++
.github/workflows/sentry-nightly.yml | 35 ++
PLAN.md | 14 +-
README.md | 59 ++-
action.yml | 69 +++
docs/observability.md | 123 +++++
docs/self-audit/evidence.json | 998 +++++++++++++++++++++++++++++++++++
src/doxify/audit.py | 250 +++++++++
src/doxify/cli.py | 165 ++++--
src/doxify/log.py | 97 ++++
src/doxify/monitoring.py | 463 +++++++++++++++-
src/doxify/ollama_client.py | 52 +-
src/doxify/pipeline.py | 11 +-
src/doxify/redaction.py | 338 ++++++++++++
src/doxify/scout.py | 5 +-
src/doxify/selfchec
… [truncated 903 chars]
$ git diff --cached --check && git diff --check
(no output)
$ sed -n '100,160p' README.md && printf '\n--- cli help ---\n' && uv run doxify --help
The repository contains test-related files at `tests/test_readme.py`. CI is
defined in `.github/workflows/ci.yml`.
<!-- doxify-evidence: E2 E4 E5 E6 E7 -->
## Scout upgrades
`doxify scout` fetches release notes between two versions, extracts breaking
changes with exact quotes, and searches your tracked code for affected APIs.
```bash
doxify scout react 18 19 --repo /path/to/app
```
Use `--notes FILE` for offline notes, `--deep` to analyze every fetched release,
and `--expect FILE` to replay a known breaking-change list. Scout writes
`scout.md`, `scout.json`, `release-notes.json`, and `manifest.json`; it never
modifies the scanned repository.
<!-- doxify-attested: scout usage instructions -->
## Verify in CI
The composite action runs the same deterministic checker. Unmarked sections
… [truncated 2945 chars]
$ uv run pytest -q && uv run ruff check . && uv build && git diff --check
........................................................................ [ 98%]
. [100%]
73 passed in 1.17s
All checks passed!
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
$ sed -n '100,160p' README.md && printf '\n--- CLI verify help ---\n' && uv run doxify verify --help 2>&1 | sed -n '1,80p' && printf '\n--- self-check help ---\n' && uv run doxify self-check --help 2>&1 | sed -n '1,80p'
The repository contains test-related files at `tests/test_readme.py`. CI is
defined in `.github/workflows/ci.yml`.
<!-- doxify-evidence: E2 E4 E5 E6 E7 -->
## Scout upgrades
`doxify scout` fetches release notes between two versions, extracts breaking
changes with exact quotes, and searches your tracked code for affected APIs.
```bash
doxify scout react 18 19 --repo /path/to/app
```
Use `--notes FILE` for offline notes, `--deep` to analyze every fetched release,
and `--expect FILE` to replay a known breaking-change list. Scout writes
`scout.md`, `scout.json`, `release-notes.json`, and `manifest.json`; it never
modifies the scanned repository.
<!-- doxify-attested: scout usage instructions -->
## Verify in CI
The composite action runs the same deterministic checker. Unmarked sections
… [truncated 3464 chars]
$ rg -n "self-audit|self audit|5 verified|attested" README.md | head -40 && printf '\n--- evidence check ---\n' && uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --check 2>&1 | tail -40
11:<!-- doxify-attested: project description and positioning -->
24:<!-- doxify-attested: installation instructions -->
38:<!-- doxify-attested: local setup and privacy behavior -->
68:<!-- doxify-attested: cloud setup, privacy, and billing guidance -->
90:<!-- doxify-attested: command reference -->
119:<!-- doxify-attested: scout usage instructions -->
124:fail; sections marked `doxify-attested` are author-owned and pass unless
125:`attested` is added to `fail-on`. Point it at an evidence-marked README:
131: evidence: docs/self-audit/evidence.json
144:<!-- doxify-attested: CI usage instructions -->
153:<!-- doxify-attested: contributor instructions -->
--- evidence check ---
Artifact: README.md
┌───────────────────┬───────────
… [truncated 463 chars]
$ uv run pytest -q && uv run ruff check . && uv build
........................................................................ [ 98%]
. [100%]
73 passed in 1.28s
All checks passed!
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
$ uv run doxify verify output/next-dashboard/case-study/case-study.md --evidence docs/self-audit/evidence.json --check 2>&1 | tail -30; printf '\nexit=%s\n' "$?"
Usage: doxify verify [OPTIONS] [session]
Try 'doxify verify --help' for help.
╭─ Error ──────────────────────────────────────────────────────────────────────╮
│ Invalid value for 'session': Directory │
│ 'output/next-dashboard/case-study/case-study.md' is a file. │
╰──────────────────────────────────────────────────────────────────────────────╯
exit=0
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: *This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
2:
3: # I built a local Gemma tool that writes the README I keep avoiding
4:
5: The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: I had built the thing. I had not explained it.
8:
9: Every project ends the same way for me. The last commit lands, my brain shuts off, and the REA
… [truncated 25250 chars]
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
1: *This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
2:
3: # I built a local Gemma tool that writes the README I keep avoiding
4:
5: The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
6:
7: I had built the thing. I had not explained it.
8:
9: Every project ends the same way for me. The last commit lands, my brain shuts off, and the REA
… [truncated 12715 chars]
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
221: ids = " ".join(dict.fromkeys(card.id for card in cards))
222: if ids:
223: lines.extend(["", f"<!-- doxify-evidence: {ids} -->"])
224: ```
225:
226: That is how Doxify audits its own README. The repository has a committed evidence file, hidden markers, and a CI job that fails if a section is unmarked or unsupported. The self-audit currently reports five verified claims and eight author-attested claims.
227:
228: ### Scout's exact-quote rule
229:
230: ```python
231: def quote_in_notes(quote: st
… [truncated 12094 chars]
file: ~/Desktop/repos/doxify/submission.md
<path>~/Desktop/repos/doxify/submission.md</path>
<type>file</type>
<content>
221: ids = " ".join(dict.fromkeys(card.id for card in cards))
222: if ids:
223: lines.extend(["", f"<!-- doxify-evidence: {ids} -->"])
224: ```
225:
226: That is how Doxify audits its own README. The repository has a committed evidence file, hidden markers, and a CI job that fails if a section is unmarked or unsupported. The self-audit currently reports five verified claims and eight author-attested claims.
227:
228: ### Scout's exact-quote rule
229:
230: ```python
231: def quote_in_notes(quote: st
… [truncated 12094 chars]
$ uv run doxify compile --help 2>&1 | sed -n '1,100p'; printf '\n--- readme ---\n'; uv run doxify readme --help 2>&1 | sed -n '1,100p'; printf '\n--- scout ---\n'; uv run doxify scout --help 2>&1 | sed -n '1,100p'
Usage: doxify compile [OPTIONS] {repo}
Run the full scan, draft, verify, defense, and export pipeline.
╭─ Arguments ──────────────────────────────────────────────────────────────────╮
│ * repo <directory> [required] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --out -o <path> Output directory.
… [truncated 5147 chars]
$ rg -n "SECURITY|secret|token|api_key|password" submission.md README.md docs/observability.md action.yml .github/workflows/*.yml | head -40
submission.md:192: claim.reason = "No citation token was found."
submission.md:203:The verifier also checks numbers against the cited evidence, checks code identifiers, checks token overlap, and flags claims that code cannot prove: deployment, "tests pass," OAuth, user counts, performance improvements, awards, team size, and "first/only" claims.
submission.md:257:key = serpapi_key or os.environ.get("SERPAPI_API_KEY")
submission.md:259: notes = fetch_serpapi_notes(package, from_version, to_version, api_key=key)
submission.md:280:Inside the Ollama client, a model call becomes a span with provider, model, temperature, max tokens, latency, and token usage. The prompt and the output are not recorded.
submission.md:287: max_tokens=num_predict,
submission.md:292: prompt_tokens=pro
… [truncated 1405 chars]
$ git diff --cached -- .github/workflows/ci.yml .github/workflows/sentry-nightly.yml action.yml | sed -n '1,220p'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2704633..74e0a7d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,3 +41,35 @@ jobs:
name: dist
path: dist/
if-no-files-found: error
+
+ verify:
+ name: Verify evidence-backed claims
+ runs-on: ubuntu-latest
+ env:
+ SENTRY_DSN: ""
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Verify self-audit README
+ uses: ./
+ with:
+ artifact: README.md
+ evidence: docs/self-audit/evidence.json
+ fail-on: needs_evidence,rejected
+
+ - name: Verify unresolved case study (expected failure)
+ id: negative
+ continue-on-error: true
+ uses: ./
+
… [truncated 3641 chars]
$ ls -la output/next-dashboard/case-study/ | sed -n '1,30p'; printf '\n--- direct negative ---\n'; set +e; uv run doxify verify --artifact output/next-dashboard/case-study/case-study.md --evidence output/next-dashboard/case-study/evidence.json --check >/tmp/doxify-negative.out 2>&1; code=$?; tail -30 /tmp/doxify-negative.out; echo "exit=$code"
total 140
drwxrwxr-x 2 [user] [user] 4096 Oct 23 16:32 .
drwxrwxr-x 3 [user] [user] 4096 Oct 23 15:54 ..
-rw-rw-r-- 1 [user] [user] 12956 Oct 23 16:32 case-study.md
-rw-rw-r-- 1 [user] [user] 25383 Oct 23 16:32 claims.json
-rw-rw-r-- 1 [user] [user] 3446 Oct 23 16:32 defense.json
-rw-rw-r-- 1 [user] [user] 2774 Oct 23 16:32 defense.md
-rw-rw-r-- 1 [user] [user] 21653 Oct 23 15:54 evidence.json
-rw-rw-r-- 1 [user] [user] 971 Oct 23 16:32 manifest.json
-rw-rw-r-- 1 [user] [user] 23939 Oct 23 16:32 sections.json
-rw-rw-r-- 1 [user] [user] 25383 Oct 23 16:32 verification.json
--- direct negative ---
No citation token was found.
C3 needs_evidence “The stack of this project consists primarily of React,
Node.js, and npm (or yarn). [E6]
Citation does not overlap wit
… [truncated 1439 chars]
$ uv run doxify self-check --json
{
"checks": [
{
"detail": "3.10",
"name": "python",
"ok": true
},
{
"detail": "/usr/bin/git",
"name": "git",
"ok": true
},
{
"detail": "0.1.0",
"name": "package",
"ok": true
},
{
"detail": "read/write ok",
"name": "tempdir",
"ok": true
}
],
"ok": true
}
$ git diff --cached --name-only | while read -r f; do [ -f "$f" ] && printf '%s ' "$f" && wc -c < "$f"; done | tail -30
.github/workflows/ci.yml 1751
.github/workflows/sentry-nightly.yml 803
PLAN.md 96605
README.md 4393
action.yml 1964
docs/observability.md 4831
docs/self-audit/evidence.json 29782
src/doxify/audit.py 8118
src/doxify/cli.py 20881
src/doxify/log.py 3076
src/doxify/monitoring.py 13446
src/doxify/ollama_client.py 7723
src/doxify/pipeline.py 11750
src/doxify/redaction.py 10086
src/doxify/scout.py 49737
src/doxify/selfcheck.py 2119
tests/conftest.py 348
tests/test_audit.py 4203
tests/test_logging.py 1560
tests/test_monitoring.py 7428
tests/test_redaction.py 4228
tests/test_self_audit.py 718
tests/test_selfcheck.py 1851
tests/test_verify_cli.py 3570
$ python - <<'PY'
import re
from pathlib import Path
text = Path('submission.md').read_text()
for url in sorted(set(re.findall(r'https://github\.com/Ekomjah/doxify/(?:blob|tree)/main/([^\s)]+)', text))):
p = Path(url)
print(('OK ' if p.exists() else 'MISS'), url)
PY
zsh:1: command not found: python
$ git diff --cached -- src/doxify/cli.py src/doxify/pipeline.py src/doxify/scout.py src/doxify/ollama_client.py | sed -n '1,240p'
diff --git a/src/doxify/cli.py b/src/doxify/cli.py
index 0b9479d..e785f40 100644
--- a/src/doxify/cli.py
+++ b/src/doxify/cli.py
@@ -1,7 +1,9 @@
from __future__ import annotations
+import logging
import re
import shutil
+import sys
from datetime import datetime
from pathlib import Path
@@ -24,8 +26,17 @@ from .console import (
stage_progress,
)
from .errors import DoxifyError
+from .log import configure_logging, log_event
from .models import ClaimStatus, CompileSummary, ScanResult
-from .monitoring import init_monitoring, report_exception
+from .monitoring import (
+ begin_stage,
+ command_transaction,
+ cron_monitor,
+ flush_monitoring,
+ init_monitoring,
+ monitoring_status,
+ span,
+)
from .ollama_client import DEFAULT_MODEL, OllamaClient
from .pi
… [truncated 7503 chars]
$ rg -n "TODO|FIXME|XXX|print\(|breakpoint\(|pdb|ghp_|sk-|AKIA|SENTRY_DSN=.*https" src tests action.yml docs .github/workflows README.md PLAN.md submission.md | head -80
docs/observability.md:16:export SENTRY_DSN="[redacted]"
docs/observability.md:110:export SENTRY_DSN="[redacted]"
src/doxify/console.py:40: console.print(table)
src/doxify/console.py:62: console.print(table)
src/doxify/console.py:65: console.print(
src/doxify/console.py:89: console.print(table)
src/doxify/console.py:92: console.print(
src/doxify/console.py:123: console.print(table)
src/doxify/console.py:127: console.print(
src/doxify/console.py:132: console.print(f" ... and {len(touching) - 5} more in the report.")
src/doxify/console.py:134: console.print(
src/doxify/console.py:139: console.print(f"\n[yellow]{len(report.warnings)} warning(s) recorded in the report.[/yellow]")
src/doxify/console.py
… [truncated 1092 chars]
$ git diff --cached | rg -n -i "(ghp_|github_pat_|glpat-|sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-|-----BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----|sntrys_|SENTRY_DSN\s*=\s*https?://[^\"' ]+@)" || true
2810:+ re.compile(r"\bgithub_pat_[A-Za-z0-9_]{20,}\b"),
3673:+ "Authorization: Bearer [redacted-token] "
3679:+ assert "ghp_" not in redacted
$ git diff --cached -- docs/self-audit/evidence.json | sed -n '1,80p'; printf '\n--- paths in evidence ---\n'; rg -n '/home/|/Users/|C:\\\\' docs/self-audit/evidence.json | head -20 || true
diff --git a/docs/self-audit/evidence.json b/docs/self-audit/evidence.json
new file mode 100644
index 0000000..d180303
--- /dev/null
+++ b/docs/self-audit/evidence.json
@@ -0,0 +1,998 @@
+{
+ "schema_version": "1.0",
+ "repo": {
+ "path": ".",
+ "name": "doxify",
+ "head_sha": "2f2bfdd69d0bcb18747d97db147083e1ebc9fe8a",
+ "branch": "main",
+ "commit_count": 8,
+ "first_commit_date": "2026-10-23",
+ "last_commit_date": "2026-10-24",
+ "tracked_file_count": 51,
+ "languages": {
+ "Python": 28,
+ "JSON": 13,
+ "Markdown": 4,
+ "TOML": 1,
+ "YAML": 1
+ },
+ "readme_path": "README.md",
+ "manifests": [
+ "pyproject.toml"
+ ],
+ "test_files": [
+ "tests/test_ollama_client.py",
+ "tests/test_readme.py",
+ "te
… [truncated 1231 chars]
$ git diff -- .obsidian/workspace.json | sed -n '1,80p'
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index ca57edf..39f63bd 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
- "file": "PLAN.md",
+ "file": "submission.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
- "title": "PLAN"
+ "title": "submission"
}
}
]
@@ -55,7 +55,7 @@
"state": {
"type": "search",
"state": {
- "query": "happens",
+ "query": "reverse",
"matchingCase": true,
… [truncated 548 chars]
$ python3 - <<'PY'
import re
from pathlib import Path
text = Path('submission.md').read_text()
for url in sorted(set(re.findall(r'https://github\.com/Ekomjah/doxify/(?:blob|tree)/main/([^\s)]+)', text))):
p = Path(url)
print(('OK ' if p.exists() else 'MISS'), url)
PY
OK .github/workflows/ci.yml
OK .github/workflows/sentry-nightly.yml
OK action.yml
OK benchmarks/scout-react-18-19.json
OK docs/observability.md
OK output/next-dashboard/case-study/case-study.md
OK output/next-dashboard/case-study/claims.json
OK output/next-dashboard/case-study/defense.md
OK src/doxify/evidence.py
OK src/doxify/monitoring.py
OK src/doxify/pipeline.py
OK src/doxify/redaction.py
OK src/doxify/scout.py
OK src/doxify/selfcheck.py
OK src/doxify/verifier.py
$ git diff --cached -- src/doxify/pipeline.py src/doxify/scout.py src/doxify/ollama_client.py | sed -n '1,200p'
diff --git a/src/doxify/ollama_client.py b/src/doxify/ollama_client.py
index 9476c66..5b09163 100644
--- a/src/doxify/ollama_client.py
+++ b/src/doxify/ollama_client.py
@@ -1,11 +1,15 @@
from __future__ import annotations
+import logging
import os
+import time
from typing import Any, Literal
from ollama import Client, RequestError, ResponseError
from .errors import ModelError
+from .log import log_event
+from .monitoring import model_span, record_model_usage
DEFAULT_HOST = "http://127.0.0.1:11434"
DEFAULT_MODEL = "gemma3:1b"
@@ -127,16 +131,62 @@ class OllamaClient:
}
if not self.is_cloud:
payload["keep_alive"] = "10m"
+ started = time.perf_counter()
try:
- response = self._client.chat(**payload)
+ with mod
… [truncated 4542 chars]
$ git diff --cached -- src/doxify/monitoring.py src/doxify/redaction.py src/doxify/log.py | sed -n '1,220p'
diff --git a/src/doxify/log.py b/src/doxify/log.py
new file mode 100644
index 0000000..1fb64b6
--- /dev/null
+++ b/src/doxify/log.py
@@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import json
+import logging
+import os
+import sys
+from datetime import datetime, timezone
+from typing import Any
+
+from .redaction import SAFE_LOG_FIELDS, redact_text, redact_value
+
+LOGGER_NAME = "doxify"
+_configured = False
+
+logger = logging.getLogger(LOGGER_NAME)
+
+
+class _RedactingFilter(logging.Filter):
+ def filter(self, record: logging.LogRecord) -> bool:
+ record.msg = redact_text(record.msg)
+ if record.args:
+ record.args = redact_value(record.args)
+ fields = getattr(record, "doxify_fields", None)
+ if isinstance(fields, dict):
+ r
… [truncated 5658 chars]
All checks pass: 73 tests, Ruff, build, README self-audit (5 verified / 8 attested), self-check, and the expected-failure case study. The CPU story is already integrated cleanly, so I’m committing the code and article separately.
$ git diff --cached --check && git commit -m "feat(observability): add tracing, self-check, and verification action"
[main afd9b8b] feat(observability): add tracing, self-check, and verification action
24 files changed, 3585 insertions(+), 75 deletions(-)
create mode 100644 .github/workflows/sentry-nightly.yml
create mode 100644 action.yml
create mode 100644 docs/observability.md
create mode 100644 docs/self-audit/evidence.json
create mode 100644 src/doxify/audit.py
create mode 100644 src/doxify/log.py
create mode 100644 src/doxify/redaction.py
create mode 100644 src/doxify/selfcheck.py
create mode 100644 tests/conftest.py
create mode 100644 tests/test_audit.py
create mode 100644 tests/test_logging.py
create mode 100644 tests/test_monitoring.py
create mode 100644 tests/test_redaction.py
create mode 100644 tests/test_self_audit.py
create mode 100644 tests/test_selfcheck.py
create mode 100
… [truncated 29 chars]
$ git log -3 --format='---%n%s%n%b'
---
feat(observability): add tracing, self-check, and verification action
---
docs(submission): add Hacktoberfest Week 3 post
---
feat(monitoring): add Sentry error monitoring with MIT license
$ git status --short --branch
## main...origin/main [ahead 1]
M .obsidian/workspace.json
M submission.md
$ git diff --cached --numstat | tail -5; printf '\n--- submission diff check ---\n'; git diff --check -- submission.md
--- submission diff check ---
$ git status --short && git add submission.md && git diff --cached --check && git commit -m "docs(submission): sharpen Hacktoberfest story and add CPU constraint"
M .obsidian/workspace.json
M submission.md
[main a26a20f] docs(submission): sharpen Hacktoberfest story and add CPU constraint
1 file changed, 118 insertions(+), 185 deletions(-)
$ git add submission.md && git commit -m "docs(submission): sharpen Week 3 story and add CPU constraint" && git push origin main
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .obsidian/workspace.json
no changes added to commit (use "git add" and/or "git commit -a")
$ git status --short --branch && git log --oneline -3 && git push origin main
## main...origin/main [ahead 2]
M .obsidian/workspace.json
a26a20f docs(submission): sharpen Hacktoberfest story and add CPU constraint
afd9b8b feat(observability): add tracing, self-check, and verification action
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
To github.com:Ekomjah/doxify.git
c1d4a57..a26a20f main -> main
$ git status --short --branch && git log --oneline -3 && git remote -v
## main...origin/main
M .obsidian/workspace.json
a26a20f docs(submission): sharpen Hacktoberfest story and add CPU constraint
afd9b8b feat(observability): add tracing, self-check, and verification action
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
origin [redacted-email]:Ekomjah/doxify.git (fetch)
origin [redacted-email]:Ekomjah/doxify.git (push)
$ git ls-remote --heads origin main
a26a20f75ca4e23991dae09b8ba56263e9544851 refs/heads/main
$ git show origin/main:submission.md | rg -n "weak CPU|almost kept me out|nearly|8 GB CPU laptop"
15:I almost skipped this challenge because my laptop has a weak CPU. Most AI demos I saw assumed a GPU. I did not want to enter a challenge I could not run. `gemma3:1b` changed that: a small open-weight model that runs locally on the machine I already have. That constraint shaped the build: a small model, bounded prompts, and a deterministic verifier to carry the trust work.
390:- **It runs on a normal laptop.** The same weak CPU that almost kept me out is enough for `gemma3:1b`, which is about 806 MB. After the model download, the default workflow is offline. No API key, no per-token bill.
418:The design target is a sub-10-minute local compile on an 8 GB CPU laptop, because that is the machine I have. The model is small, the prompts are bounded, and the expensive trust work is determinist
… [truncated 137 chars]
$ git status --short --branch
## main...origin/main
M .obsidian/workspace.json
$ gh run list --limit 3 2>&1 | sed -n '1,20p'
completed success docs(submission): sharpen Hacktoberfest story and add CPU constraint CI main push 40137693434 17s 2026-10-24T22:14:27Z
completed failure docs(submission): add Hacktoberfest Week 3 post CI main push 40122512889 12s 2026-10-24T18:50:02Z
completed failure feat(monitoring): add Sentry error monitoring with MIT license CI main push 40117647728 15s 2026-10-24T17:46:27Z
$ gh run list --limit 6 2>&1 | sed -n '1,20p'
failed to get runs: Get "https://api.github.com/repos/Ekomjah/doxify/actions/runs?per_page=6&exclude_pull_requests=true": net/http: TLS handshake timeout
$ gh run list --limit 5 2>&1 | head -10
error connecting to api.github.com
check your internet connection or https://githubstatus.com
Added the CPU constraint to the intro, tied it into Why Does Open Innovation Matter? and The Lazy Payoff, and fixed the line-369 typo. Verified 73 tests, Ruff, build, README self-audit, and self-check; the expected-failure case still exits 1. Pushed afd9b8b (code/observability) and a26a20f (submission); latest CI is green. .obsidian/workspace.json remains unstaged.
Added the poor-CPU backstory to the intro, laptop bullet, and design-target paragraph; fixed the case-study typo. All checks pass (73 tests, Ruff, build, self-audit), and latest CI succeeded. Pushed afd9b8b and a26a20f to main; .obsidian/workspace.json remains uncommitted.
Confirmed. Both commits are pushed, CI is green, and .obsidian/workspace.json remains intentionally unstaged.
Correct. The only remaining step is publishing submission.md on DEV with #devchallenge and #hf26challenge; the repo is clean except the intentionally uncommitted .obsidian/workspace.json.
Ready for DEV publishing; the repo is clean apart from the intentional .obsidian/workspace.json change.
I can’t publish to DEV from this environment. Create a new DEV post, paste submission.md, add #devchallenge and #hf26challenge in the tag field, then submit it to the Week 3 challenge. The repo is otherwise ready.
Proceed. Paste submission.md, keep the existing title, and add #devchallenge + #hf26challenge as DEV tags—not just body text. After publishing, spot-check the GitHub artifact links and confirm the post appears in the challenge feed.
plan how to host doxify so the people reading the blog can easily install the cli and test it out, preferably with curl. i am thinking of using raw.githubcontent.com/... .sh
$ sed -n '1,220p' pyproject.toml && printf '\n--- README install ---\n' && sed -n '1,100p' README.md
[project]
name = "doxify"
version = "0.1.0"
license = "MIT"
description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
readme = "README.md"
authors = [
{ name = "Ekomjah Edet", email = "[redacted-email]" }
]
requires-python = ">=3.10"
dependencies = [
"gitpython>=3.2.1",
"ollama>=0.6.3",
"pydantic>=2.14.1",
"rich>=15.0.0",
"sentry-sdk>=2.73.0",
"tomli>=2.5.0",
"typer>=0.27.3",
]
[project.scripts]
doxify = "doxify:main"
[build-system]
requires = ["uv_build>=0.13.1,<0.14.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff>=0.17.2",
]
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "UP", "B", "SIM", "RUF"]
ign
… [truncated 3050 chars]
$ sed -n '1,140p' .github/workflows/ci.yml && printf '\n--- git tags ---\n' && git tag -l && printf '\n--- remote ---\n' && git remote -v && printf '\n--- branch ---\n' && git status --short --branch
name: CI
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
test:
name: Lint, test, and build
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Test
run: uv run pytest -q
- name: Build package
run: uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-f
… [truncated 1165 chars]
$ ls -la && printf '\n--- packaging files ---\n' && glob=''; find . -maxdepth 2 -type f \( -name '*.sh' -o -name 'MANIFEST.in' -o -name 'Dockerfile' -o -name 'install*' \) -print
total 304
drwxrwxr-x 14 [user] [user] 4096 Oct 24 21:59 .
drwxrwxr-x 16 [user] [user] 4096 Oct 23 23:32 ..
drwxrwxr-x 7 [user] [user] 4096 Oct 24 23:23 .git
drwxrwxr-x 3 [user] [user] 4096 Oct 23 23:15 .github
-rw-rw-r-- 1 [user] [user] 158 Oct 23 23:21 .gitignore
drwxrwxr-x 2 [user] [user] 4096 Oct 22 17:40 .obsidian
drwxrwxr-x 3 [user] [user] 4096 Oct 23 21:08 .pytest_cache
-rw-rw-r-- 1 [user] [user] 5 Oct 22 22:27 .python-version
drwxrwxr-x 3 [user] [user] 4096 Oct 23 21:01 .ruff_cache
drwxrwxr-x 4 [user] [user] 4096 Oct 22 22:27 .venv
-rw-rw-r-- 1 [user] [user] 1069 Oct 24 18:45 LICENSE
-rw-rw-r-- 1 [user] [user] 96605 Oct 24 19:51 PLAN.md
-rw-rw-r-- 1 [user] [user] 4393 Oct 24 18:59 README.md
-rw-rw-r-- 1 [user] [user] 1964 Oct
… [truncated 561 chars]
$ curl -fsSL --max-time 15 https://pypi.org/pypi/doxify/json | python3 -c 'import json,sys; d=json.load(sys.stdin); i=d["info"]; print("name:",i["name"]); print("version:",i["version"]); print("summary:",i["summary"]); print("home:",i.get("home_page")); print("project_urls:",i.get("project_urls")); print("releases:", sorted(d["releases"], reverse=True)[:10])' 2>&1 || true
curl: (22) The requested URL returned error: 404
Traceback (most recent call last):
File "<string>", line 1, in <module>
import json,sys; d=json.load(sys.stdin); i=d["info"]; print("name:",i["name"]); print("version:",i["version"]); print("summary:",i["summary"]); print("home:",i.get("home_page")); print("project_urls:",i.get("project_urls")); print("releases:", sorted(d["releases"], reverse=True)[:10])
~~~~~~~~~^^^^^^^^^^^
File "/usr/lib/python3.14/json/__init__.py", line 298, in load
return loads(fp.read(),
cls=cls, object_hook=object_hook,
parse_float=parse_float, parse_int=parse_int,
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
File "/usr/lib/python3.14/json/__init__.py", line 352, in loads
re
… [truncated 455 chars]
$ curl -fsSL --max-time 15 https://api.github.com/repos/Ekomjah/doxify/releases | python3 -c 'import json,sys; d=json.load(sys.stdin); print("releases:", len(d)); [print(r.get("tag_name"), r.get("name"), r.get("draft"), r.get("prerelease")) for r in d[:5]]' 2>&1 || true
releases: 0
$ sed -n '1,80p' .gitignore && printf '\n--- dist ---\n' && ls -lh dist && printf '\n--- entrypoint ---\n' && rg -n "def main|__version__|importlib.metadata|Typer\(|version" src/doxify/__init__.py src/doxify/cli.py pyproject.toml | head -60
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
# Local tool caches
.pytest_cache/
.ruff_cache/
--- dist ---
total 128K
-rw-rw-r-- 1 [user] [user] 68K Oct 24 23:06 doxify-0.1.0-py3-none-any.whl
-rw-rw-r-- 1 [user] [user] 60K Oct 24 23:06 doxify-0.1.0.tar.gz
--- entrypoint ---
src/doxify/cli.py:53:app = typer.Typer(
src/doxify/cli.py:67:def _default_scout_output(package: str, from_version: str, to_version: str) -> Path:
src/doxify/cli.py:70: safe_from = re.sub(r"[^A-Za-z0-9_.-]+", "-", from_version).strip("-") or "from"
src/doxify/cli.py:71: safe_to = re.sub(r"[^A-Za-z0-9_.-]+", "-", to_version).strip("-") or "to"
src/doxify/cli.py:367: from_version: str = typer.Argument(..., metavar="FROM", help="Current version,
… [truncated 459 chars]
$ sed -n '1,80p' src/doxify/__init__.py && sed -n '45,85p' src/doxify/cli.py && sed -n '615,635p' src/doxify/cli.py && printf '\n--- uv version ---\n' && uv --version && printf '\n--- pipx ---\n' && command -v pipx || true
from .cli import main
__all__ = ["main"]
load_evidence_file,
readme_repo,
scan_only,
verify_session,
)
from .scout import scout_repo
from .selfcheck import SelfCheckError, run_self_check
app = typer.Typer(
no_args_is_help=True,
help=(
"Doxify compiles evidence-backed READMEs and portfolio case studies "
"from local git repositories, and scouts breaking dependency upgrades."
),
)
def _default_output(repo: Path) -> Path:
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
return Path.cwd() / "doxify-out" / f"{repo.name}-{stamp}"
def _default_scout_output(package: str, from_version: str, to_version: str) -> Path:
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
safe_package = re.sub(r"[^A-Za-z0-9_.-]+", "-", package).strip("-") or
… [truncated 1198 chars]
$ curl -fsSL --max-time 15 https://api.github.com/repos/Ekomjah/doxify | python3 -c 'import json,sys; d=json.load(sys.stdin); print({k:d.get(k) for k in ["full_name","default_branch","visibility","license","description","homepage","has_wiki","has_issues"]})' 2>&1 || true
{'full_name': 'Ekomjah/doxify', 'default_branch': 'main', 'visibility': 'public', 'license': {'key': 'mit', 'name': 'MIT License', 'spdx_id': 'MIT', 'url': 'https://api.github.com/licenses/mit', 'node_id': 'MDc6TGljZW5zZTEz'}, 'description': 'Compile evidence-backed READMEs and portfolio case studies from local git repositories', 'homepage': None, 'has_wiki': True, 'has_issues': True}
$ rg -n "doxify (scan|verify|readme|compile|doctor|self-check)|uvx|pipx|install" README.md submission.md docs -g '*.md' | head -80
README.md:22:Or install the package and run `doxify` directly.
README.md:24:<!-- doxify-attested: installation instructions -->
README.md:34:doxify doctor
README.md:35:doxify readme /path/to/repo
README.md:40:## Cloud mode (no local Ollama install)
README.md:47:doxify doctor --provider cloud
README.md:48:doxify readme /path/to/repo --provider cloud
README.md:73:doxify doctor [--provider local|cloud]
README.md:74:doxify scan REPO
README.md:75:doxify compile REPO
README.md:76:doxify readme REPO [--output PATH]
README.md:78:doxify verify SESSION [--artifact PATH] [--fail-on STATUSES]
README.md:85:`doxify verify` exits non-zero when a claim needs evidence or is rejected. Use
submission.md:50:`doxify readme` runs the same compile, then renders a README from verified and attested material.
submi
… [truncated 1962 chars]
$ uv tool install --help | sed -n '1,220p'
Install commands provided by a Python package
Usage: uv tool install [OPTIONS] <PACKAGE>
Arguments:
<PACKAGE> The package to install commands from
Options:
-w, --with <WITH>
Include the following additional requirements
--with-requirements <WITH_REQUIREMENTS>
Run with the packages listed in the given files
-e, --editable
Install the target package in editable mode, such that changes in the package's source
directory are reflected without reinstallation
--with-editable <WITH_EDITABLE>
Include the given packages in editable mode
--with-executables-from <WITH_EXECUTABLES_FROM>
Install executables from the following packages
-c, --constraints <CONSTRAINTS>
Constrain versions using the given requ
… [truncated 9987 chars]
$ uv python install --help | sed -n '1,160p'; printf '\n--- tool dir ---\n'; uv tool dir; printf '\n--- managed pythons ---\n'; uv python list | head -20
Download and install Python versions
Usage: uv python install [OPTIONS] [TARGETS]...
Arguments:
[TARGETS]... The Python version(s) to install [env: UV_PYTHON=]
Options:
-i, --install-dir <INSTALL_DIR>
The directory to store the Python installation in [env: UV_PYTHON_INSTALL_DIR=]
--no-bin
Do not install a Python executable into the `bin` directory
--no-registry
Do not register the Python installation in the Windows registry
--mirror <MIRROR>
Set the URL to use as the source for downloading Python installations
--pypy-mirror <PYPY_MIRROR>
Set the URL to use as the source for downloading PyPy installations
--graalpy-mirror <GRAALPY_MIRROR>
Set the URL to use as the source for downloading GraalPy
… [truncated 4086 chars]
$ curl -fsSL --max-time 15 https://docs.astral.sh/uv/reference/cli/ | rg -o 'uv tool install[^<]{0,120}' | head -10 2>/dev/null || true
uv tool install
uv tool install
uv tool install
uv tool install
uv tool install
uv tool install [OPTIONS] <PACKAGE>
uv tool install
$ git status --short --branch && printf '\n--- submission diff ---\n' && git diff -- submission.md | sed -n '1,120p' && printf '\n--- show later ---\n' && git show --stat --oneline HEAD
## main...origin/main
M .obsidian/workspace.json
M submission.md
?? .cursor/
?? .entire/
?? .opencode/
--- submission diff ---
diff --git a/submission.md b/submission.md
index 963ddec..a6d42a8 100644
--- a/submission.md
+++ b/submission.md
@@ -1,23 +1,31 @@
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
-# I built a local Gemma tool that writes the README I keep avoiding
+# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
-Every project ends the same way for me. The last commit lands, my bra
… [truncated 3277 chars]
$ git diff --stat && git log --oneline -4
.obsidian/workspace.json | 12 ++++++------
submission.md | 16 ++++++++++++----
2 files changed, 18 insertions(+), 10 deletions(-)
a26a20f docs(submission): sharpen Hacktoberfest story and add CPU constraint
afd9b8b feat(observability): add tracing, self-check, and verification action
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
$ git show HEAD:submission.md | rg -n "weak CPU|almost kept me out|The a local|The run against" || true; printf '\n--- working ---\n'; rg -n "weak CPU|almost kept me out|The a local|The run against" submission.md || true
15:I almost skipped this challenge because my laptop has a weak CPU. Most AI demos I saw assumed a GPU. I did not want to enter a challenge I could not run. `gemma3:1b` changed that: a small open-weight model that runs locally on the machine I already have. That constraint shaped the build: a small model, bounded prompts, and a deterministic verifier to carry the trust work.
371:**The first case study was too confident.** The run against the local demo repo left 34 of its 47 claims needing evidence. That is exactly the kind of fluent, vague writing I was trying to avoid. The fix was not a longer prompt. It was smaller evidence sets, deterministic templates for structural sections, and a review queue that shows every unresolved claim.
390:- **It runs on a normal laptop.** The same weak CPU
… [truncated 1034 chars]
$ unzip -l dist/doxify-0.1.0-py3-none-any.whl | sed -n '1,80p'; printf '\n--- metadata ---\n'; unzip -p dist/doxify-0.1.0-py3-none-any.whl 'doxify-0.1.0.dist-info/METADATA' | sed -n '1,80p'
Archive: dist/doxify-0.1.0-py3-none-any.whl
Length Date Time Name
--------- ---------- ----- ----
0 1980-01-01 00:00 doxify/
42 1980-01-01 00:00 doxify/__init__.py
61 1980-01-01 00:00 doxify/__main__.py
8118 1980-01-01 00:00 doxify/audit.py
3742 1980-01-01 00:00 doxify/claims.py
20881 1980-01-01 00:00 doxify/cli.py
5749 1980-01-01 00:00 doxify/console.py
6039 1980-01-01 00:00 doxify/defense.py
566 1980-01-01 00:00 doxify/errors.py
17985 1980-01-01 00:00 doxify/evidence.py
4913 1980-01-01 00:00 doxify/exporter.py
4339 1980-01-01 00:00 doxify/generator.py
5618 1980-01-01 00:00 doxify/git_repo.py
0 1980-01-01 00:00 doxify/interview.py
3076 1980-01-01
… [truncated 3452 chars]
$ git show HEAD:pyproject.toml | sed -n '1,80p'; printf '\n--- current ---\n'; sed -n '1,80p' pyproject.toml
[project]
name = "doxify"
version = "0.1.0"
license = "MIT"
description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
readme = "README.md"
authors = [
{ name = "Ekomjah Edet", email = "[redacted-email]" }
]
requires-python = ">=3.10"
dependencies = [
"gitpython>=3.2.1",
"ollama>=0.6.3",
"pydantic>=2.14.1",
"rich>=15.0.0",
"sentry-sdk>=2.73.0",
"tomli>=2.5.0",
"typer>=0.27.3",
]
[project.scripts]
doxify = "doxify:main"
[build-system]
requires = ["uv_build>=0.13.1,<0.14.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff>=0.17.2",
]
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "UP", "B", "SIM", "RUF"]
ign
… [truncated 847 chars]
$ curl -fsSL --max-time 15 https://raw.githubusercontent.com/Ekomjah/doxify/main/README.md >/dev/null && echo "raw main OK"; curl -fsSL --max-time 15 https://raw.githubusercontent.com/Ekomjah/doxify/main/install.sh >/dev/null 2>&1 && echo "install.sh exists" || echo "install.sh missing (expected)"
curl: (28) Resolving timed out after 15001 milliseconds
<shell_metadata>
shell tool terminated command after exceeding timeout 30000 ms. If this command is expected to take longer and is not waiting for interactive input, retry with a larger timeout value in milliseconds.
</shell_metadata>
continue
Recommendation
Ship two layers:
-
Publish the wheel/sdist to PyPI as
doxify— the canonical install path. -
Add
install.shat the repo root — the curl-friendly bootstrap for the blog.
The raw URL is fine, but use raw.githubusercontent.com and pin it to a release tag:
curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
sh /tmp/doxify-install.sh
Avoid raw.githubcontent.com; the correct host is raw.githubusercontent.com. Prefer the download-then-run form over curl | sh, because a failed download can otherwise look like a successful install.
Current Findings
-
uv buildalready producesdoxify-0.1.0-py3-none-any.whland an sdist; the wheel is pure Python and contains thedoxifyconsole script. - No git tags or GitHub Releases exist yet.
- PyPI returns 404 for
doxify, so the name appears unclaimed — reserve it before publishing. -
README.mdonly documentsuv sync; there is no one-line install. - The CLI has no
--versionflag, which the installer and blog should use as a smoke test. - CI builds the package but does not publish it.
- The working tree is dirty:
submission.mdhas uncommitted edits,.obsidian/is modified, and.cursor/,.entire/,.opencode/are untracked. A release tag must come from a clean, deliberate commit.
Target User Experience
curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
sh /tmp/doxify-install.sh
doxify --version
doxify self-check --json
Then a no-model test:
git clone --depth 1 https://github.com/Ekomjah/doxify /tmp/doxify-demo
doxify scan /tmp/doxify-demo
And the full local test:
ollama serve
ollama pull gemma3:1b
doxify readme /tmp/doxify-demo
Manual alternatives to document:
uv tool install doxify
pipx install doxify
uv tool upgrade doxify
uv tool uninstall doxify
Windows can use WSL initially; add install.ps1 after launch if needed.
Implementation Plan
1. Package readiness
- Add
doxify --versionusing package metadata, with a test. - Add
[project.urls]for Homepage, Repository, Issues, and Changelog. - Add PyPI classifiers and keywords.
- Verify the wheel installs cleanly in a fresh environment:
uv builduv tool install --force dist/doxify-0.1.0-py3-none-any.whldoxify --versiondoxify self-check --jsondoxify scan /tmp/doxify-demo
- Keep
dist/out of git.
2. Installer
Create a small POSIX install.sh that:
- Uses
set -euo pipefail. - Supports Linux, macOS, and WSL; rejects native Windows with a PowerShell/WSL message.
- Requires
curl; fails with a clear message if missing. - Installs
uvif absent by downloading the official installer to a temporary file, not piping it directly. - Installs Doxify per-user with:
-
uv tool install --force "doxify==${DOXIFY_VERSION}"from PyPI by default. -
DOXIFY_VERSIONoverride for pinning. - A
DOXIFY_WHEELtest hook for CI/local wheel testing.
-
- Does not use
sudo. - Does not install Ollama or download a model automatically.
- Checks for
gitand warns that it is required for scanning. - Runs
doxify --versionanddoxify self-check --jsonafter installation. - Prints PATH guidance for
~/.local/bin; does not edit shell rc files by default. - Prints the next commands:
doxify self-checkdoxify scan /path/to/repo-
ollama pull gemma3:1bfor full README generation
- Is idempotent and safe to rerun.
Keep the script short enough that readers can inspect it. Add sh -n install.sh and ShellCheck to CI.
3. Publishing
Add .github/workflows/release.yml triggered by v* tags:
- Check out the tag.
- Run tests and Ruff.
- Build wheel and sdist.
- Install the built wheel in a temporary environment and run the smoke tests.
- Publish to PyPI with Trusted Publishing (
id-token: write,environment: pypi) usingpypa/gh-action-pypi-publish. - Create a GitHub Release with the wheel, sdist,
SHA256SUMS, andinstall.sh.
For speed, the first release can be manual:
uv build
uv publish
gh release create v0.1.0 dist/* --generate-notes
Then move to Trusted Publishing so no long-lived PyPI token is stored.
Use a tag URL for the blog:
https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh
Keep main/install.sh as the latest-version convenience URL, but do not put the mutable URL in the published article.
4. Documentation and article
Update README.md:
- Quick curl install for Linux/macOS.
-
uv tool install doxifyandpipx install doxify. - Upgrade/uninstall commands.
- “Try it without a model” commands.
- Full Ollama setup.
- Windows note.
- Link to the release and PyPI page.
Update submission.md under Demo:
- Add the curl install block.
- Add a 30-second smoke test (
doxify --version,doxify self-check). - Keep
uv syncas the contributor/source path. - Add a short “no Ollama required for scan/verify” note.
The article’s existing “no hosted demo” claim remains accurate; the install script is the distribution path, not a hosted demo.
5. Validation
Before publishing the article:
- Test the installer in a clean
ubuntu:24.04container with no Python or uv preinstalled. - Test on macOS via CI.
- Test pinned tag install and
maininstall. - Test upgrade and uninstall.
- Verify the raw URL returns 200 and the script hash matches.
- Test from a clean
$HOME. - Confirm Sentry remains disabled without
SENTRY_DSN. - Confirm the installer does not touch shell configs or install the model.
- Verify PyPI page, GitHub Release assets, and README links.
Fallback If PyPI Is a Blocker
If you do not want to create a PyPI account before the deadline:
- Create a GitHub Release with the wheel and sdist.
- Make
install.shdownload the pinned wheel to a temporary file. - Verify its SHA256 against
SHA256SUMS. - Run `uv tool install --force … [truncated 663 chars]
create the install.sh script. add version tags and other neccessary content. stick with install.sh as the method for installation for now
$ sed -n '1,110p' src/doxify/cli.py && sed -n '610,635p' src/doxify/cli.py
from __future__ import annotations
import logging
import re
import shutil
import sys
from datetime import datetime
from pathlib import Path
import typer
from rich.table import Table
from .audit import (
ArtifactReport,
audit_artifact,
failing_claims,
parse_fail_on,
)
from .console import (
console,
error,
print_compile_summary,
print_readme_summary,
print_scan_summary,
print_scout_summary,
stage_progress,
)
from .errors import DoxifyError
from .log import configure_logging, log_event
from .models import ClaimStatus, CompileSummary, ScanResult
from .monitoring import (
begin_stage,
command_transaction,
cron_monitor,
flush_monitoring,
init_monitoring,
monitoring_status,
span,
)
from .ollama_client import DEFAULT_MODEL
… [truncated 3003 chars]
$ ls tests && sed -n '1,120p' tests/test_ollama_client.py && sed -n '1,80p' tests/conftest.py
__pycache__
conftest.py
test_audit.py
test_logging.py
test_monitoring.py
test_ollama_client.py
test_readme.py
test_redaction.py
test_scout.py
test_self_audit.py
test_selfcheck.py
test_verify_cli.py
from __future__ import annotations
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
from ollama import ResponseError
from typer.testing import CliRunner
import doxify.cli as cli
import doxify.ollama_client as ollama_client
from doxify.errors import DoxifyError, ModelError
from doxify.ollama_client import OllamaClient
def _use_temp_env(monkeypatch: pytest.MonkeyPatch) -> None:
for name in (
"DOXIFY_PROVIDER",
"DOXIFY_MODEL",
"DOXIFY_OLLAMA_HOST",
"OLLAMA_API_KEY",
):
monkeypatch.delenv(name, raisin
… [truncated 3287 chars]
$ sed -n '1,50p' README.md && sed -n '85,130p' submission.md && printf '\n--- workflow tail ---\n' && sed -n '1,120p' .github/workflows/ci.yml
# Doxify
Doxify compiles evidence-backed READMEs and portfolio case studies from local
git repositories. It also scouts breaking dependency upgrades against your
code.
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
<!-- doxify-attested: project description and positioning -->
## Install
Requires Python 3.10+ and git.
```bash
uv sync
uv run doxify --help
```
Or install the package and run `doxify` directly.
<!-- doxify-attested: installation instructions -->
## Local mode (default)
Local mode uses Ollama and `gemma3:1b`. Nothing about the repository leaves
your machine.
```bash
ollama serve
ollama pull gemma3:1b
doxify doctor
do
… [truncated 4434 chars]
$ uv tool dir --help 2>&1 | sed -n '1,80p'; printf '\n--- bin ---\n'; uv tool dir --bin 2>&1 || true
Show the path to the uv tools directory
Usage: uv tool dir [OPTIONS]
Options:
--bin Show the directory into which `uv tool` will install executables.
Cache options:
-n, --no-cache Avoid reading from or writing to the cache, instead using a temporary
directory for the duration of the operation [env: UV_NO_CACHE=]
--cache-dir <CACHE_DIR> Path to the cache directory [env: UV_CACHE_DIR=]
Python options:
--managed-python Require use of uv-managed Python versions [env: UV_MANAGED_PYTHON=]
--no-managed-python Disable use of uv-managed Python versions [env: UV_NO_MANAGED_PYTHON=]
--no-python-downloads Disable automatic downloads of Python. [env:
"UV_PYTHON_DOWNLOADS=never"]
Gl
… [truncated 1191 chars]
clear the git tree; commit all changes by batches
$ git status --short --branch && printf '\n--- tracked local dirs ---\n' && git ls-files .obsidian .cursor .entire .opencode && printf '\n--- all status ---\n' && git status --porcelain=v1 -uall
## main...origin/main
M .obsidian/workspace.json
M submission.md
?? .cursor/
?? .entire/
?? .opencode/
--- tracked local dirs ---
.obsidian/app.json
.obsidian/appearance.json
.obsidian/core-plugins.json
.obsidian/workspace.json
--- all status ---
M .obsidian/workspace.json
M submission.md
?? .cursor/hooks.json
?? .entire/.gitignore
?? .entire/settings.json
?? .opencode/plugins/entire.ts
$ find .cursor .entire .opencode -maxdepth 2 -type f 2>/dev/null | sed -n '1,80p'; printf '\n--- obsidian files ---\n'; find .obsidian -maxdepth 2 -type f | sed -n '1,40p'
.cursor/hooks.json
.entire/.gitignore
.entire/settings.json
.opencode/plugins/entire.ts
--- obsidian files ---
.obsidian/appearance.json
.obsidian/workspace.json
.obsidian/app.json
.obsidian/core-plugins.json
$ git diff --stat && printf '\n--- submission diff summary ---\n' && git diff -- submission.md | sed -n '1,80p'
.obsidian/workspace.json | 12 ++++++------
submission.md | 23 ++++++++++++++---------
2 files changed, 20 insertions(+), 15 deletions(-)
--- submission diff summary ---
diff --git a/submission.md b/submission.md
index 963ddec..eef69a1 100644
--- a/submission.md
+++ b/submission.md
@@ -1,23 +1,31 @@
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
-# I built a local Gemma tool that writes the README I keep avoiding
+# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
-Every project e
… [truncated 5189 chars]
$ git status --short --branch && printf '\n--- untracked dirs ---\n' && find .cursor .entire .opencode -maxdepth 3 -type f -printf '%p %s bytes\n' 2>/dev/null | head -80
## main...origin/main
M .obsidian/workspace.json
M submission.md
?? .cursor/
?? .entire/
?? .opencode/
--- untracked dirs ---
.cursor/hooks.json 1216 bytes
.entire/.gitignore 58 bytes
.entire/settings.json 94 bytes
.opencode/plugins/entire.ts 26120 bytes
$ git diff -- submission.md | sed -n '1,220p'
diff --git a/submission.md b/submission.md
index 963ddec..eef69a1 100644
--- a/submission.md
+++ b/submission.md
@@ -1,23 +1,31 @@
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
-# I built a local Gemma tool that writes the README I keep avoiding
+# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
-Every project ends the same way for me. The last commit lands, my brain shuts off, and the README is still empty. That page is the first thing an interviewer, a contributor, or another developer sees
… [truncated 5005 chars]
$ git diff -- .obsidian/workspace.json | sed -n '1,120p'
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index ca57edf..39f63bd 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
- "file": "PLAN.md",
+ "file": "submission.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
- "title": "PLAN"
+ "title": "submission"
}
}
]
@@ -55,7 +55,7 @@
"state": {
"type": "search",
"state": {
- "query": "happens",
+ "query": "reverse",
"matchingCase": true,
… [truncated 548 chars]
$ printf '%s\n' '--- .cursor/hooks.json ---'; sed -n '1,120p' .cursor/hooks.json; printf '%s\n' '--- .entire/settings.json ---'; sed -n '1,120p' .entire/settings.json; printf '%s\n' '--- .entire/.gitignore ---'; sed -n '1,80p' .entire/.gitignore; printf '%s\n' '--- .opencode/plugins/entire.ts ---'; sed -n '1,160p' .opencode/plugins/entire.ts
--- .cursor/hooks.json ---
{
"hooks": {
"beforeSubmitPrompt": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor before-submit-prompt'"
}
],
"preCompact": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor pre-compact'"
}
],
"sessionEnd": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor session-end'"
}
],
"sessionStart": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor session-start'"
}
],
"stop": [
{
"command": "sh -c 'if ! command -v entire >
… [truncated 8737 chars]
$ git diff -- .obsidian/workspace.json | sed -n '1,100p'; printf '\n--- submission tail ---\n'; sed -n '405,435p' submission.md
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index ca57edf..39f63bd 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
- "file": "PLAN.md",
+ "file": "submission.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
- "title": "PLAN"
+ "title": "submission"
}
}
]
@@ -55,7 +55,7 @@
"state": {
"type": "search",
"state": {
- "query": "happens",
+ "query": "reverse",
"matchingCase": true,
… [truncated 3805 chars]
$ sed -n '1,80p' .gitignore; printf '\n--- pyproject ---\n'; sed -n '1,60p' pyproject.toml
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
# Local tool caches
.pytest_cache/
.ruff_cache/
--- pyproject ---
[project]
name = "doxify"
version = "0.1.0"
license = "MIT"
description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
readme = "README.md"
authors = [
{ name = "Ekomjah Edet", email = "[redacted-email]" }
]
requires-python = ">=3.10"
dependencies = [
"gitpython>=3.2.1",
"ollama>=0.6.3",
"pydantic>=2.14.1",
"rich>=15.0.0",
"sentry-sdk>=2.73.0",
"tomli>=2.5.0",
"typer>=0.27.3",
]
[project.scripts]
doxify = "doxify:main"
[build-system]
requires = ["uv_build>=0.13.1,<0.14.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"p
… [truncated 192 chars]
$ cat .entire/settings.json .entire/.gitignore .cursor/hooks.json; printf '\n--- opencode plugin head ---\n'; sed -n '1,80p' .opencode/plugins/entire.ts
{
"enabled": true,
"checkpoints": {
"primary": {
"type": "git-refs"
}
}
}
tmp/
settings.local.json
metadata/
logs/
redactors/local/
{
"hooks": {
"beforeSubmitPrompt": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor before-submit-prompt'"
}
],
"preCompact": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor pre-compact'"
}
],
"sessionEnd": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor session-end'"
}
],
"sessionStart": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec
… [truncated 5592 chars]
$ rg -n "token|secret|api[_-]?key|password|ghp_|sk-" .cursor .entire .opencode | head -40 || true
.opencode/plugins/entire.ts:17: // In-memory store for message metadata (role, tokens, etc.)
.opencode/plugins/entire.ts:459: // Store message metadata (role, time, tokens, etc.)
$ git log --oneline -6 && printf '\n--- diff code ---\n' && git diff --stat HEAD
a26a20f docs(submission): sharpen Hacktoberfest story and add CPU constraint
afd9b8b feat(observability): add tracing, self-check, and verification action
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
d5c94b8 feat(scout): add evidence-backed upgrade impact analysis
f4b1d79 chore(demo): add precomputed nextjs-dashboard compile artifacts
--- diff code ---
.obsidian/workspace.json | 12 ++++++------
submission.md | 23 ++++++++++++++---------
2 files changed, 20 insertions(+), 15 deletions(-)
$ cat .entire/settings.json; printf '\n--- entire gitignore ---\n'; cat .entire/.gitignore; printf '\n--- cursor hooks ---\n'; cat .cursor/hooks.json
{
"enabled": true,
"checkpoints": {
"primary": {
"type": "git-refs"
}
}
}
--- entire gitignore ---
tmp/
settings.local.json
metadata/
logs/
redactors/local/
--- cursor hooks ---
{
"hooks": {
"beforeSubmitPrompt": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor before-submit-prompt'"
}
],
"preCompact": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor pre-compact'"
}
],
"sessionEnd": [
{
"command": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks cursor session-end'"
}
],
"sessionStart": [
{
"command": "sh -c 'if ! command
… [truncated 616 chars]
$ rg -n -i "(token|secret|api[_-]?key|password|ghp_|sk-|xox|AKIA|/home/|/Users/)" .cursor .entire .opencode | head -40 || true
.opencode/plugins/entire.ts:17: // In-memory store for message metadata (role, tokens, etc.)
.opencode/plugins/entire.ts:459: // Store message metadata (role, time, tokens, etc.)
$ wc -l .opencode/plugins/entire.ts .cursor/hooks.json .entire/settings.json .obsidian/workspace.json
585 .opencode/plugins/entire.ts
40 .cursor/hooks.json
8 .entire/settings.json
206 .obsidian/workspace.json
839 total
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
import logging
import re
import shutil
import sys
from datetime import datetime
from pathlib import Path
new:
import logging
import re
import shutil
import sys
from datetime import datetime
from importlib.metadata import PackageNotFoundError, version as package_version
from pathlib import Path
Edit applied successfully.
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
app = typer.Typer(
no_args_is_help=True,
help=(
"Doxify compiles evidence-backed READMEs and portfolio case studies "
"from local git repositories, and scouts breaking dependen
… [truncated 65 chars]
new:
app = typer.Typer(
no_args_is_help=True,
help=(
"Doxify compiles evidence-backed READMEs and portfolio case studies "
"from local git repositories, and scouts breaking dependency upgrades."
),
)
def _version_callback(value: bool) -> None:
if not value:
return
try:
current = package_version("doxify")
except PackageNotFoundError:
curr
… [truncated 430 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/install.sh
content:
#!/bin/sh
set -eu
REPO="Ekomjah/doxify"
DEFAULT_VERSION="0.1.0"
UV_INSTALL_URL="https://astral.sh/uv/install.sh"
usage() {
cat <<'EOF'
Doxify installer
Usage:
sh install.sh [--version VERSION] [--help]
Environment:
DOXIFY_VERSION Release to install (default: 0.1.0)
DOXIFY_SOURCE_DIR Install from a local source tree (maintainer testing)
The installer uses uv to place Doxify in an isolated per-user environment.
It does not use sudo, install Ollama, or download a model.
EOF
}
log() {
printf '%s\n' "doxify-install: $*"
}
fail() {
printf '%s\n' "doxify-install: error: $*" >&2
exit 1
}
VERSION="${DOXIFY_VERSION:-$DEFAULT_VERSION}"
while [ "$#" -gt 0 ]; do
ca
… [truncated 3324 chars]
Wrote file successfully.
file: ~/Desktop/repos/doxify/tests/test_cli_version.py
content:
from __future__ import annotations
import re
from typer.testing import CliRunner
from doxify.cli import app
def test_version_flag_prints_installed_version() -> None:
result = CliRunner().invoke(app, ["--version"])
assert result.exit_code == 0
assert re.fullmatch(r"\d+\.\d+\.\d+", result.output.strip())
Wrote file successfully.
file: ~/Desktop/repos/doxify/install.sh
content:
#!/bin/sh
set -eu
REPO="Ekomjah/doxify"
DEFAULT_VERSION="0.1.0"
UV_INSTALL_URL="https://astral.sh/uv/install.sh"
usage() {
cat <<'EOF'
Doxify installer
Usage:
sh install.sh [--version VERSION] [--help]
Options:
--version VERSION Doxify release to install (default: 0.1.0)
--help Show this help
Environment:
DOXIFY_VERSION Doxify release to install
DOXIFY_SOURCE_DIR Install from a local source tree (maintainer testing)
UV_TOOL_DIR Override uv's tool environment directory
UV_TOOL_BIN_DIR Override uv's executable directory
The installer uses uv to create an isolated per-user install.
It does not use sudo, install Ollama, or download a mode
… [truncated 3453 chars]
Wrote file successfully.
file: ~/Desktop/repos/doxify/pyproject.toml
old:
description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
readme = "README.md"
authors = [
{ name = "Ekomjah Edet", email = "[redacted-email]" }
]
… [truncated 27 chars]
new:
description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
readme = "README.md"
authors = [
{ name = "Ekomjah Edet", email = "[redacted-email]" }
]
keywords = ["cli", "documentation", "gemma", "git", "ollama", "readme"]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Developers",
… [truncated 163 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/pyproject.toml
old:
[project.scripts]
doxify = "doxify:main"
[build-system]
new:
[project.scripts]
doxify = "doxify:main"
[project.urls]
Homepage = "https://github.com/Ekomjah/doxify"
Repository = "https://github.com/Ekomjah/doxify"
Issues = "https://github.com/Ekomjah/doxify/issues"
Changelog = "https://github.com/Ekomjah/doxify/releases"
[build-system]
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
## Install
Requires Python 3.10+ and git.
```bash
uv sync
uv run doxify --help
```
Or install the package and run `doxify` directly.
<!-- doxify-attested: installation instructions -->
new:
## Install
Linux, macOS, and WSL:
```bash
curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
sh /tmp/doxify-install.sh
```
The installer requires `curl` and `git`. It installs `uv` if needed, places
Doxify in an isolated per-user environment, and runs `doxify self-check`. It
does not use `sudo`, install Ollama, or download a model.
Verify th
… [truncated 317 chars]
Edit applied successfully.
$ sh -n install.sh && echo 'syntax ok' && rm -rf /tmp/doxify-tool /tmp/doxify-bin && mkdir -p /tmp/doxify-tool /tmp/doxify-bin && UV_TOOL_DIR=/tmp/doxify-tool UV_TOOL_BIN_DIR=/tmp/doxify-bin DOXIFY_SOURCE_DIR="$PWD" sh install.sh
syntax ok
doxify-install: Installing from local source: ~/Desktop/repos/doxify
doxify-install: Installing Doxify 0.1.0 with uv...
Resolved 26 packages in 7.72s
Building doxify @ file://~/Desktop/repos/doxify
Built doxify @ file://~/Desktop/repos/doxify
Downloading pydantic-core (2.1MiB)
Downloaded pydantic-core
Prepared 4 packages in 5.68s
warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
If the cache and target directories are on different filesystems, hardlinking may not be supported.
If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 26 packages in 394ms
+ annotated-doc==0.0.5
+ annotated-types==0.8.0
+ an
… [truncated 991 chars]
$ uv run pytest -q tests/test_cli_version.py && uv run ruff check src/doxify/cli.py tests/test_cli_version.py
. [100%]
1 passed in 0.15s
Building doxify @ file://~/Desktop/repos/doxify
Built doxify @ file://~/Desktop/repos/doxify
Uninstalled 1 package in 3ms
Installed 1 package in 24ms
I001 [*] Import block is un-sorted or un-formatted
--> src/doxify/cli.py:1:1
|
1 | / from __future__ import annotations
2 | |
3 | | import logging
4 | | import re
5 | | import shutil
6 | | import sys
7 | | from datetime import datetime
8 | | from importlib.metadata import PackageNotFoundError, version as package_version
9 | | from pathlib import Path
10 | |
11 | | import typer
12 | | from rich.table import Table
13 | |
14 | | from .audit import (
15 | | ArtifactReport,
16 | | audit_artifact,
… [truncated 1509 chars]
file: ~/Desktop/repos/doxify/src/doxify/cli.py
old:
from datetime import datetime
from importlib.metadata import PackageNotFoundError, version as package_version
from pathlib import Path
new:
from datetime import datetime
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as package_version
from pathlib import Path
Edit applied successfully.
$ ls -la /tmp/doxify-tool /tmp/doxify-bin 2>/dev/null || true; /tmp/doxify-bin/doxify --version 2>/dev/null || true
(no output)
$ uv run pytest -q tests/test_cli_version.py && uv run ruff check src/doxify/cli.py tests/test_cli_version.py
. [100%]
1 passed in 0.03s
All checks passed!
$ sh -n install.sh && echo 'syntax ok'
syntax ok
$ rm -rf /tmp/doxify-tool /tmp/doxify-bin && mkdir -p /tmp/doxify-tool /tmp/doxify-bin && UV_TOOL_DIR=/tmp/doxify-tool UV_TOOL_BIN_DIR=/tmp/doxify-bin DOXIFY_SOURCE_DIR="$PWD" sh install.sh
doxify-install: Installing from local source: ~/Desktop/repos/doxify
doxify-install: Installing Doxify 0.1.0 with uv...
Resolved 26 packages in 20ms
Building doxify @ file://~/Desktop/repos/doxify
Built doxify @ file://~/Desktop/repos/doxify
Prepared 1 package in 28ms
warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
If the cache and target directories are on different filesystems, hardlinking may not be supported.
If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 26 packages in 53ms
+ annotated-doc==0.0.5
+ annotated-types==0.8.0
+ anyio==4.15.1
+ certifi==2026.10.23
+ doxify==0.1.0 (from file:///home/ekom
… [truncated 916 chars]
$ rm -rf /tmp/doxify-tool /tmp/doxify-bin && mkdir -p /tmp/doxify-tool /tmp/doxify-bin && timeout 90 env UV_TOOL_DIR=/tmp/doxify-tool UV_TOOL_BIN_DIR=/tmp/doxify-bin uv tool install --force --python 3.12 "$PWD" 2>&1 | tail -40; echo "exit=${pipestatus[1]:-$?}"
Resolved 26 packages in 100ms
Building doxify @ file://~/Desktop/repos/doxify
Built doxify @ file://~/Desktop/repos/doxify
Prepared 1 package in 92ms
warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
If the cache and target directories are on different filesystems, hardlinking may not be supported.
If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 26 packages in 220ms
+ annotated-doc==0.0.5
+ annotated-types==0.8.0
+ anyio==4.15.1
+ certifi==2026.10.23
+ doxify==0.1.0 (from file://~/Desktop/repos/doxify)
+ gitdb==4.0.12
+ gitpython==3.2.1
+ h11==0.16.0
+ httpcore==1.0.9
+ httpx==0.28.1
+ idna==3.20
+
… [truncated 484 chars]
[session context compacted]
Objective
- Create a curl-friendly
install.shas Doxify’s sole installation method for now, add version tagging/install content, and clear the git tree by committing all pending work in logical batches.
Important Details
- Latest user directives: create
install.sh; add version tags and necessary content; stick withinstall.shfor now, not PyPI; clear the git tree; commit all changes by batches. - Target install UX:
curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh && sh /tmp/doxify-install.sh; correct host israw.githubusercontent.com, notraw.githubcontent.com. Prefer download-then-run over blind| sh. - Planned
install.sh: POSIXsh, Linux/macOS/WSL, reject native Windows,set -eu, bootstrapuvif absent, defaultDOXIFY_VERSION=0.1.0, supportDOXIFY_SOURCE_DIRfor local/CI testing, downloadhttps://github.com/Ekomjah/doxify/archive/refs/tags/v0.1.0.tar.gz, install withuv tool install --force --python 3.12 "$source_dir", nosudo, no Ollama/model download, rundoxify --versionanddoxify self-check --json, then print PATH/next steps. -
self-checkis the smoke test, notdoctor, becausedoctorchecks Ollama. - Verified
uv tool dir --bin->~/.local/bin;uv tool installaccepts a local source directory. - Add
doxify --version/-V; currently missing. Use package metadata with fallback0.1.0; add a test. - Package:
doxify==0.1.0, pure Python wheeldoxify-0.1.0-py3-none-any.whl, entry pointdoxify = "doxify:main", requires Python >=3.10.uv buildworks. - No PyPI publishing for now:
https://pypi.org/pypi/doxify/jsonreturned 404; name appears unclaimed but deferred. Do not advertiseuv tool install doxifyfrom PyPI yet. - No git tags or GitHub releases exist;
git tag -lis empty. The source tarball/raw installer URL only works after pushingv0.1.0; GitHub Release is optional because tag archives work. - Default version is embedded in
install.sh; bump it each release or passDOXIFY_VERSION. - Current HEAD is
a26a20f;mainis pushed. Latest CI fora26a20fsucceeded, but working-tree edits after that are untested. - Dirty state:
submission.mdmodified;.obsidian/workspace.jsonmodified; untracked.cursor/hooks.json,.entire/.gitignore,.entire/settings.json,.opencode/plugins/entire.ts;dist/is ignored. - Committed article title:
# I built a local Gemma tool that writes the README I keep avoiding. Working tree has newer uncommitted title:# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.plus a new TLDR/intro. Preserve user edits. - Working-tree
submission.mdhas typocontributor, another developer ,and duplicated Lazy Payoff/design-target text; clean before committing. Keep 20 balanced code blocks. - The new title’s “47 claims / 13 through” is consistent with
13 verified / 34 needs evidence / 0 rejected. - Article must retain section order:
What I Built,Demo,Code,How I Built It,Why Does Open Innovation Matter?,My Agent Session,The Lazy Payoff,Prize Categories; tags#devchallengeand#hf26challenge. -
.entireis Entire CLI generated withgit-refscheckpoints;.cursor/hooks.jsonand.opencode/plugins/entire.tsare generated agent hooks. Likely relevant to the Entire category, but decide whether to commit or ignore. - Prior “do not commit
.obsidian/workspace.json” guidance conflicts with latest “clear git tree”; do not silently discard it. It is tracked and modified—commit intentionally, restore, or remove from tracking with.gitignore. - No hosted demo/video/screenshots and no actual Copilot CLI/DevRelay embed; categories remain represented honestly via GitHub Actions, session summary, artifacts, and now possible
.entirehooks. - No fabricated user study or benchmark; CPU story stays qualitative.
- Verified facts: 73 tests, Ruff, build pass; self-audit = 5 verified / 8 attested;
nextjs-dashboard= 13 verified / 34 needs evidence / 0 rejected; directcase-study.mdre-extraction yields 71 needs-evidence. - Sentry stays opt-in via
SENTRY_DSN/DOXIFY_SENTRY=0; prompts/outputs are not recorded. SerpApi remains Scout fallback after--notesand GitHub releases. - Deadline: 2026-10-25 11:59 PM PDT = 2026-10-26 7:59 AM WAT.
- DEV publishing is not available from this environment; user must paste/publish manually.
Work State
Completed
- CPU backstory added to intro, laptop bullet, and design-target paragraph; typo fixed.
- Code and article committed/pushed:
afd9b8b feat(observability): add tracing, self-check, and verification actiona26a20f docs(submission): sharpen Hacktoberfest story and add CPU constraint
- Pushed
main; latest CI fora26a20fsucceeded. - Verified before latest working-tree edits: 73 tests, Ruff, build, README self-audit (5/8), self-check, and expected-failure case.
- Installer research completed: build artifacts, package metadata/entrypoint, no tags/releases, no PyPI, README only
uv sync, CLI lacks--version. - Installer/version-flag/CI/docs plan settled.
Active
- Implement
install.sh,doxify --version, version test, README/CI install coverage, andsubmission.mdinstall block. - Fix working-tree article title/TLDR/typo/duplicates; preserve new user title; add install command pinned to
v0.1.0. - Clear git tree by committing in logical batches, then create/push annotated
v0.1.0. - Decide batch handling for
.obsidian,.cursor,.entire,.opencode. - No
install.sh, tag, or release exists yet; PyPI is deferred.
Blocked
-
v0.1.0source tarball/raw installer URL 404s until the tag is pushed. - GitHub API/raw network intermittently timed out; avoid API-dependent latest-release resolution and pin the tag.
- No PyPI; installer must use the GitHub tag source archive.
- No real measured CPU benchmark; keep claims qualitative.
- No hosted demo/video/screenshots or DevRelay embed.
-
.obsidianconflict unr … [truncated 1686 chars]
Objective
- Build a curl-friendly
install.shthat installs Doxify from a pinned GitHub tag usinguv, add--version/v0.1.0release metadata, and document the installer. Do not use PyPI yet. - Preserve the Hacktoberfest article and commit all pending changes in logical batches so the Git tree ends clean.
Important Details
- Latest user directive:
clear the git tree; commit all changes by batches. This supersedes the earlier instruction not to commit.obsidian/workspace.json; local agent dirs still need an explicit include/ignore decision. - Chosen installation strategy for now:
install.shas the only install path, not PyPI. - Planned installer URL:
https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh; correct host israw.githubusercontent.com, notraw.githubcontent.com. - Planned source archive URL:
https://github.com/Ekomjah/doxify/archive/refs/tags/v0.1.0.tar.gz; download-then-run is preferred over blindcurl | sh. - Installer should bootstrap
uvif missing, runuv tool install --force --python 3.12 "$source_dir", use nosudo, and install per-user. - Installer should not install Ollama or download a model automatically; it checks for
git, runsdoxify --versionanddoxify self-check --json, and prints PATH/next-step guidance. - Planned test hook:
DOXIFY_SOURCE_DIRfor local source installs andDOXIFY_VERSIONfor version overrides. - Native Windows unsupported for now; use WSL. Windows
install.ps1is deferred. - CLI currently lacks
--version; add it usingimportlib.metadatawith fallback. -
pyproject.tomlcurrently hasversion = "0.1.0",requires-python = ">=3.10", and entry pointdoxify = "doxify:main". -
uv buildproducesdist/doxify-0.1.0-py3-none-any.whlanddist/doxify-0.1.0.tar.gz;dist/is ignored by git. - No git tags or GitHub Releases exist yet. The
v0.1.0tag must be created/pushed before the installer URL or source archive works. - PyPI returns 404 for
doxify, so the name appears available, but PyPI is intentionally deferred. - Current branch is
main, remote[redacted-email]:Ekomjah/doxify.git, and HEADa26a20fis pushed. - Current worktree is dirty:
M .obsidian/workspace.jsonM submission.md?? .cursor/?? .entire/?? .opencode/
- Untracked local agent configs:
.cursor/hooks.json,.entire/settings.json,.entire/.gitignore,.opencode/plugins/entire.ts. -
.entireenables checkpoints via git refs;.cursor/hooks.jsoncontains Entire hooks;.opencode/plugins/entire.tsis a 26KB auto-generated Entire plugin. - Current working
submission.mdtitle is# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.; preserve the user’s uncommitted title/TLDR edits. - Known article copy issues to fix before committing:
contributor, another developer ,and possible duplicated Lazy Payoff/design-target text. - Required article section order remains:
What I Built,Demo,Code,How I Built It,Why Does Open Innovation Matter?,My Agent Session,The Lazy Payoff,Prize Categories. - Required tags
#devchallengeand#hf26challengeremain present;#hf26challengeis in the closing tag line. - CPU backstory is already integrated and pushed in
a26a20f; it still appears in the working copy as “I almost skipped this challenge. My laptop has a weak CPU…” - No fabricated Copilot/Entire/DevRelay integration, hosted demo, video, or screenshots. Entire category uses session summary plus
manifest.json/claims.json. - No measured CPU benchmark; keep performance claims aligned with the 8 GB CPU laptop /
gemma3:1b~806 MB positioning. - Verified article facts: 73 tests, self-audit = 5 verified / 8 attested,
nextjs-dashboard= 13 verified / 34 needs evidence / 0 rejected. - Direct artifact verification of
output/next-dashboard/case-study/case-study.mdyields 71 needs-evidence; the article’s 34 refers to the compile/claims.jsonrun. - Latest checks passed:
uv run pytest -q(73),uv run ruff check .,uv build, README self-audit,doxify self-check --json, and expected-failure case study. - Latest CI run for
a26a20fsucceeded. -
README.mdcurrently documentsuv sync/uv run doxify --help; it needs an install.sh-first section. -
submission.mdDemo still needs the curl install block and no-model smoke test. - GitHub network/API access was intermittently flaky (
ghTLS timeouts, raw.githubusercontent timeout), so verify tag/push/install URLs after network operations. -
.obsidian/workspace.jsoncontains local workspace state only, but is tracked and modified; latest directive requires clearing it somehow.
Work State
Completed
- CPU backstory added to intro, laptop bullet, and design-target paragraph; line-369 typo fixed and pushed in
a26a20f. - Supporting code/observability commit
afd9b8band article commita26a20fare pushed toorigin/main. - Latest CI for
a26a20fis green. - 73 tests, Ruff, build, README self-audit, self-check, and expected-failure case study all pass.
-
uv buildartifacts generated in ignoreddist/; wheel is pure Python. - Install plan reviewed: source-archive
uvinstaller is viable without PyPI. - Checked PyPI:
doxifypackage not present (404); no tags or GitHub Releases exist. - Confirmed
uv 0.13.1anduv tool dir --bin→~/.local/bin.
Active
- Implementing
install.sh; not yet created. - Adding CLI
--versionsupport and tests. - Preparing
v0.1.0tag/version metadata and installer docs. - Updating
README.mdandsubmission.mdwith install.sh usage. - Planning CI installer smoke test and optional release workflow.
- Reviewing uncommitted article edits before batch commits; user’s new title/TLDR must be preserved.
- Deciding whether
.cursor/,.entire/,.opencode/, and.obsidian/workspace.jsonshould be committed as project config or ignored.
Blocked
-
install.shdoes not exist yet; no--versionflag yet. -
v0.1.0tag does not exis … [truncated 2223 chars]
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
$ git status --short --branch && git log --oneline -5 && git tag --list && git remote -v
## main...origin/main
M .obsidian/workspace.json
M README.md
M pyproject.toml
M src/doxify/cli.py
M submission.md
?? .cursor/
?? .entire/
?? .opencode/
?? install.sh
?? tests/test_cli_version.py
a26a20f docs(submission): sharpen Hacktoberfest story and add CPU constraint
afd9b8b feat(observability): add tracing, self-check, and verification action
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
d5c94b8 feat(scout): add evidence-backed upgrade impact analysis
origin [redacted-email]:Ekomjah/doxify.git (fetch)
origin [redacted-email]:Ekomjah/doxify.git (push)
file: ~/Desktop/repos/doxify/src/doxify/cli.py
<path>~/Desktop/repos/doxify/src/doxify/cli.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2:
3: import logging
4: import re
5: import shutil
6: import sys
7: from datetime import datetime
8: from importlib.metadata import PackageNotFoundError
9: from importlib.metadata import version as package_version
10: from pathlib import Path
11:
12: import typer
13: from rich.table import Table
14:
15: from .audit import (
16: ArtifactReport,
17: audit_artifact,
18: failing_claims,
19: parse_fail_on,
20: )
21: from .console import (
22: con
… [truncated 1616 chars]
file: ~/Desktop/repos/doxify/pyproject.toml
<path>~/Desktop/repos/doxify/pyproject.toml</path>
<type>file</type>
<content>
1: [project]
2: name = "doxify"
3: version = "0.1.0"
4: license = "MIT"
5: description = "Compile evidence-backed READMEs and portfolio case studies from local git repositories"
6: readme = "README.md"
7: authors = [
8: { name = "Ekomjah Edet", email = "[redacted-email]" }
9: ]
10: keywords = ["cli", "documentation", "gemma", "git", "ollama", "readme"]
11: classifiers = [
12: "Development Status :: 4 - Beta",
13: "Environment :: Console",
14: "Intended Audience :: Developers",
15:
… [truncated 1116 chars]
file: ~/Desktop/repos/doxify/.github/workflows/ci.yml
<path>~/Desktop/repos/doxify/.github/workflows/ci.yml</path>
<type>file</type>
<content>
1: name: CI
2:
3: on:
4: push:
5: branches: ["**"]
6: pull_request:
7:
8: permissions:
9: contents: read
10:
11: jobs:
12: test:
13: name: Lint, test, and build
14: runs-on: ubuntu-latest
15: steps:
16: - name: Check out repository
17: uses: actions/checkout@v4
18:
19: - name: Install uv
20: uses: astral-sh/setup-uv@v6
21: with:
22: python-version: "3.10"
23: enable-cache: true
24: cache-dependency-glo
… [truncated 1585 chars]
$ git diff -- README.md pyproject.toml src/doxify/cli.py tests/test_cli_version.py install.sh
diff --git a/README.md b/README.md
index b171f31..484d00e 100644
--- a/README.md
+++ b/README.md
@@ -12,14 +12,34 @@ The model only writes; the compiler checks.
## Install
-Requires Python 3.10+ and git.
+Linux, macOS, and WSL:
```bash
+curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
+sh /tmp/doxify-install.sh
+```
+
+The installer requires `curl` and `git`. It installs `uv` if needed, places
+Doxify in an isolated per-user environment, and runs `doxify self-check`. It
+does not use `sudo`, install Ollama, or download a model.
+
+Verify the install:
+
+```bash
+doxify --version
+doxify self-check --json
+```
+
+From a source checkout:
+
+```bash
+git clone https://github.com/Ekomjah/doxify
+cd doxify
uv sync
uv run doxify --
… [truncated 2403 chars]
$ git diff -- submission.md
diff --git a/submission.md b/submission.md
index 963ddec..eef69a1 100644
--- a/submission.md
+++ b/submission.md
@@ -1,23 +1,31 @@
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
-# I built a local Gemma tool that writes the README I keep avoiding
+# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
-Every project ends the same way for me. The last commit lands, my brain shuts off, and the README is still empty. That page is the first thing an interviewer, a contributor, or another developer sees
… [truncated 5005 chars]
$ sed -n '1,220p' install.sh; printf '\n--- test ---\n'; cat tests/test_cli_version.py
#!/bin/sh
set -eu
REPO="Ekomjah/doxify"
DEFAULT_VERSION="0.1.0"
UV_INSTALL_URL="https://astral.sh/uv/install.sh"
usage() {
cat <<'EOF'
Doxify installer
Usage:
sh install.sh [--version VERSION] [--help]
Options:
--version VERSION Doxify release to install (default: 0.1.0)
--help Show this help
Environment:
DOXIFY_VERSION Doxify release to install
DOXIFY_SOURCE_DIR Install from a local source tree (maintainer testing)
UV_TOOL_DIR Override uv's tool environment directory
UV_TOOL_BIN_DIR Override uv's executable directory
The installer uses uv to create an isolated per-user install.
It does not use sudo, install Ollama, or download a model.
EOF
}
log() {
printf '%s\n' "doxify-install: $*"
}
fail() {
printf '%s\n' "doxify-insta
… [truncated 3689 chars]
$ git diff --stat && git diff -- .obsidian/workspace.json
.obsidian/workspace.json | 15 ++++++++-------
README.md | 24 ++++++++++++++++++++++--
pyproject.toml | 15 +++++++++++++++
src/doxify/cli.py | 27 +++++++++++++++++++++++++++
submission.md | 23 ++++++++++++++---------
5 files changed, 86 insertions(+), 18 deletions(-)
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index ca57edf..35b6695 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
- "file": "PLAN.md",
+ "file": "submission.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
- "titl
… [truncated 959 chars]
$ rg -n "^## |^# |install|uv sync|git clone|curl|Demo|Code" submission.md | head -80
3:# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.
29:## What I Built
93:## Demo
108:git clone https://github.com/Ekomjah/doxify
110:uv sync
127:## Code
147:## How I Built It
149:Doxify is a Python 3.10 CLI built with Typer, Rich, Pydantic, GitPython, Ollama, and the Sentry SDK. The default model is `gemma3:1b` running locally. Users who cannot or do not want to install Ollama can opt into Ollama Cloud with `--provider cloud` and `OLLAMA_API_KEY`; cloud mode defaults to `gemma4:31b` and warns that evidence cards will leave the machine.
347:The monitor config describes a nightly 3 a.m. UTC check-in with a 10-minute margin and a 15-minute maximum runtime. `.github/workflows/sentry-nightly.yml` runs `doxify self-check` on that schedule, so a broken install shows up as a
… [truncated 643 chars]
$ sed -n '1,120p' submission.md
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*
# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.
I had built the thing. I had not explained it.
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer , or a future-me sees. If it is empty, they leave.
Two years later, the other bill comes due. The dependencies have moved, the build is deprecated, and the release notes are a wall. I do not need a generic summary. I
… [truncated 7106 chars]
$ sed -n '330,445p' submission.md
"gen_ai": {"inputs": False, "outputs": False},
"database_query_data": False,
"queues": False,
"stack_frame_variables": False,
"frame_context_lines": 0,
}
```
Tokens, API keys, and absolute home paths are redacted before events leave the process. `before_send`, `before_breadcrumb`, `before_send_transaction`, and `before_send_log` all pass through the same policy.
`self-check` can also use a Sentry cron monitor:
```python
with cron_monitor(monitor_slug):
result = run_self_check(client=client)
```
The monitor config describes a nightly 3 a.m. UTC check-in with a 10-minute margin and a 15-minute maximum runtime. `.github/workflows/sentry-nightly.yml` runs `doxify self-check` on that schedule, so a broken install shows up as a missed check-in.
### GitHub Actions
Th
… [truncated 7218 chars]
$ sed -n '120,165p' submission.md
Precomputed artifacts:
- [Case study](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/case-study.md)
- [Claim ledger](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/claims.json)
- [Defense pack](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/defense.md)
## Code
The full project is at [github.com/Ekomjah/doxify](https://github.com/Ekomjah/doxify).
Start with:
- [`src/doxify/pipeline.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/pipeline.py) — compile, README, verify, and export.
- [`src/doxify/evidence.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/evidence.py) — deterministic scanning and evidence cards.
- [`src/doxify/verifier.py`](https://github.com/Ekomjah/doxify/blo
… [truncated 2275 chars]
$ sed -n '1,160p' README.md
# Doxify
Doxify compiles evidence-backed READMEs and portfolio case studies from local
git repositories. It also scouts breaking dependency upgrades against your
code.
The core pipeline is deterministic: scan the repository, build evidence cards,
generate prose from that evidence, verify every claim, and export the result.
The model only writes; the compiler checks.
<!-- doxify-attested: project description and positioning -->
## Install
Linux, macOS, and WSL:
```bash
curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
sh /tmp/doxify-install.sh
```
The installer requires `curl` and `git`. It installs `uv` if needed, places
Doxify in an isolated per-user environment, and runs `doxify self-check`. It
does not use `sudo`, install Olla
… [truncated 3914 chars]
$ rg -n "self-check|def self_check|self_check" src/doxify/cli.py | head -20; sed -n '560,665p' src/doxify/cli.py
53:from .selfcheck import SelfCheckError, run_self_check
199:@app.command(name="self-check")
200:def self_check(
230: result = run_self_check(client=client)
244: table = Table(title="Doxify self-check", show_header=True)
if artifact_report is not None:
_print_artifact_verification(artifact_report, fail_statuses)
failed = []
if session_report is not None:
failed.extend(failing_claims(session_report.claims, fail_statuses))
if artifact_report is not None:
failed.extend(failing_claims(artifact_report.claims, fail_statuses))
if failed:
console.print(
f"[red]Verification failed:[/red] {len(failed)} claim(s) matched --fail-on."
)
raise typer.Exit(code=1)
console.print("[green]Verification passed.[/
… [truncated 3154 chars]
$ sed -n '190,270p' src/doxify/cli.py
)
healthy, detail = client.health()
table.add_row("Endpoint", "ok" if healthy else "missing", detail)
console.print(table)
if not git_path or not healthy:
raise typer.Exit(code=1)
@app.command(name="self-check")
def self_check(
json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
check_model: bool = typer.Option(
False, "--check-model", help="Also check the configured Ollama model."
),
model: str | None = typer.Option(None, help="Model to check with --check-model."),
host: str | None = typer.Option(None, help="Ollama endpoint URL."),
provider: str | None = typer.Option(
None,
"--provider",
help="Inference backend: local (default) or cloud.",
),
monitor_slug:
… [truncated 2072 chars]
$ rg -n "73|tests|test count|installer|Install" README.md submission.md .github/workflows src tests pyproject.toml | head -80
submission.md:28:> - **How it is checked:** 73 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
submission.md:142:- [`.github/workflows/ci.yml`](https://github.com/Ekomjah/doxify/blob/main/.github/workflows/ci.yml) — tests, build, and the self-audit.
submission.md:145:The repository includes 73 tests, a Ruff check, a build check, and a self-audit that runs Doxify's verifier against Doxify's README.
submission.md:211:The verifier also checks numbers against the cited evidence, checks code identifiers, checks token overlap, and flags claims that code cannot prove: deployment, "tests pass," OAuth, user counts, performance improvements, awards, team size, and "first/only" claims.
submission.md:351:The test job in
… [truncated 3641 chars]
$ rg -n "73" --glob '!uv.lock' .
./pyproject.toml:25: "sentry-sdk>=2.73.0",
./submission.md:28:> - **How it is checked:** 73 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
./submission.md:145:The repository includes 73 tests, a Ruff check, a build check, and a self-audit that runs Doxify's verifier against Doxify's README.
./submission.md:351:The test job installs `uv`, syncs the lockfile, runs Ruff, runs the 73 tests, builds the package, and uploads the distributions.
./submission.md:435:**Best Use of GitHub Copilot.** The category includes automating your project with GitHub Actions. The whole contribution path is automated: every push and pull request runs lint, 73 tests, a build, and the Doxify evidence gate. A Copilot-authored PR g
… [truncated 64 chars]
Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.
{
"todos": [
{
"content": "Fix article copy issues and add install.sh section",
"status": "in_progress",
"priority": "high"
},
{
"content": "Harden installer and add CI smoke coverage",
"status": "pending",
"priority": "high"
},
{
"content": "Run full tests, lint, build, self-audit, and local install",
"status": "pending",
"priority": "high"
},
{
"content": "Commit changes in logical batches",
"status": "pending",
"priority": "high"
},
{
"content": "Create and push v0.1.0 tag; verify published installer URL",
"status": "pending",
"priority": "high"
}
]
}
[
{
"content": "Fix article copy issues and add install.sh section",
"status": "in_progress",
"priority": "high"
},
{
"content": "Harden installer and add CI smoke coverage",
"status": "pending",
"priority": "high"
},
{
"content": "Run full tests, lint, build, self-audit, and local install",
"status": "pending",
"priority": "high"
},
{
"content": "Commit changes in logical batches",
"status": "pending",
"priority": "high"
},
{
"content": "Create and push v0.1.0 tag; verify published installer URL",
"status": "pending",
… [truncated 27 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer , or a fut
… [truncated 40 chars]
new:
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer, or a future-me sees. If it is empty, they leave.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
> - **How it is checked:** 73 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
## What I Built
new:
> - **How it is checked:** 73 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
## What I Built
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
Reproduce it locally:
```bash
git clone https://github.com/Ekomjah/doxify
cd doxify
uv sync
ollama pull gemma3:1b
uv run doxify self-check --json
uv run doxify compile /path/to/nextjs-dashboard --ou
… [truncated 165 chars]
new:
Install Doxify on Linux, macOS, or WSL:
```bash
curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
sh /tmp/doxify-install.sh
doxify --version
```
The installer sets up an isolated `uv` environment and runs `doxify self-check`. It does not use `sudo`, install Ollama, or download a model.
Reproduce the run locally:
```bash
ollama pull gemma3:1
… [truncated 319 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
The design target is a sub-10-minute local compile on an 8 GB CPU laptop, because that is the machine I have. The model is small, the prompts are bounded, and the expensive trust work is deterministic
… [truncated 405 chars]
new:
The design target is a sub-10-minute local compile on an 8 GB CPU laptop, because that is the machine I have. The model is small, the prompts are bounded, and the expensive trust work is deterministic. I would rather ship a 1B model plus a verifier that says "I cannot prove that" than a larger model that says everything confidently.
Slow and steady is the right pace for documentation, because the
… [truncated 74 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
- [`src/doxify/pipeline.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/pipeline.py) — compile, README, verify, and export.
new:
- [`install.sh`](https://github.com/Ekomjah/doxify/blob/main/install.sh) — one-command install for Linux, macOS, and WSL.
- [`src/doxify/pipeline.py`](https://github.com/Ekomjah/doxify/blob/main/src/doxify/pipeline.py) — compile, README, verify, and export.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer , or a fut
… [truncated 40 chars]
new:
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer, or a future-me sees. If it is empty, they leave.
file: ~/Desktop/repos/doxify/submission.md
old:
Doxify is a local-first CLI that scans a git repository, builds evidence cards, and asks Gemma to write from those cards. Then a deterministic verifier checks every sentence. It gives open-source deve
… [truncated 59 chars]
new:
It gives open-source developers, reviewers, bots, and fellow sloths their time back.
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
> - **How it is checked:** 73 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
## What I Built
new:
> - **How it is checked:** 74 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
## What I Built
$ sed -n '1,35p' submission.md | cat -A | sed -n '1,35p'
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*$
$
# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.$
$
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.$
$
I had built the thing. I had not explained it.$
$
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer, or a future-me sees. If it is empty, they leave.$
$
Two years later, the other bill comes due. The dependencies have moved, the build is deprecated, and the release notes are a wall. I do not need a generic sum
… [truncated 1971 chars]
$ sed -n '1,36p' submission.md | cat -A | head -40
*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*$
$
# A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.$
$
The repository had 20 commits, nine MCP tools, three database models, 26 seeded scholarship records, and no README.$
$
I had built the thing. I had not explained it.$
$
Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer, or a future-me sees. If it is empty, they leave.$
$
Two years later, the other bill comes due. The dependencies have moved, the build is deprecated, and the release notes are a wall. I do not need a generic sum
… [truncated 2084 chars]
$ sed -n '90,125p' submission.md; printf '\n---\n'; sed -n '418,442p' submission.md
The checked-in `nextjs-dashboard` run left 34 of its 47 claims needing evidence. That is the product working. The 34 are the review queue.
That artifact is the raw run, not a cleaned-up sample. The `[needs evidence]` tags and the model's awkward openings are the point: the verifier flags them instead of hiding them.
## Demo
There is no hosted demo. Doxify is a CLI, and the repository under analysis does not have to leave the machine. The walkthrough is the command block below; the checked-in artifacts are its output.
The motivating demo is `scholarship-mcp`: 20 commits, nine MCP tools, three models, 26 seeded records, and no README. The reproducibility artifact is a run against `nextjs-dashboard`, because it is a useful stress test.
That run produced:
- **13 verified claims**
- **34
… [truncated 3142 chars]
$ sed -n '88,125p' submission.md; printf '\n---\n'; sed -n '128,150p' submission.md; printf '\n---\n'; sed -n '412,442p' submission.md
Unsupported sentences are not deleted silently. They are marked `[needs evidence]`, paired with a gap question, and included in the claim ledger. Author context can be added with `doxify attest` and is labeled as an attestation, not repository proof.
The checked-in `nextjs-dashboard` run left 34 of its 47 claims needing evidence. That is the product working. The 34 are the review queue.
That artifact is the raw run, not a cleaned-up sample. The `[needs evidence]` tags and the model's awkward openings are the point: the verifier flags them instead of hiding them.
## Demo
There is no hosted demo. Doxify is a CLI, and the repository under analysis does not have to leave the machine. The walkthrough is the command block below; the checked-in artifacts are its output.
The motivating demo i
… [truncated 5669 chars]
$ sed -n '118,140p' submission.md
```bash
ollama pull gemma3:1b
doxify self-check --json
doxify compile /path/to/nextjs-dashboard --out /tmp/doxify-demo
doxify readme /path/to/nextjs-dashboard --output /tmp/README.md
SERPAPI_API_KEY=... doxify scout react 18 19 --repo /path/to/app
```
Working from a source checkout instead? Run `uv sync`, then prefix each command with `uv run`.
Sentry is opt-in. Leave `SENTRY_DSN` unset to keep tracing off, or set it to see command, stage, model, and HTTP spans.
Precomputed artifacts:
- [Case study](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/case-study.md)
- [Claim ledger](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-study/claims.json)
- [Defense pack](https://github.com/Ekomjah/doxify/blob/main/output/next-dashboard/case-stud
… [truncated 124 chars]
file: ~/Desktop/repos/doxify/submission.md
old:
73 tests
new:
74 tests
file: ~/Desktop/repos/doxify/submission.md
old:
The test job installs `uv`, syncs the lockfile, runs Ruff, runs the 74 tests, builds the package, and uploads the distributions.
new:
The test job installs `uv`, syncs the lockfile, runs Ruff, runs the 74 tests, builds the package, and uploads the distributions. A separate installer job runs `install.sh` from a source checkout, then checks `doxify --version` and `doxify self-check`.
Edit applied successfully.
file: ~/Desktop/repos/doxify/.github/workflows/ci.yml
old:
verify:
name: Verify evidence-backed claims
runs-on: ubuntu-latest
env:
SENTRY_DSN: ""
steps:
- name: Check out repository
uses: actions/checkout@v4
- name
… [truncated 180 chars]
new:
installer:
name: Installer smoke test
runs-on: ubuntu-latest
env:
SENTRY_DSN: ""
DOXIFY_SOURCE_DIR: ${{ github.workspace }}
UV_TOOL_DIR: ${{ runner.temp }}/doxify-tool
UV_TOOL_BIN_DIR: ${{ runner.temp }}/doxify-bin
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
… [truncated 719 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/submission.md
old:
73 tests
new:
74 tests
Edit applied successfully.
file: ~/Desktop/repos/doxify/install.sh
old:
command -v curl >/dev/null 2>&1 || fail "curl is required"
tmp_dir=$(mktemp -d)
new:
command -v curl >/dev/null 2>&1 || fail "curl is required"
command -v git >/dev/null 2>&1 || fail "git is required"
tmp_dir=$(mktemp -d)
Edit applied successfully.
file: ~/Desktop/repos/doxify/install.sh
old:
log "uv not found; installing it to $HOME/.local/bin"
curl -fsSL "$UV_INSTALL_URL" -o "$tmp_dir/uv-install.sh" || fail "could not download uv"
sh "$tmp_dir/uv-install.sh"
new:
log "uv not found; installing it to $HOME/.local/bin"
curl -fsSL "$UV_INSTALL_URL" -o "$tmp_dir/uv-install.sh" || fail "could not download uv"
UV_NO_MODIFY_PATH=1 sh "$tmp_dir/uv-install.sh"
Edit applied successfully.
file: ~/Desktop/repos/doxify/install.sh
old:
installed_version=$("$doxify_bin" --version)
log "Installed Doxify ${installed_version}"
"$doxify_bin" self-check --json >/dev/null || fail "Doxify self-check failed"
case ":$PATH:" in
*":$tool_b
… [truncated 230 chars]
new:
installed_version=$("$doxify_bin" --version)
if [ -z "${DOXIFY_SOURCE_DIR:-}" ] && [ "$installed_version" != "$VERSION" ]; then
fail "expected Doxify ${VERSION}, but the installed version is ${installed_version}"
fi
log "Installed Doxify ${installed_version}"
"$doxify_bin" self-check --json >/dev/null || fail "Doxify self-check failed"
case ":$PATH:" in
*":$tool_bin:"*) ;;
*) log "Add
… [truncated 68 chars]
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
```bash
doxify doctor [--provider local|cloud]
doxify scan REPO
new:
```bash
doxify --version
doxify doctor [--provider local|cloud]
doxify scan REPO
Edit applied successfully.
$ rg -n "74 tests|73 tests|contributor|I would rather|## What I Built|## Prize Categories" submission.md; sed -n '20,32p' submission.md | cat -A; sed -n '420,435p' submission.md | cat -A
9:Every project ends the same way for me. The last commit lands, my brain shuts off, and the README stays empty. That page is the first thing an interviewer, a contributor, another developer, or a future-me sees. If it is empty, they leave.
28:> - **How it is checked:** 74 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
30:## What I Built
43:Doxify is for the developer who just pushed the last commit and cannot face a blank page. It is for the developer whose best work is private or under NDA. It is for the maintainer who comes back to a project after two years of dependency drift. It is also for the open-source maintainer whose README is the first thing contributors and bots read.
155:The repository include
… [truncated 3645 chars]
$ sed -n '432,440p' submission.md | cat -A
The README run produced a clean README with hidden provenance that CI can check. Scout turns a stack of release notes into the few breaking changes that touch my code, with file and line matches. And `scholarship-mcp`, the repo that started all this, now has something to compile: 20 commits and no README become a structured draft, a claim ledger, and an interview defense pack.$
$
The design target is a sub-10-minute local compile on an 8 GB CPU laptop, because that is the machine I have. The model is small, the prompts are bounded, and the expensive trust work is deterministic. I would rather ship a 1B model plus a verifier that says "I cannot prove that" than a larger model that says everything confidently.$
$
Slow and steady is the right pace for documentation, because the careful part i
… [truncated 287 chars]
$ sed -n '1,120p' .github/workflows/ci.yml; printf '\n--- README commands ---\n'; sed -n '80,125p' README.md
name: CI
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
test:
name: Lint, test, and build
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.10"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies
run: uv sync --frozen
- name: Lint
run: uv run ruff check .
- name: Test
run: uv run pytest -q
- name: Build package
run: uv build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-f
… [truncated 3306 chars]
file: ~/Desktop/repos/doxify/README.md
old:
The installer requires `curl` and `git`. It installs `uv` if needed, places
Doxify in an isolated per-user environment, and runs `doxify self-check`. It
does not use `sudo`, install Ollama, or downloa
… [truncated 10 chars]
new:
The installer requires `curl`, `git`, and `tar`. It installs `uv` if needed,
places Doxify in an isolated per-user environment, and runs
`doxify self-check`. It does not use `sudo`, install Ollama, or download a model.
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
```bash
doxify doctor [--provider local|cloud]
doxify scan REPO
new:
```bash
doxify --version
doxify self-check [--json] [--check-model]
doxify doctor [--provider local|cloud]
doxify scan REPO
file: ~/Desktop/repos/doxify/README.md
old:
```bash
doxify doctor [--provider local|cloud]
doxify scan REPO
new:
```bash
doxify --version
doxify doctor [--provider local|cloud]
doxify scan REPO
$ sed -n '88,102p' README.md | cat -A
<!-- doxify-attested: cloud setup, privacy, and billing guidance -->$
$
## Commands$
$
```bash$
doxify --version$
doxify doctor [--provider local|cloud]$
doxify scan REPO$
doxify compile REPO$
doxify readme REPO [--output PATH]$
doxify scout PACKAGE FROM TO [--repo PATH]$
doxify verify SESSION [--artifact PATH] [--fail-on STATUSES]$
doxify attest SESSION --claim C1 --answer "..."$
doxify export SESSION$
```$
$ sed -n '88,110p' README.md | cat -A
<!-- doxify-attested: cloud setup, privacy, and billing guidance -->$
$
## Commands$
$
```bash$
doxify --version$
doxify doctor [--provider local|cloud]$
doxify scan REPO$
doxify compile REPO$
doxify readme REPO [--output PATH]$
doxify scout PACKAGE FROM TO [--repo PATH]$
doxify verify SESSION [--artifact PATH] [--fail-on STATUSES]$
doxify attest SESSION --claim C1 --answer "..."$
doxify export SESSION$
```$
$
`scan`, `verify`, `attest`, and `export` never call a model and work offline.$
$
`doxify verify` exits non-zero when a claim needs evidence or is rejected. Use$
`--fail-on none` to report without failing, `--check` to avoid rewriting session$
files, and `--artifact` with `--evidence` to audit a README or case study$
directly.$
$
file: ~/Desktop/repos/doxify/README.md
old:
doxify --version
doxify doctor [--provider local|cloud]
new:
doxify --version
doxify self-check [--json] [--check-model]
doxify doctor [--provider local|cloud]
Edit applied successfully.
file: ~/Desktop/repos/doxify/README.md
old:
doxify --version
doxify doctor [--provider local|cloud]
new:
doxify --version
doxify self-check [--json] [--check-model]
doxify doctor [--provider local|cloud]
$ sed -n '13,50p' README.md
## Install
Linux, macOS, and WSL:
```bash
curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
sh /tmp/doxify-install.sh
```
The installer requires `curl`, `git`, and `tar`. It installs `uv` if needed,
places Doxify in an isolated per-user environment, and runs
`doxify self-check`. It does not use `sudo`, install Ollama, or download a model.
Verify the install:
```bash
doxify --version
doxify self-check --json
```
From a source checkout:
```bash
git clone https://github.com/Ekomjah/doxify
cd doxify
uv sync
uv run doxify --help
```
Native Windows is not supported by the shell installer yet. Use WSL for now.
<!-- doxify-attested: installation instructions -->
## Local mode (default)
Local mode uses Ollama and `gemma3:1b`. Nothing
… [truncated 43 chars]
$ rg -n "73 tests|74 tests|install.sh|--version|self-check" submission.md README.md .github/workflows/ci.yml | head -80
README.md:18:curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
README.md:19:sh /tmp/doxify-install.sh
README.md:24:`doxify self-check`. It does not use `sudo`, install Ollama, or download a model.
README.md:29:doxify --version
README.md:30:doxify self-check --json
README.md:93:doxify --version
README.md:94:doxify self-check [--json] [--check-model]
submission.md:28:> - **How it is checked:** 74 tests, Ruff, a build check, and a CI job that runs Doxify's verifier on Doxify's own README and fails if a section is unsupported.
submission.md:109:curl -fsSL https://raw.githubusercontent.com/Ekomjah/doxify/v0.1.0/install.sh -o /tmp/doxify-install.sh
submission.md:110:sh /tmp/doxify-install.sh
submission.md:111:doxify --version
submission.md:11
… [truncated 2606 chars]
$ sh -n install.sh && echo 'install.sh syntax ok' && chmod +x install.sh && ls -l install.sh
install.sh syntax ok
-rwxrwxr-x 1 [user] [user] 4268 Oct 25 05:57 install.sh
$ uv run pytest -q tests/test_cli_version.py && uv run doxify --version && uv run doxify --help | head -20
. [100%]
1 passed in 0.05s
0.1.0
Usage: doxify [OPTIONS] COMMAND [ARGS]...
Doxify compiles evidence-backed READMEs and portfolio case studies from local
git repositories, and scouts breaking dependency upgrades.
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --version -V Show the installed Doxify version and exit. │
│ --install-completion Install comple
… [truncated 924 chars]
$ rg -n -C 1 "doxify --version|doxify doctor" README.md
28-```bash
29:doxify --version
30-doxify self-check --json
--
53-ollama pull gemma3:1b
54:doxify doctor
55-doxify readme /path/to/repo
--
66-export OLLAMA_API_KEY="[redacted]"
67:doxify doctor --provider cloud
68-doxify readme /path/to/repo --provider cloud
--
92-```bash
93:doxify --version
94-doxify self-check [--json] [--check-model]
95:doxify doctor [--provider local|cloud]
96-doxify scan REPO
$ sed -n '1,175p' install.sh
#!/bin/sh
set -eu
REPO="Ekomjah/doxify"
DEFAULT_VERSION="0.1.0"
UV_INSTALL_URL="https://astral.sh/uv/install.sh"
usage() {
cat <<'EOF'
Doxify installer
Usage:
sh install.sh [--version VERSION] [--help]
Options:
--version VERSION Doxify release to install (default: 0.1.0)
--help Show this help
Environment:
DOXIFY_VERSION Doxify release to install
DOXIFY_SOURCE_DIR Install from a local source tree (maintainer testing)
UV_TOOL_DIR Override uv's tool environment directory
UV_TOOL_BIN_DIR Override uv's executable directory
The installer uses uv to create an isolated per-user install.
It does not use sudo, install Ollama, or download a model.
EOF
}
log() {
printf '%s\n' "doxify-install: $*"
}
fail() {
printf '%s\n' "doxify-insta
… [truncated 3468 chars]
$ sed -n '430,560p' src/doxify/cli.py
),
max_releases: int = typer.Option(
12,
"--max-releases",
help="Maximum number of GitHub releases to fetch.",
),
deep: bool = typer.Option(
False,
"--deep",
help="Analyze every fetched release instead of the first target-line release.",
),
model: str | None = typer.Option(
None,
help=(
"Model to use. Defaults to gemma3:1b locally or "
"gemma4:31b on Ollama Cloud."
),
),
host: str | None = typer.Option(None, help="Ollama endpoint URL."),
provider: str | None = typer.Option(
None,
"--provider",
help="Inference backend: local (default) or cloud.",
),
force: bool = typer.Option(False, "--force", help="Allow an existing output direc
… [truncated 3604 chars]
$ rm -rf /tmp/doxify-tool /tmp/doxify-bin && mkdir -p /tmp/doxify-tool /tmp/doxify-bin && UV_LINK_MODE=copy UV_TOOL_DIR=/tmp/doxify-tool UV_TOOL_BIN_DIR=/tmp/doxify-bin DOXIFY_SOURCE_DIR="$PWD" timeout 180 sh install.sh; status=$?; echo "install exit=$status"; if [ "$status" -eq 0 ]; then /tmp/doxify-bin/doxify --version; /tmp/doxify-bin/doxify self-check --json; fi
doxify-install: Installing from local source: ~/Desktop/repos/doxify
doxify-install: Installing Doxify 0.1.0 with uv...
Resolved 26 packages in 1.59s
Building doxify @ file://~/Desktop/repos/doxify
Built doxify @ file://~/Desktop/repos/doxify
Prepared 1 package in 62ms
Installed 26 packages in 304ms
+ annotated-doc==0.0.5
+ annotated-types==0.8.0
+ anyio==4.15.1
+ certifi==2026.10.23
+ doxify==0.1.0 (from file://~/Desktop/repos/doxify)
+ gitdb==4.0.12
+ gitpython==3.2.1
+ h11==0.16.0
+ httpcore==1.0.9
+ httpx==0.28.1
+ idna==3.20
+ markdown-it-py==4.2.0
+ mdurl==0.1.2
+ ollama==0.6.3
+ pydantic==2.14.1
+ pydantic-core==2.50.1
+ pygments==2.21.0
+ rich==15.0.0
+ sentry-sdk==2.73.0
+ shellingham==1.5.4
+ smmap==5.0
… [truncated 626 chars]
$ uv python find 3.12; uv --version; command -v uv
/usr/bin/python3.12
uv 0.13.1 (x86_64-unknown-linux-gnu)
~/.local/bin/uv
$ ls -ld /tmp/doxify-tool /tmp/doxify-bin 2>/dev/null || true; ls -la /tmp/doxify-bin 2>/dev/null || true
drwxrwxr-x 2 [user] [user] 60 Oct 25 05:51 /tmp/doxify-bin
drwxrwxr-x 3 [user] [user] 100 Oct 25 05:51 /tmp/doxify-tool
total 0
drwxrwxr-x 2 [user] [user] 60 Oct 25 05:51 .
drwxrwxrwt 39 root root 1120 Oct 25 05:58 ..
lrwxrwxrwx 1 [user] [user] 34 Oct 25 05:51 doxify -> /tmp/doxify-tool/doxify/bin/doxify
$ ls -la /tmp/doxify-tool /tmp/doxify-tool/doxify/bin 2>/dev/null; /tmp/doxify-bin/doxify --version; echo status=$?
/tmp/doxify-tool:
total 4
drwxrwxr-x 3 [user] [user] 100 Oct 25 05:51 .
drwxrwxrwt 39 root root 1120 Oct 25 05:58 ..
-rw-rw-r-- 1 [user] [user] 1 Oct 25 05:51 .gitignore
-rw-rw-rw- 1 [user] [user] 0 Oct 25 05:51 .lock
drwxrwxr-x 4 [user] [user] 180 Oct 25 05:51 doxify
/tmp/doxify-tool/doxify/bin:
total 68
drwxrwxr-x 2 [user] [user] 420 Oct 25 05:51 .
drwxrwxr-x 4 [user] [user] 180 Oct 25 05:51 ..
-rw-rw-r-- 1 [user] [user] 4076 Oct 25 05:51 activate
-rw-rw-r-- 1 [user] [user] 2656 Oct 25 05:51 activate.bat
-rw-rw-r-- 1 [user] [user] 2606 Oct 25 05:51 activate.csh
-rw-rw-r-- 1 [user] [user] 4317 Oct 25 05:51 activate.fish
-rw-rw-r-- 1 [user] [user] 3934 Oct 25 05:51 activate.nu
-rw-rw-r-- 1 [user] [user] 2765 Oct 25 05:51 activate.ps1
-rw-rw-r--
… [truncated 786 chars]
$ /tmp/doxify-bin/doxify self-check --json; echo status=$?
{"checks": [{"detail": "3.12", "name": "python", "ok": true}, {"detail": "/usr/bin/git", "name": "git", "ok": true}, {"detail": "0.1.0", "name": "package", "ok": true}, {"detail": "read/write ok", "name": "tempdir", "ok": true}], "ok": true}
status=0
$ rm -rf /tmp/doxify-tool2 /tmp/doxify-bin2; mkdir -p /tmp/doxify-tool2 /tmp/doxify-bin2; UV_LINK_MODE=copy UV_TOOL_DIR=/tmp/doxify-tool2 UV_TOOL_BIN_DIR=/tmp/doxify-bin2 DOXIFY_SOURCE_DIR="$PWD" timeout 60 sh install.sh >/tmp/doxify-install.log 2>&1; code=$?; echo "code=$code"; tail -25 /tmp/doxify-install.log; /tmp/doxify-bin2/doxify --version; /tmp/doxify-bin2/doxify self-check --json
code=0
+ rich==15.0.0
+ sentry-sdk==2.73.0
+ shellingham==1.5.4
+ smmap==5.0.3
+ tomli==2.5.0
+ typer==0.27.3
+ typing-extensions==4.16.0
+ typing-inspection==0.4.4
+ urllib3==2.8.0
Installed 1 executable: doxify
warning: `/tmp/doxify-bin2` is not on your PATH. To use installed tools, run `export PATH="/tmp/doxify-bin2:$PATH"` or `uv tool update-shell`.
doxify-install: Installed Doxify 0.1.0
doxify-install: Add /tmp/doxify-bin2 to PATH if 'doxify' is not found in a new shell.
Doxify is ready.
doxify --version
doxify self-check
doxify scan /path/to/repo
For the full local compile:
ollama serve
ollama pull gemma3:1b
doxify readme /path/to/repo
0.1.0
{"checks": [{"detail": "3.12", "name": "python", "ok": true}, {"detail": "/usr/bin/git", "name": "git", "ok": true}, {"
… [truncated 123 chars]
$ ls -la /tmp/doxify-bin2 /tmp/doxify-tool2 2>/dev/null || true; printf '\n--- log ---\n'; tail -40 /tmp/doxify-install.log 2>/dev/null || true
/tmp/doxify-bin2:
total 0
drwxrwxr-x 2 [user] [user] 60 Oct 25 06:00 .
drwxrwxrwt 43 root root 1240 Oct 25 06:01 ..
lrwxrwxrwx 1 [user] [user] 35 Oct 25 06:00 doxify -> /tmp/doxify-tool2/doxify/bin/doxify
/tmp/doxify-tool2:
total 4
drwxrwxr-x 3 [user] [user] 100 Oct 25 06:00 .
drwxrwxrwt 43 root root 1240 Oct 25 06:01 ..
-rw-rw-r-- 1 [user] [user] 1 Oct 25 06:00 .gitignore
-rw-rw-rw- 1 [user] [user] 0 Oct 25 06:00 .lock
drwxrwxr-x 4 [user] [user] 180 Oct 25 06:00 doxify
--- log ---
+ anyio==4.15.1
+ certifi==2026.10.23
+ doxify==0.1.0 (from file://~/Desktop/repos/doxify)
+ gitdb==4.0.12
+ gitpython==3.2.1
+ h11==0.16.0
+ httpcore==1.0.9
+ httpx==0.28.1
+ idna==3.20
+ markdown-it-py==4.2.0
+ mdurl==0.1.2
+ ollama==0.6.3
+
… [truncated 730 chars]
$ ps -ef | rg 'uv|doxify|install.sh' | rg -v rg || true
(no output)
$ sh install.sh --help | head -20; echo '---'; sh install.sh --bogus >/tmp/bogus.out 2>&1; echo "bogus_exit=$?"; cat /tmp/bogus.out
Doxify installer
Usage:
sh install.sh [--version VERSION] [--help]
Options:
--version VERSION Doxify release to install (default: 0.1.0)
--help Show this help
Environment:
DOXIFY_VERSION Doxify release to install
DOXIFY_SOURCE_DIR Install from a local source tree (maintainer testing)
UV_TOOL_DIR Override uv's tool environment directory
UV_TOOL_BIN_DIR Override uv's executable directory
The installer uses uv to create an isolated per-user install.
It does not use sudo, install Ollama, or download a model.
---
bogus_exit=1
doxify-install: error: unknown argument: --bogus (use --help)
$ uv run ruff check . && uv run pytest -q
All checks passed!
........................................................................ [ 97%]
.. [100%]
74 passed in 1.04s
$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
$ uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --check
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
$ uv run doxify self-check --json
{
"checks": [
{
"detail": "3.10",
"name": "python",
"ok": true
},
{
"detail": "/usr/bin/git",
"name": "git",
"ok": true
},
{
"detail": "0.1.0",
"name": "package",
"ok": true
},
{
"detail": "read/write ok",
"name": "tempdir",
"ok": true
}
],
"ok": true
}
$ uv run ruff format --check src tests
unformatted: File would be reformatted
--> src/doxify/audit.py:56:26
|
55 | choices = ", ".join(status.value for status in ClaimStatus)
- raise ValueError(f"Unknown failure status: {', '.join(sorted(unknown))}. Choose from {choices}.")
56 + raise ValueError(
57 + f"Unknown failure status: {', '.join(sorted(unknown))}. Choose from {choices}."
58 + )
59 | return {ClaimStatus(item) for item in requested}
--------------------------------------------------------------------------------
94 | visible = [
- match.group(1).lower()
- for match in VISIBLE_MARKER_RE.finditer(_visible_text(body))
95 + match.group(1).lower() for match in VISIBLE_MARKER_RE.finditer(_visible_text(body))
96
… [truncated 32440 chars]
$ SENTRY_DSN='' uv run doxify verify --artifact README.md --evidence docs/self-audit/evidence.json --check --fail-on needs_evidence,rejected
Artifact: README.md
┌───────────────────┬────────────────────────────────────────────────────────┐
│ Mode │ readme │
│ Sections │ 9 │
│ Attested sections │ 8 │
│ Claims │ 5 verified, 8 attested, 0 needing evidence, 0 rejected │
└───────────────────┴────────────────────────────────────────────────────────┘
Verification passed.
$ SENTRY_DSN='' uv run doxify self-check --json
{
"checks": [
{
"detail": "3.10",
"name": "python",
"ok": true
},
{
"detail": "/usr/bin/git",
"name": "git",
"ok": true
},
{
"detail": "0.1.0",
"name": "package",
"ok": true
},
{
"detail": "read/write ok",
"name": "tempdir",
"ok": true
}
],
"ok": true
}
$ git diff --check; echo "diff_check=$?"; rg -n '^## ' submission.md
diff_check=0
30:## What I Built
94:## Demo
136:## Code
157:## How I Built It
403:## Why Does Open Innovation Matter?
418:## My Agent Session
426:## The Lazy Payoff
438:## Prize Categories
$ cat .gitignore 2>/dev/null || echo '(no .gitignore)'; printf '\n--- tracked local configs ---\n'; git ls-files .obsidian .cursor .entire .opencode | head -40; printf '\n--- untracked contents ---\n'; find .cursor .entire .opencode -maxdepth 3 -type f -print | sort
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
# Local tool caches
.pytest_cache/
.ruff_cache/
--- tracked local configs ---
.obsidian/app.json
.obsidian/appearance.json
.obsidian/core-plugins.json
.obsidian/workspace.json
--- untracked contents ---
.cursor/hooks.json
.entire/.gitignore
.entire/settings.json
.opencode/.gitignore
.opencode/node_modules/cross-spawn/index.js
.opencode/node_modules/cross-spawn/LICENSE
.opencode/node_modules/cross-spawn/package.json
.opencode/node_modules/cross-spawn/README.md
.opencode/node_modules/detect-libc/index.d.ts
.opencode/node_modules/detect-libc/LICENSE
.opencode/node_modules/detect-libc/package.json
.opencode/node_modules/detect-libc/README.md
.opencode/node_modules/effect/LICENSE
.ope
… [truncated 4690 chars]
$ git status --short --branch
## main...origin/main
M .github/workflows/ci.yml
M .obsidian/workspace.json
M README.md
M pyproject.toml
M src/doxify/cli.py
M submission.md
?? .cursor/
?? .entire/
?? .opencode/
?? install.sh
?? tests/test_cli_version.py
$ git diff --stat; git diff -- src/doxify/cli.py tests/test_cli_version.py install.sh .github/workflows/ci.yml README.md pyproject.toml
.github/workflows/ci.yml | 28 +++++++++++++++++++++++
.obsidian/workspace.json | 15 +++++++------
README.md | 26 ++++++++++++++++++++--
pyproject.toml | 15 +++++++++++++
src/doxify/cli.py | 27 ++++++++++++++++++++++
submission.md | 58 ++++++++++++++++++++++++++++++------------------
6 files changed, 139 insertions(+), 30 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 74e0a7d..bae4ea0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,6 +42,34 @@ jobs:
path: dist/
if-no-files-found: error
+ installer:
+ name: Installer smoke test
+ runs-on: ubuntu-latest
+ env:
+ SENTRY_DSN: ""
+ DOXIFY_SOURCE_DIR: ${{ github.workspace }}
+ UV_TOOL_
… [truncated 4131 chars]
$ cat .opencode/.gitignore 2>/dev/null; cat .opencode/package.json 2>/dev/null; du -sh .opencode .cursor .entire 2>/dev/null
node_modules
package.json
package-lock.json
bun.lock
.gitignore{
"dependencies": {
"@opencode-ai/plugin": "1.19.4"
}
}
63M .opencode
8.0K .cursor
12K .entire
$ git check-ignore -v .opencode/node_modules/zod/index.js .opencode/package-lock.json .entire/settings.json .cursor/hooks.json 2>/dev/null || true
.opencode/.gitignore:1:node_modules .opencode/node_modules/zod/index.js
.opencode/.gitignore:3:package-lock.json .opencode/package-lock.json
$ ls -la .opencode; git status --short --untracked-files=all .opencode | head -20
total 40
drwxr-x--- 4 [user] [user] 4096 Oct 25 05:51 .
drwxrwxr-x 17 [user] [user] 4096 Oct 25 05:49 ..
-rw-rw-r-- 1 [user] [user] 63 Oct 25 05:50 .gitignore
drwxrwxr-x 29 [user] [user] 4096 Oct 25 05:51 node_modules
-rw-rw-r-- 1 [user] [user] 14628 Oct 25 05:51 package-lock.json
-rw-rw-r-- 1 [user] [user] 64 Oct 25 05:51 package.json
drwxr-x--- 2 [user] [user] 4096 Oct 24 23:24 plugins
?? .opencode/plugins/entire.ts
$ cat .opencode/.gitignore; printf '\n--- package.json ---\n'; cat .opencode/package.json; printf '\n--- size ---\n'; du -sh .opencode/node_modules .opencode/plugins/entire.ts 2>/dev/null
node_modules
package.json
package-lock.json
bun.lock
.gitignore
--- package.json ---
{
"dependencies": {
"@opencode-ai/plugin": "1.19.4"
}
}
--- size ---
63M .opencode/node_modules
28K .opencode/plugins/entire.ts
$ find .entire .cursor -maxdepth 2 -type f -print -exec wc -c {} \; | head -40
.entire/.gitignore
58 .entire/.gitignore
.entire/settings.json
94 .entire/settings.json
.cursor/hooks.json
1216 .cursor/hooks.json
$ python - <<'PY'
from pathlib import Path
text = Path('submission.md').read_text()
print('fences', text.count('```'))
print('title', text.splitlines()[2])
print('headings', [line for line in text.splitlines() if line.startswith('## ')])
PY
zsh:1: command not found: python
$ python - <<'PY'
from pathlib import Path
for p in [Path('submission.md'), Path('README.md')]:
text = p.read_text()
paras = [x.strip() for x in text.split('\n\n') if x.strip()]
seen = {}
print(p)
for para in paras:
if len(para) > 80 and para in seen:
print('duplicate:', para[:100])
seen[para] = True
PY
zsh:1: command not found: python
$ sh install.sh --help >/dev/null && echo installer_help_ok; sh install.sh --bogus >/tmp/install-bogus.out 2>&1; code=$?; echo "bogus_exit=$code"; cat /tmp/install-bogus.out
installer_help_ok
bogus_exit=1
doxify-install: error: unknown argument: --bogus (use --help)
$ uv run python - <<'PY'
from pathlib import Path
text = Path('submission.md').read_text()
print('fences', text.count('```'))
print('title', text.splitlines()[2])
print('headings', [line for line in text.splitlines() if line.startswith('## ')])
PY
fences 42
title # A 1B Gemma wrote 47 claims about a repo. My verifier let 13 through.
headings ['## What I Built', '## Demo', '## Code', '## How I Built It', '## Why Does Open Innovation Matter?', '## My Agent Session', '## The Lazy Payoff', '## Prize Categories']
$ uv run python - <<'PY'
from pathlib import Path
for p in [Path('submission.md'), Path('README.md')]:
text = p.read_text()
paras = [x.strip() for x in text.split('\n\n') if x.strip()]
seen = set()
print(p)
for para in paras:
if len(para) > 80 and para in seen:
print('duplicate:', para[:100])
seen.add(para)
PY
submission.md
README.md
$ sh install.sh --help >/dev/null; echo help_exit=$?
help_exit=0
$ sh install.sh --bogus >/tmp/install-bogus.out 2>&1; code=$?; echo "bogus_exit=$code"; cat /tmp/install-bogus.out
bogus_exit=1
doxify-install: error: unknown argument: --bogus (use --help)
$ uv sync --frozen
Checked 32 packages in 0.74ms
$ git diff --check && echo diff_check_ok
diff_check_ok
$ git status --short --branch
## main...origin/main
M .github/workflows/ci.yml
M .obsidian/workspace.json
M README.md
M pyproject.toml
M src/doxify/cli.py
M submission.md
?? .cursor/
?? .entire/
?? .opencode/
?? install.sh
?? tests/test_cli_version.py
$ git add -n .cursor .entire .opencode .obsidian/workspace.json
add '.obsidian/workspace.json'
add '.cursor/hooks.json'
add '.entire/.gitignore'
add '.entire/settings.json'
add '.opencode/plugins/entire.ts'
$ git status --short --untracked-files=all .opencode | head -20
?? .opencode/plugins/entire.ts
$ cat .opencode/.gitignore; printf '\n--- entire settings ---\n'; cat .entire/settings.json
node_modules
package.json
package-lock.json
bun.lock
.gitignore
--- entire settings ---
{
"enabled": true,
"checkpoints": {
"primary": {
"type": "git-refs"
}
}
}
$ git add src/doxify/cli.py tests/test_cli_version.py pyproject.toml && git commit -m "feat(cli): add version flag and package metadata"
[main d4d21b5] feat(cli): add version flag and package metadata
3 files changed, 56 insertions(+)
create mode 100644 tests/test_cli_version.py
file: ~/Desktop/repos/doxify/install.sh
old:
VERSION="${VERSION#v}"
[ -n "$VERSION" ] || fail "version cannot be empty"
new:
VERSION="${VERSION#v}"
[ -n "$VERSION" ] || fail "version cannot be empty"
case "$VERSION" in
*[!0-9A-Za-z._-]*)
fail "invalid version: $VERSION"
;;
esac
Edit applied successfully.
$ sh -n install.sh; sh install.sh --version 'bad/version' >/tmp/install-invalid.out 2>&1; code=$?; echo "invalid_exit=$code"; cat /tmp/install-invalid.out
invalid_exit=1
doxify-install: error: invalid version: bad/version
$ uv run pytest -q tests/test_cli_version.py && uv run ruff check src/doxify/cli.py tests/test_cli_version.py && uv run doxify --version
. [100%]
1 passed in 0.23s
All checks passed!
0.1.0
$ sh -n install.sh && sh install.sh --version 'bad/version' >/tmp/invalid.out 2>&1; code=$?; echo "invalid_exit=$code"; cat /tmp/invalid.out
invalid_exit=1
doxify-install: error: invalid version: bad/version
$ git status --short --branch
## main...origin/main [ahead 1]
M .github/workflows/ci.yml
M .obsidian/workspace.json
M README.md
M submission.md
?? .cursor/
?? .entire/
?? .opencode/
?? install.sh
$ uv run ruff check . && uv run pytest -q
All checks passed!
........................................................................ [ 97%]
.. [100%]
74 passed in 3.73s
$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/doxify-0.1.0.tar.gz
Successfully built dist/doxify-0.1.0-py3-none-any.whl
$ git log --oneline -5 --decorate; printf '\n--- show head ---\n'; git show --stat --oneline HEAD
d4d21b5 (HEAD -> main) feat(cli): add version flag and package metadata
a26a20f (origin/main, origin/HEAD) docs(submission): sharpen Hacktoberfest story and add CPU constraint
afd9b8b feat(observability): add tracing, self-check, and verification action
c1d4a57 docs(submission): add Hacktoberfest Week 3 post
2f2bfdd feat(monitoring): add Sentry error monitoring with MIT license
--- show head ---
d4d21b5 feat(cli): add version flag and package metadata
pyproject.toml | 15 +++++++++++++++
src/doxify/cli.py | 27 +++++++++++++++++++++++++++
tests/test_cli_version.py | 14 ++++++++++++++
3 files changed, 56 insertions(+)
$ git diff --stat HEAD; git diff --name-only HEAD
.github/workflows/ci.yml | 28 +++++++++++++++++++++++
.obsidian/workspace.json | 17 +++++++-------
README.md | 26 ++++++++++++++++++++--
submission.md | 58 ++++++++++++++++++++++++++++++------------------
4 files changed, 98 insertions(+), 31 deletions(-)
.github/workflows/ci.yml
.obsidian/workspace.json
README.md
submission.md
$ uv run pytest -q
........................................................................ [ 97%]
.. [100%]
74 passed in 4.26s
$ git add install.sh .github/workflows/ci.yml README.md && git commit -m "feat(install): add curl installer and smoke test"
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .obsidian/workspace.json
modified: submission.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
.cursor/
.entire/
.opencode/
no changes added to commit (use "git add" and/or "git commit -a")
$ git add install.sh .github/workflows/ci.yml README.md && git commit -m "feat(install): add curl installer and CI smoke test"
[main 523c9e5] feat(install): add curl installer and CI smoke test
3 files changed, 206 insertions(+), 2 deletions(-)
create mode 100755 install.sh
$ git add submission.md && git commit -m "docs(submission): sharpen hook and document installer"
On branch main
Your branch is ahead of 'origin/main' by 3 commits.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .obsidian/workspace.json
Untracked files:
(use "git add <file>..." to include in what will be committed)
.cursor/
.entire/
.opencode/
no changes added to commit (use "git add" and/or "git commit -a")
$ git add submission.md && git commit -m "docs(submission): sharpen Week 3 story and document installer"
[main 792e126] docs(submission): sharpen Week 3 story and document installer
1 file changed, 37 insertions(+), 21 deletions(-)
$ git add .cursor .entire .opencode .obsidian/workspace.json && git diff --cached --stat && git status --short
.cursor/hooks.json | 40 +++
.entire/.gitignore | 5 +
.entire/settings.json | 8 +
.obsidian/workspace.json | 17 +-
.opencode/plugins/entire.ts | 585 ++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 647 insertions(+), 8 deletions(-)
A .cursor/hooks.json
A .entire/.gitignore
A .entire/settings.json
M .obsidian/workspace.json
A .opencode/plugins/entire.ts
$ git add .cursor .entire .opencode && git commit -m "chore(agent): track Entire session hooks"
[main febd291] chore(agent): track Entire session hooks
5 files changed, 647 insertions(+), 8 deletions(-)
create mode 100644 .cursor/hooks.json
create mode 100644 .entire/.gitignore
create mode 100644 .entire/settings.json
create mode 100644 .opencode/plugins/entire.ts
$ git commit -m "chore(agent): track Entire session hooks and workspace state"
On branch main
Your branch is ahead of 'origin/main' by 4 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
The Lazy Payoff
I used Doxify on nextjs-dashboard and on Doxify's own README.
The nextjs-dashboard run returned 13 verified claims and 34 claims that need evidence. That sounds like a bad result. It is the useful part: the tool gave me a review queue instead of 34 confident sentences I would have had to fact-check from memory.
The README run produced a clean README with hidden provenance that CI can check. Scout turns a stack of release notes into the few breaking changes that touch my code, with file and line matches. And scholarship-mcp, the repo that started all this, now has something to compile: 20 commits and no README become a structured draft, a claim ledger, and an interview defense pack.
The design target is a sub-10-minute local compile on an 8 GB CPU laptop, because that is the machine I have. The model is small, the prompts are bounded, and the expensive trust work is deterministic. I would rather ship a 1B model plus a verifier that says "I cannot prove that" than a larger model that says everything confidently.
Slow and steady is the right pace for documentation, because the careful part is the part nobody wants to do by hand.
Prize Categories
Best Use of Gemma. Gemma is the writer inside a compiler: gemma3:1b locally by default, gemma4:31b on Ollama Cloud when the user opts in. The model drafts the narrative; deterministic checks decide what survives.
Best Use of Sentry Agent Tracing. Doxify records opt-in command transactions, pipeline stages, model spans, HTTP spans, token counts, and latency, while explicitly disabling prompt/output collection and redacting secrets and paths. The trace tree above is the shape; self-check --monitor-slug and the nightly workflow turn a health check into a Sentry cron monitor.
Best Use of SerpApi. Scout falls back to SerpApi when GitHub does not have release notes, pulls the page text, and still requires the model's breaking-change quote to exist in that text. --notes FILE keeps the offline path available.
Best Use of GitHub Copilot. The category includes automating your project with GitHub Actions. The whole contribution path is automated: every push and pull request runs lint, 74 tests, a build, and the Doxify evidence gate. A Copilot-authored PR gets the same check as mine. The agent can propose; CI verifies.
Best Use of Entire. I integrated Entire into Doxify's Git workflow to preserve the context behind AI-generated code. By linking agent sessions, prompts, and file changes to commits, Entire makes it easier to trace how implementation decisions were made, revisit previous work, and understand not just what changed, but why.
#devchallenge #hf26challenge #hacktoberfest #opensource #ai #gemma #ollama #sentry #serpapi #githubactions #copilot #entire #portfolio #career #sloth
Top comments (0)