What does docuharnessx do?
DocuHarnessX is a Python package — version 2.0.0 — whose own docstring describes it as a tool to "generate grounded developer documentation from a software repository".
flowchart TB
subgraph people["People"]
actor_operator(["Operator"])
end
subgraph enterprise["This system"]
system["DocuHarnessX"]
end
subgraph external["External"]
external_ci["GitHub Actions"]
external_docs[("Documentation site")]
external_repo[("norandom/DocuHarnessX")]
end
actor_operator -->|"runs CLI"| system
system -->|"reads and cites"| external_repo
external_ci -->|"runs in"| system
system -->|"publishes"| external_docs
class actor_operator person
class system system
class external_ci external
class external_docs store
class external_repo store
classDef person fill:#E8B923,stroke:#7A5100,color:#1A1300
classDef system fill:#A8C8FF,stroke:#1E4BB8,color:#0B1F4A
classDef container fill:#B9D4FE,stroke:#2F62C4,color:#0B1F4A
classDef external fill:#D5DCE6,stroke:#4A5568,color:#1A202C
classDef store fill:#7ED4C0,stroke:#0F766E,color:#042F2E
flowchart TB
subgraph b0["Interface"]
b0m0["CLI"]
b0m1["mcp"]
end
subgraph b1["Application"]
b1m0["assembler"]
b1m1["composition"]
b1m2["deployer"]
b1m3["pipeline"]
b1m4["planning"]
b1m5["review"]
end
subgraph b2["Domain"]
b2m0["analysis"]
b2m1["comprehension"]
b2m2["ontology"]
end
b0 -->|"depends on"| b1
b1 -->|"depends on"| b2
class b0m0,b0m1,b1m0,b1m1,b1m2,b1m3,b1m4,b1m5,b2m0,b2m1,b2m2 container
classDef person fill:#E8B923,stroke:#7A5100,color:#1A1300
classDef system fill:#A8C8FF,stroke:#1E4BB8,color:#0B1F4A
classDef container fill:#B9D4FE,stroke:#2F62C4,color:#0B1F4A
classDef external fill:#D5DCE6,stroke:#4A5568,color:#1A202C
classDef store fill:#7ED4C0,stroke:#0F766E,color:#042F2E
flowchart TB
actor_operator(["Operator"])
subgraph sys["DocuHarnessX"]
subgraph band_interface["Interface"]
container_CLI["CLI"]
container_mcp["mcp"]
end
subgraph band_application["Application"]
container_assembler["assembler"]
container_composition["composition"]
container_deployer["deployer"]
container_pipeline["pipeline"]
container_planning["planning"]
container_review["review"]
end
subgraph band_domain["Domain"]
container_analysis["analysis"]
container_comprehension["comprehension"]
container_ontology["ontology"]
end
end
actor_operator -->|"runs CLI"| container_CLI
container_CLI -->|"depends on"| container_assembler
container_assembler -->|"depends on"| container_analysis
class actor_operator person
class container_CLI container
class container_analysis container
class container_assembler container
class container_composition container
class container_comprehension container
class container_deployer container
class container_mcp container
class container_ontology container
class container_pipeline container
class container_planning container
class container_review container
classDef person fill:#E8B923,stroke:#7A5100,color:#1A1300
classDef system fill:#A8C8FF,stroke:#1E4BB8,color:#0B1F4A
classDef container fill:#B9D4FE,stroke:#2F62C4,color:#0B1F4A
classDef external fill:#D5DCE6,stroke:#4A5568,color:#1A202C
classDef store fill:#7ED4C0,stroke:#0F766E,color:#042F2E
classDiagram
direction LR
class CLI {
<<command>>
ci()
evolve()
hook()
init()
install_ci()
install_hooks()
mcp()
run()
status()
sufficient()
}
class Cadoption {
<<module>>
AdoptionRecord()
declare_sufficient()
load_adoption()
mark_stale()
save_adoption()
}
class Canalyzer {
<<module>>
analyze()
}
class Cdetectors {
<<module>>
detect_artifacts()
detect_build_files()
detect_ci()
detect_docs()
detect_entrypoints()
detect_public_surface()
}
class Cenrich {
<<module>>
enrich()
}
flowchart TB
n0["What does docuharnessx do?"]
n1["__init__.py"]
n2["pyproject.toml"]
n0 --> n1
n0 --> n2
flowchart TB
n0["What does docuharnessx do?"]
n1["docuharnessx"]
n2["__init__.py"]
n3["_ontology.py"]
n4["adoption.py"]
n5["blueprint.py"]
n6["bundle.py"]
n7["pyproject.toml"]
n0 --> n1
n1 --> n2
n1 --> n3
n1 --> n4
n1 --> n5
n1 --> n6
n0 --> n2
n0 --> n7
flowchart TB
page["What does docuharnessx do?"]
subgraph d0["docuharnessx"]
e0["__init__.py"]
end
subgraph d1["repo root"]
e1["pyproject.toml"]
end
page --> e0
page --> e1
What does DocuHarnessX do?
DocuHarnessX is a Python package — version 2.0.0 (docuharnessx/__init__.py:5) — whose own docstring describes it as a tool to "generate grounded developer documentation from a software repository" (docuharnessx/__init__.py:1). In practice it is a CLI-driven pipeline that scans a target repo, decides which software questions deserve documentation, runs bounded model agents to write those pages, and assembles the accepted ones into an MkDocs site. Its pyproject entry point is the console script dhx = "docuharnessx.cli:main" (pyproject.toml:33).
The dhx command surface
The CLI boundary is docuharnessx.cli (cli.py:1). main() (cli.py:1446) dispatches the subcommands registered in _SUBCOMMANDS: run, init, mcp, status, sufficient, evolve, hook, ci, install-hooks, and install-ci (cli.py:98-111). It also supports the bare form dhx <target-repo> --out DIR --config YAML by rewriting the first positional into an implicit run (cli.py:143-172). Boundary failures are typed DocuHarnessXErrors that main prints as <ErrorType>: <message> and maps to a non-zero exit (cli.py:1528-1532).
What dhx run actually does
dhx run validates the target directory first (cli.py:570-587), loads the project vocabulary and a YAML config, then hands off through orchestrate_run to docuharnessx.pipeline.run.run_pipeline (cli.py:765, pipeline/run.py:87). The pipeline is a sequential, explore-first flow (pipeline/run.py:109-121):
scan(repo_path)builds a bounded, deterministicFileInventory(analysis/scanner.py:44-50), excluding noise dirs like.git,node_modules, and__pycache__by name (analysis/scanner.py:60-80).analyze(inventory)turns that inventory into aRepoAnalysis.plan_questions(analysis)deterministically produces aQuestionPlan— bounded, model-free software questions such as "How does this program start?" and "How is this project built and verified?" (planning/questions.py:36-41).write_questions(...)indocuharnessx.composition.explore_writerruns one bounded writer harness per question; a missing model omits every question with reasonno_modelrather than substituting an outline (composition/explore_writer.py:54-58). Accepted pages go into the project's living page store viastore.put(page)(pipeline/run.py:124-125).assemble_question_sitewrites a Material-for-MkDocs source tree under<out>/siteonly when at least one page was accepted (pipeline/run.py:59-84); otherwise the run is an "honest-empty" zero-page report that still exits 0.
The living pages persist as Markdown with YAML front-matter under <project>/.docuharnessx/pages/, keyed by page_filename (pages/store.py:33-34, 59-97). Accepted pages live alongside MkDocs and journals inside .docuharnessx/, and these files are meant to be committed so docs stay versioned with the code.
Adoption: init, ontology, and sufficiency
dhx init is the onboarding path. docuharnessx.ontology_setup.run_init (ontology_setup.py:142) refuses to overwrite an existing config without --force (ontology_setup.py:169-174), builds a Vocabulary from either the shipped default_profile() or interactive answers, serializes it with vocabulary_to_config, writes .docuharnessx/ontology.yaml (ontology_setup.py:65, 166), round-trip validates it via load_vocabulary, and seeds an AdoptionRecord (ontology_setup.py:202-213). The vocabulary model itself — roles, intents, and subject_prefixes as frozen tuples with id-based membership checks (ontology/vocabulary.py:53-70) — and the default profile (10 roles, 13 intents, prefixes component:, tech:, artifact:, topic:) live in the bundled ontology engine, which docuharnessx._ontology deliberately re-exports as a single import site to limit contract drift (_ontology.py:50-58).
The adoption record is a separate file, .docuharnessx/adoption.yaml (ADOPTION_RELPATH, adoption.py:33), holding blueprint name/version, timestamps, a sufficient flag, and an optional harness_snapshot pointer (adoption.py:36-51). load_adoption returns None for a missing file rather than raising (adoption.py:82-99), and mark_stale flips a true sufficiency declaration stale whenever a living page is written (adoption.py:135-140; called from pages/store.py:92-94). The shipped blueprint identity is docuharnessx-default / 2.0.0 (blueprint.py:14-15), and dhx sufficient records an operator's sufficiency declaration via declare_sufficient (adoption.py:115-132; cli.py:1199-1212). dhx status computes coverage — planned vs. living question ids, omissions, and staleness — without any model (status.py:22-40).
Harness composition and the pipeline stages
docuharnessx.bundle.make_docgen (bundle.py:73) is the single composition seam: it builds a HarnessX HarnessConfig by starting from the Control bundle (make_control, with loop-detection thresholds raised to 12/8 for 25–40k-LOC repos, bundle.py:69-70, 117-121), appending the eight pipeline stages with the | operator (control | stages_builder(), bundle.py:131), and rooting a HarnessJournal tracer at the output dir (bundle.py:137). The stage registry STAGES in docuharnessx.stages fixes the canonical order ingest → analyze → classify → plan → write → review → assemble → deploy (stages/__init__.py:60-69), each stage added with strictly positive order so stages sort after pre-existing control processors ("append-don't-replace", stages/__init__.py:108-132). The config stays model-free; the CLI binds the model separately via a ModelConfig (bundle.py:16-17).
Publication, hooks, CI, and evolution
After at least one accepted page, _publish_if_accepted invokes deploy_site with a resolved deploy mode — emit-ci-workflow (default), gh-deploy, or build-only (cli.py:247-258, 687-743). dhx init also installs the fail-open pre-commit hook and CI wiring via install_onboarding (cli.py:1176-1179), and dhx hook / dhx ci are the incremental entry points that skip bot [dhx] commits and runs without credentials (cli.py:1257-1290; hooks.py, ci_policy.py). dhx evolve is the meta loop: it reads traces from .docuharnessx/journals/, only accepts candidates that keep the substance_gate, and writes the evolved processor set to .docuharnessx/harnesses/current.yaml, recording the snapshot on the adoption record (evolve.py:27-60). Finally, dhx mcp launches a stdio MCP server (cli.py:885-945) built on docuharnessx.mcp, letting an MCP client refine generated docs segment-by-segment through tools such as open_workspace.
In one sentence: DocuHarnessX turns a repository into a question-organized, gate-checked, human-readable living documentation site under .docuharnessx/, keeps that documentation in sync through git hooks and CI, and can adapt its own generation harness from run journals — with the actual document writing delegated to per-question model agents on the HarnessX runtime.
Grounding
docuharnessx/__init__.pypyproject.toml