Skip to content

Architecture

Mikhail Deynekin edited this page Oct 14, 2026 · 1 revision

Architecture

How the kit is put together: the component map, the data flow between the four phases, the design decisions behind each tool (every one of which exists because a real run broke without it), the recovery architecture that survives context loss, and the CI pipeline that keeps the kit honest. All statements are traced to source at commit 7e17a11 or to tests executed during this wiki run.

Contents


The shape of the system

The kit is not a program; it is a pipeline an agent walks, with one tool per job and files as the only interface between phases. Nothing is in-memory across phases: each phase reads the previous phase's files and writes its own. That is deliberate — the files are the recovery points.

flowchart LR
    subgraph P1["PHASE 1 — research (~40%)"]
        A1["setup/bootstrap.sh<br/>capability + egress report"] --> A2["git clone SUBJECT"]
        A2 --> A3["scan/repo_scan.py --brief<br/>inventory in 2 minutes"]
        A3 --> A4["lib/serve.py + probe/webapp_probe.py<br/>RUN the subject"]
        A4 --> A5["research/*.md + facts.json<br/>+ sources.md sections A-D"]
    end
    subgraph P2["PHASE 2 — architecture (~3%)"]
        B1["page tree in PROGRESS.json"] --> B2["ban.json<br/>(from sources.md section D)"]
        B2 --> B3["terms.json<br/>(from the glossary)"]
    end
    subgraph P3["PHASE 3 — writing (~45%)"]
        C1["scaffold/new_page.py --init<br/>QA-clean skeletons"] --> C2["fill pages"]
        C2 --> C3["qa/fix_toc.py<br/>rebuild every Contents block"]
    end
    subgraph P4["PHASE 4 — QA + delivery (~12%)"]
        D1["qa/qa_wiki.py --self-test<br/>then --strict to 0 errors"] --> D2["qa/verify_commands.py<br/>re-run documented commands"]
        D2 --> D3["publish/publish.sh<br/>commit + bundle + PUBLISH.md"]
        D3 --> D4["REPORT.md<br/>incl. limits of analysis"]
    end
    P1 --> P2 --> P3 --> P4
Loading

The effort split is measured, not aspirational: the reference run (MD.Viewer, 22 files, 73,013 words, 0 QA errors) spent ~40% of its effort on research, ~3% on wiki architecture, ~45% on writing and ~12% on QA and delivery. Phase 3 is only fast because Phase 1 was thorough.

Directory layout and responsibilities

github-wiki-llm-writer/
├── SKILL.md          # agent entry point (Agent Skills frontmatter + workflow)
├── AGENTS.md         # phase-to-tool mapping, tool table, workspace discipline
├── PROMPT.md         # ready-to-paste task prompt; only the header block changes
├── PUBLISHING.md     # verified publishing flow + the two traps
├── CHANGELOG.md      # the version history (no git tags exist)
├── REWORK-PLAN.md    # completed hardening plan from the field report (evidence trail)
├── setup/            # bootstrap.sh · PITFALLS.md · check_runtime.py · snapshot.sh
├── scan/             # repo_scan.py
├── lib/              # req.py · serve.py · session.py · evidence.py
├── probe/            # webapp_probe.py (+ SUITES registry)
├── scaffold/         # new_page.py
├── qa/               # qa_wiki.py · fix_toc.py · verify_commands.py
├── publish/          # publish.sh
├── templates/        # ban.json · terms.json · STATE/PROGRESS/REWORK-STATE templates
├── examples/         # reference-run artefacts + run2-mdscribe (1571-line battery)
└── .github/workflows/selftest.yml

Measured size at 7e17a11: 44 files, ~621 KB, 15 code files, 5,523 code lines; the largest logic files are qa/qa_wiki.py (802), probe/webapp_probe.py (457), scan/repo_scan.py (433), scaffold/new_page.py (353), lib/serve.py (325). Dependencies: none declared — no manifest of any ecosystem exists; the floor is Python 3.8+ and bash, with PHP only when the subject is a PHP app.

Layering

Three layers, with strict dependency direction:

flowchart TB
    subgraph L3["orchestration"]
        W["webapp_probe.py<br/>(batteries + suites)"]
        N["new_page.py"]
        Q["qa_wiki.py / fix_toc.py"]
        P["publish.sh"]
    end
    subgraph L2["capabilities"]
        S["serve.py<br/>ServedApp"]
        R["req.py<br/>Response tuple"]
        SE["session.py<br/>SessionClient"]
        E["evidence.py<br/>write_evidence"]
        SC["repo_scan.py"]
    end
    subgraph L1["substrate"]
        PY["python stdlib: urllib, socket, subprocess,<br/>json, re, tempfile, pathlib"]
        SH["bash + git + (php for PHP subjects)"]
    end
    W --> S
    W --> R
    W --> E
    S --> R
    SE --> PY
    Q --> SH
    P --> SH
    N --> PY
    SC --> SH
    S --> SH
    R --> PY
    E --> PY
Loading

Rules the layering enforces in practice:

  • req.py is the only HTTP primitive; serve.py, session.py and webapp_probe.py all build on the same (status, headers, body) contract, so a status code observed anywhere is comparable everywhere.
  • qa_wiki.py never executes wiki code: shell blocks go through bash -n with the text on stdin (not as an argument), Python blocks through compile(). The auditor is safe to run on untrusted prose.
  • fix_toc.py imports github_slug/mask_code from qa_wiki.py when shipped inside qa/, so the anchor algorithm has exactly one implementation; the standalone fallback exists only for copies outside the tree.
  • No layer makes any LLM API call. The intelligence lives in the agent operating the kit; the kit's job is to make every claim checkable.

Data contracts between phases

Artefact Producer Consumer Contract
research/scan.json repo_scan.py agent (Phase 1 notes) full inventory: git, license, deps, files, symbols, hosts, signals, config keys
staged instance serve.py probe batteries clean docroot, verified-free port, process-group teardown
research/probe.{json,md} webapp_probe.py Phase 3 pages, sources.md §B every request: status, ms, bytes, headers, 180-char excerpt, expectation match
research/facts.json agent Phase 3, recovery every value carries source: code:file:line | readme | git | test:name | web:url
research/sources.md §D agent ban.json claims deliberately NOT made, with reasons
PROGRESS.json agent (Phase 2) new_page.py --progress, resuming agents page tree + groups + style contract + do_not_claim
wiki *.md (spaces in names) Phase 3 qa_wiki.py, publish.sh filenames with spaces; [[Page Name]] links; computed anchors
QA JSON qa_wiki.py --json PROGRESS.json qa block errors/warnings/infos/stats incl. per-page table
commit + bundle + PUBLISH.md publish.sh the owner branch master, verified bundle, three push options, never force-push
REPORT.md agent (Phase 4) the owner inventory, verification methods, doc-vs-code findings, limits of the analysis

Design decisions (and the incident behind each)

Decision Incident that forced it
ServedApp stages a clean copy on a verified-free port and raises on a busy preferred port A hand-rolled php -S 127.0.0.1:8080 & died on "Address already in use"; every subsequent test hit the OLD server with OLD state, producing plausible, false conclusions (PITFALLS A1)
req.py returns 4xx/5xx as data and never follows redirects A probe suite that raises on 404 silently loses exactly the cases it exists to document; a login 302 is part of the contract
session.py keeps its own cookie jar dict(resp.headers) collapses multiple Set-Cookie headers — stateful flows break invisibly
CSRF token goes to body field AND header, never URL The MD.Scribe subject rejected query-string tokens outright (403, no hint why)
write_evidence() falls back instead of raising A field run's final evidence write hit FileNotFoundError while the shell saw the directory fine a second later (PITFALLS B11)
check_runtime.py exits 2 on a runtime-floor mismatch Probing a subject under PHP 8.2 that needs 8.3 measures a corpse — every "finding" is fiction (PITFALLS B10)
qa_wiki.py --self-test is mandatory doctrine Three checker bugs produced 381, 12 and 235 phantom errors in real runs; "a checker that cannot fail is worse than no checker"
fix_toc.py replaces only the list block, with a word-count guard and --bak The first TOC regenerator ate a page's lead paragraph, badges and an alert on a page lacking a --- separator (PITFALLS D8)
Scaffold derives every link from the actual page list Hardcoded [[Glossary]]/[[Getting Started]] in templates became site-wide dead links for any other page list (PITFALLS D7)
Ban/terms match a scrubbed view with negation downgrade and exemptions A field run produced 10 QA errors and zero real problems — negations, refutations, verbatim subject strings, anchor slugs and domain terms all fired (PITFALLS C8)
publish.sh never prompts, never force-pushes, configures origin on fallback init Sandboxes have no controlling terminal; wiki repos carry the owner's Initial commit; snapshots strip .git/config (PITFALLS A3/B6)
.gitignore refuses run workspaces, and CI enforces it A 184-file / 4.4 MB run workspace sat one git add -A away from being published with the kit (CHANGELOG 1.2.1)

Recovery architecture

Long agent runs get compacted or interrupted. The kit treats resumability as a first-class property:

flowchart LR
    L["context lost /<br/>session interrupted"] --> O["1. ls workspace + head STATE.md"]
    O --> R["2. read REWORK-STATE.md log<br/>(append-only, newest last)"]
    R --> E["3. rebuild environment<br/>(bootstrap.sh, clone)"]
    E --> V["4. re-run last verification<br/>command from the log"]
    V --> N["5. continue from 'Next action'"]
    F["research/facts.json +<br/>sources.md"] -.->|"resume WRITING without<br/>re-reading subject code"| N
Loading

Five artefacts carry the state: STATE.md (orientation: phase, layout, confirmed facts, checklist, next action), PROGRESS.json (per-page status/words, style contract, do_not_claim, QA numbers, delivery status), research/ (the five Phase-1 files — with facts.json + sources.md a fresh agent resumes writing without re-reading the subject), REWORK-STATE.md (resume recipe + environment rebuild + sandbox quirks + append-only log), and snapshot.sh git snapshots at phase boundaries so a bad bulk edit costs minutes, not hours. The doctrine: update these at phase boundaries, not at the end.

This wiki run exercised the recovery layer for real: the run's REWORK-STATE.md log recorded a sandbox path-rewriting discovery mid-Phase-1 (the harness rewrote /home/user inside command strings but not inside script files — PITFALLS B4 manifesting at the tooling layer), which a resuming agent would otherwise have re-debugged from scratch.

CI architecture

.github/workflows/selftest.yml (triggers: push to main/master, PRs, manual; matrix Python 3.9/3.11/3.12, fail-fast: false) runs ten steps that mirror the kit's own doctrine — including negative controls, because a gate that cannot fail is theatre:

Step What it proves
Python ast.parse on every .py; bash -n on every .sh; json.load on every tracked .json nothing ships syntactically dead (the JSON step exists because examples/research/facts.json once shipped as Markdown with a fence inside)
--help on all 11 Python tools + a NOTE for any .py neither exercised nor excused a new tool cannot ship with a broken CLI invisibly (six tools once were invisible to CI)
qa_wiki.py --self-test the auditor can fail — 22 detections
fix_toc.py --self-test, evidence.py --self-test, session.py --help the 1.1.0/1.2.0 tools are exercised (their live PHP run is a Phase-1 task, not CI)
scaffold round-trip × 3 page lists → qa_wiki.py --strict skeletons are QA-clean by construction, including the degenerate one-page case
--ban/--terms contract fixtures a prose phrase fires, an inline-code phrase stays silent, an all-meta ban file warns — the step cannot pass vacuously (the 1.2.1 fix after inline-code masking turned it into a no-op)
documented paths exist every file the docs link is present
workspace-refusal no v3.*/, uploads/, incoming/, workspace-fallback/ present, unignored or tracked

Per CHANGELOG 1.2.1, the full gate was verified on a real GNU/Linux host (Debian 12, bash 5.2.15, Python 3.11.2) under three shells, with negative controls: a dead ban loader, a reverted structural-key filter, a deleted documented file, an unignored v3.2/, and a tool with a broken --help each make their step exit non-zero. This wiki run replicated the runnable steps locally (T4–T8): all green.

What the architecture deliberately does not do

  • No daemon, no service, no state of its own. Every tool is a one-shot process; state lives in files you can read, diff and back up.
  • No LLM calls. The kit cannot "write" anything; scaffolds are templates with slots. This keeps the output auditable and the dependency surface at zero.
  • No language lock-in at the transport level. The batteries speak HTTP; ServedApp speaks a command template. PHP is the default, not a constraint (--serve-cmd 'node {root}/server.js', 'python3 -m http.server {port} -d {root}'). CLI-shaped subjects skip serve.py entirely and shell out — the reusable part is the shape: declarative cases → evidence JSON → Markdown tables.
  • No trust in its own templates. The scaffold round-trip and the ban/terms fixtures exist because the kit's own templates shipped bugs (D7: hardcoded footer links; 1.2.1: the template banned its own structural keys).

See also

Clone this wiki locally