Exegete
=======

A qualitative analysis application you use in conversation with an AI
assistant, compatible with QualCoder. It runs as a Model Context Protocol
server.

Copyright (C) 2025-2026 Niccolò Tempini

Exegete (formerly qualcoder-mcp) is free software: you can redistribute
it and/or modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version (SPDX:
LGPL-3.0-or-later).

Exegete is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
General Public License for more details.

The licence texts ship with this program. COPYING.LESSER is the GNU
Lesser General Public License, version 3. legal/GPL-3.0.txt is the GNU
General Public License, version 3, whose terms the Lesser licence
incorporates.

This licence applies from version 0.13. Every release up to and
including 0.12.1 was published under the MIT License, and this
project's own code in those releases remains available under those
terms. Those releases also contained some of the QualCoder-derived
items listed below; those items were always under QualCoder's licence,
LGPL-3.0-or-later, whatever those releases declared.


Code derived from QualCoder
===========================

Exegete is a separate program that reads and writes projects in the
file format of QualCoder (https://github.com/ccbogel/QualCoder). It does not
include QualCoder. It contains a small number of routines and values
taken from QualCoder, so that its results match QualCoder's exactly. It
also restates facts of QualCoder's file format, so that projects stay
compatible. This section lists every such item. The list comes from an
audit of this repository against QualCoder's source and history, made
in September 2026. The audit searched for citations of QualCoder, for
strings shared with it and for similar code, and checked that the list
was complete. Code written for this project to give QualCoder's
results, without carrying QualCoder's code, is not listed.

QualCoder is copyright Colin Curtain and contributors. The headers of
its source files name the authors as Colin Curtain, Kai Dröge, Justin
Missaghieh--Poncet and Lorenzo Salomón. QualCoder is licensed under the
GNU Lesser General Public License, version 3 or (at your option) any
later version (LGPL-3.0-or-later). This project uses the items below
under that licence.

How to read an entry. "Here" is the file in this repository and the
functions, classes or names that carry the item. "From" is the
QualCoder file and lines. Unless the entry says otherwise, the file is
under src/qualcoder/ in QualCoder's master branch at commit
9bddf17f86aa457aff72c3a38dbc10a37f296ad2. An entry may instead give the
file at the 3.8.2 release tag (marked 3.8.2), at the 4.0 tag (the
release of 2 October 2026), or from the root of QualCoder's repository.
"Author", where given, is what QualCoder's file header or git history
records for those lines. How a git identity maps to a person is for the
QualCoder project to confirm. "Why" is the reason the item was copied.
An entry marked withdrawn keeps its number, so that the entries'
references to each other stay true; the code it described is no longer
in Exegete.


Routines in the package
-----------------------

1. QualCoder's memo privacy helpers (copied verbatim)
   Here: src/exegete/memo_privacy.py, PERSONAL_NOTE_MARK,
   _SEPARATOR_CHARS, split_public_private_memo, extract_ai_memo and
   merge_public_memo.
   From: ai_memo.py, lines 28-59.
   Author: Kai Dröge (ai_memo.py's header; QualCoder's git history
   records all 32 lines under his identity kaixxx).
   Why: to honour QualCoder 4.0's '#####' private-memo convention on
   every memo Exegete reads or writes, exactly as QualCoder does.

2. QualCoder's "Kappa" value (copied verbatim)
   Here: src/exegete/coder_comparison.py, kappa_qualcoder.
   From: reports.py, lines 1140-1151 (the same expression is at
   report_compare_coder_file.py, lines 817-829).
   Author: Colin Curtain (QualCoder's git history, 9 of the 12 lines).
   Why: the value must be the number that QualCoder's Coder comparison
   report displays. A floating-point result is identical only when the
   same operations run in the same order, so the expression is
   QualCoder's.

3. QualCoder's coder comparison percentages (closely ported)
   Here: src/exegete/coder_comparison.py, statistics.
   From: reports.py, lines 1102-1107.
   Why: the five headline percentages are rounded as QualCoder rounds
   them, so that they equal QualCoder's to the last digit. One of them is
   100 minus the agreement already rounded. That needs QualCoder's
   expressions, in QualCoder's order.

4. QualCoder's coder comparison counting (closely ported)
   Here: src/exegete/coder_comparison.py, qualcoder_report_values.
   From: reports.py, lines 1061-1115.
   Why: when one coder's segments of a code overlap, QualCoder counts
   differently from Exegete. These values state exactly what
   QualCoder's report would show then. They are a disclosure, never the
   headline number.

5. QualCoder's palette matcher (closely ported)
   Here: src/exegete/database.py, snap_to_palette.
   From: color_selector.py, lines 144-162 (color_matcher).
   Author: recorded in QualCoder's git history under the identity
   "QualCoder Developer".
   Why: a colour the assistant gives snaps to the colour QualCoder
   itself would choose, ties included, which needs QualCoder's metric and
   its scan in its order.

6. QualCoder's coding editor span walk (closely ported) (withdrawn)
   Withdrawn in 0.15.1: the code it described was removed.

7. QualCoder's sub-code descendant check (copied verbatim)
   Here: src/exegete/database.py,
   QualcoderDatabase.code_is_descendant.
   From: code_tree.py, lines 619-641.
   Author: Salomon-Mazatlan (a git identity in QualCoder's history).
   Why: to refuse a move or a merge that would put a code under its own
   descendant, by the same test QualCoder applies.

8. QualCoder's sub-code branch collector (closely ported)
   Here: src/exegete/database.py, QualcoderDatabase.get_branch_cids.
   From: code_tree.py, lines 796-818.
   Why: a code and all its sub-codes, collected as QualCoder collects
   them for a cascade delete.

9. QualCoder's code path text (closely ported)
   Here: src/exegete/database.py, QualcoderDatabase.code_path.
   From: memo.py, lines 255-274.
   Author: Salomon-Mazatlan (a git identity in QualCoder's history).
   Why: to render "Category > ... > Code" exactly as QualCoder's memo
   dialog renders it.

10. QualCoder's merge memo provenance block (closely ported)
    Here: src/exegete/database.py, QualcoderDatabase.merge_codes,
    QualcoderDatabase.merge_category and _append_provenance_block.
    From: code_tree.py, lines 1410-1417 and 1528-1536.
    Author: Salomon-Mazatlan (a git identity in QualCoder's history,
    for lines 1410-1417).
    Why: a memo merged here reads exactly as one merged in QualCoder:
    the same labels, field order and joining of the two memos.

11. QualCoder 4.0's methodology vocabulary (closely ported), and the
    order and ideas of four sections of the assistant's brief (followed
    in this project's own words)
    Here: src/exegete/server.py, METHODOLOGY_VOCABULARY and
    explain_ai_coding_tools.
    Here: src/exegete/server.py, BRIEF_FULL.
    From: ai_prompts/_agent.md, lines 81-88; for the brief, the same
    file on QualCoder's main branch at commit 67cbb8e (last changed at
    bb71301, 1 September 2026), lines 5-10 (its principles), 46-58
    (when to ask and when to act), 60-79 (reaching the data) and 92-110
    (tone and proactiveness).
    Author: kaixxx (a git identity in QualCoder's history, which
    ai_memo.py's header gives as Kai Dröge's).
    Why: to offer the assistant QualCoder 4.0's four-way methodological
    gate (allow, allow_with_caveat, reframe_and_ask, refuse) in
    QualCoder's terms. The four labels are QualCoder's, and their
    definitions closely paraphrase its prompt. The brief's sections 2
    (principles), 12 (when to ask and when to act), 10 (finding your way
    in the data) and 13 (reporting to the researcher) follow those four
    sections of QualCoder's brief for its own assistant, in order and in
    idea. They are in this project's own words: no run of more than four
    words is shared with QualCoder's file. Where Exegete differs by
    design (the approval of each item, working only through the tools),
    the brief says so.

12. QualCoder's journal-name loop (closely ported)
    Here: src/exegete/server.py, _pseudonymise_journal_attempt.
    From: code_pdf.py, lines 6035-6049.
    Why: to find a free journal name for the pseudonymisation audit
    entry by the same sequence of candidates as QualCoder, and to give
    up at the same point.

13. QualCoder's invalid file-name test (copied verbatim)
    Here: src/exegete/database.py, file_name_is_invalid_upstream.
    From: manage_files.py, line 2595 (the same test is at lines 1966
    and 3492).
    Author: recorded in QualCoder's git history under the identity
    "QualCoder Developer".
    Why: QualCoder 4.0 treats a file name as invalid when it is empty or
    holds only spaces or only dots, and renames such a file every time
    Manage Files opens. Exegete refuses such a name by QualCoder's own
    test, so the two programs agree on which file names are invalid.

14. QualCoder's REFI-QDA file type for a text with no stored file
    (closely ported)
    Here: src/exegete/database.py, refi_declared_text_type.
    From: refi.py, lines 3160-3167 (3.8.2 refi.py, lines 3068-3075).
    Author: recorded in QualCoder's git history under the identity
    "QualCoder Developer".
    Why: rename_file refuses a name that would change the file type that
    QualCoder's REFI-QDA export declares for such a text. To know that
    type, Exegete reads it from the name by QualCoder's steps.


Values in the package
---------------------

15. QualCoder's colour palette (120 colours)
    Here: src/exegete/database.py, QUALCODER_COLORS.
    From: color_selector.py, lines 52-65.
    Why: a code created here takes its colour from QualCoder's own
    palette, in QualCoder's order.

16. QualCoder's backup exclusion patterns
    Here: src/exegete/database.py, QUALCODER_BACKUP_IGNORE_PATTERNS.
    From: app.py, lines 1619-1625.
    Why: backups leave out the same regenerable files QualCoder's own
    backups leave out.

17. The tables QualCoder harvests coder names from
    Here: src/exegete/database.py, HARVEST_OWNER_TABLES.
    From: app.py, lines 1481-1494.
    Why: coder names are checked against the same tables, in the same
    order, as QualCoder's own harvest.

18. QualCoder's pseudonym minimum lengths
    Here: src/exegete/pseudonymise.py, MIN_ORIGINAL_CHARS and
    MIN_PSEUDONYM_CHARS.
    From: pseudonyms.py, lines 72 and 76.
    Why: the same minimum lengths as QualCoder's pseudonym dialog.

19. The coder names QualCoder writes as the owner of a row
    Here: src/exegete/database.py, KNOWN_AI_ASSISTANT_OWNER.
    Here: src/exegete/new_project.py, SPEAKER_CODER_NAME.
    Here: src/exegete/server.py, SPEAKER_SYSTEM_CODER.
    From: ai_mcp_server.py, line 85; speakers.py, line 47.
    Why: to recognise rows written by QualCoder's own assistant and by
    its speaker coder.

20. QualCoder 4.0's segment budget and sampling names
    Here: src/exegete/server.py, MAX_SEGMENT_CHARS,
    SEGMENT_STRATEGIES, _resolve_exclude_code_ids, get_coded_segments
    and prune_backups.
    From: ai_mcp_server.py, lines 69-70, 1693, 3587, 3757 and 5358.
    Why: to align these with QualCoder 4.0's own MCP server. They are
    the ceiling on the segment budget, and QualCoder 4.0's default budget
    of 8000 characters, which the get_coded_segments description
    recommends. They are also the names of the sampling strategies, and
    the parameter and field names exclude_code_ids, hit_max_char_limit
    and requires_confirmation.

21. QualCoder's journal-name attempt limit
    Here: src/exegete/server.py, JOURNAL_NAME_ATTEMPTS.
    From: code_pdf.py, line 6047.
    Why: to give up after the same number of attempts as QualCoder.

22. QualCoder's coded-segments CSV headings
    Here: src/exegete/server.py, export_coded_segments_report.
    From: report_codes.py, lines 919-931.
    Why: the CSV export has the same columns as QualCoder's coding report
    export.

23. QualCoder's frequencies CSV headings
    Here: src/exegete/server.py, export_frequencies_csv.
    From: reports.py, lines 226, 233, 338 and 394.
    Why: the frequencies CSV (file name, headings and cid:/catid: cells)
    reads like QualCoder's frequencies export.

24. QualCoder's cascade-delete warning
    Here: src/exegete/database.py,
    QualcoderDatabase.preview_delete_code and QualcoderDatabase.delete_code.
    Here: src/exegete/server.py, delete_code.
    From: code_tree.py, lines 841-843.
    Why: to warn that sub-codes will also be deleted, in QualCoder's
    words.

25. QualCoder's merge refusal message
    Here: src/exegete/database.py,
    QualcoderDatabase.preview_merge_codes.
    From: code_tree.py, line 1509.
    Why: the refusal of a merge into a code's own descendant, in
    QualCoder's words.

26. QualCoder's reserved attribute names
    Here: src/exegete/database.py,
    QualcoderDatabase.RESERVED_ATTRIBUTE_NAMES.
    From: add_attribute.py, lines 50 and 77.
    Why: never to create an attribute that collides with QualCoder's
    reference-manager attributes.

27. The method literature QualCoder's prompt library cites
    Here: src/exegete/server.py, METHODS_GUIDANCE.
    From: in ai_prompts/text-analysis/,
    themes-generation-friese-2024.md, line 7;
    reconstructive-srp-lieder-schaffer-2024.md, line 5;
    interactive-brainstorming-with-ai.md, line 6.
    Why: to point researchers to the same publications; the references
    are to third-party works, as QualCoder lists them.

28. The name QualCoder gives a file entry whose name is invalid
    Here: src/exegete/database.py, file_name_problem.
    Here: src/exegete/server.py, _file_rename_precheck and
    rename_file.
    From: manage_files.py, line 1967.
    Why: to say in QualCoder's words what it does with such a name
    (unnamed_file_<id>). Exegete also refuses a new name that would take
    the name QualCoder is about to give another file.

29. Two menu labels of QualCoder's Manage Files
    Here: src/exegete/database.py, _ENDING_STILL_POSSIBLE.
    Here: src/exegete/server.py, RENAME_FILE_NOTE, rename_file and
    _stored_copy_block.
    From: manage_files.py, lines 1081 and 1087 (3.8.2 manage_files.py,
    lines 511 and 517).
    Why: to point the researcher to QualCoder's own commands, "Rename
    database entry" and "Import linked file", by the words on its menu.

30. Labels of QualCoder's menus and buttons named in Exegete's answers
    Here: src/exegete/database.py, PDF_PROBLEM_MESSAGES and
    QualcoderDatabase.search_files.
    Here: src/exegete/server.py, get_project_summary and
    _opening_in_qualcoder_40.
    From: GUI/ui_main.py, lines 368 and 379; code_pdf.py, lines
    2018-2020 and 5792; __main__.py, lines 2034-2035.
    Why: to point the researcher to QualCoder's own commands by the
    words on its menus and buttons: "Open Project" under Project,
    "Restructure" in the PDF view, and "Keep" or "Switch" when
    QualCoder asks about a coder name.

31. QualCoder's colours that take a light text (copied values)
    Here: src/exegete/reading_copy.py, QUALCODER_WHITE_TEXT and
    text_colour.
    From: color_selector.py, lines 37-49 (TextColor).
    Why: a reading copy draws each coded passage in the text colour that
    QualCoder's own HTML export of a coded file gives it. That export is
    in code_text.py, lines 3504-3621. The reading copy follows its rules
    without copying its code.


Facts of QualCoder's file format in the package
-----------------------------------------------

These are facts of QualCoder's file format, restated so that projects
stay compatible. They are the table and column layouts, file names,
conventions and rules that a program must follow for QualCoder to open,
read and trust a project Exegete has written. They are restated in this
project's own code. Where the statement is a table or column list, it
is the one the format dictates.

32. QualCoder 3.8.2's lock file: its name, timeout, content and
    heartbeat interval.
    Here: src/exegete/database.py, QUALCODER_LOCK_FILENAME,
    QUALCODER_LOCK_TIMEOUT, LOCK_REFRESH_SECONDS, ProjectLock and
    hold_project_lock.
    From: 3.8.2 __main__.py, lines 131, 133, 151-159, 2546-2547 and 2637.

33. The names of QualCoder's coder-visibility views, and the four
    tables they show.
    Here: src/exegete/database.py, VISIBILITY_VIEWS and
    QualcoderDatabase.PSEUDONYMISE_MEMO_OWNED.
    From: app.py, lines 1519, 1530, 1541 and 1552.

34. The tables and columns that tell QualCoder's schema versions apart.
    Here: src/exegete/database.py, SchemaCapabilities,
    QualcoderDatabase._probe_capabilities and
    QualcoderDatabase.write_support.
    From: __main__.py, lines 2296-2346.

35. The media path prefixes that tell text, PDF, image, audio and video
    sources apart, and what a PDF source with no text layer holds. After
    QualCoder 4.0's import, it holds only whitespace, by the test that
    import applies. After a 3.8.2 import, it holds the PDF file itself,
    decoded as text.
    Here: src/exegete/database.py, _detect_file_type,
    pdf_text_problem and QualcoderDatabase.pdf_text_problems.
    Here: src/exegete/server.py, _stored_copy_block and
    stored_file_name.
    From: manage_files.py, lines 615-619, 1429-1434 and 3301-3308
    (3.8.2 manage_files.py, lines 2010-2020).

36. The project folder layout, the name of QualCoder's AI folder
    (ai_data) and the project table's "about" marker.
    Here: src/exegete/database.py, validate_qda_path,
    QualcoderDatabase._check_version and AI_DATA_FOLDER.
    Here: src/exegete/new_project.py, SUBFOLDERS, CREATED_FOLDERS and
    about_line.
    From: 3.8.2 __main__.py, lines 306, 2635 and 2700-2702; __main__.py,
    lines 1767-1776, 1991 and 2011-2015; at the 4.0 tag, __main__.py,
    lines 2193-2201, 2893 and 2895, ai_vectorstore.py, lines 576-577,
    and ai_chat.py, lines 1693-1695.
    Why: Exegete makes an empty ai_data folder when it creates a project
    or copies one that has none, so that QualCoder 4.0 finds the folder
    the first time it opens the project. This is a named departure:
    QualCoder's New Project makes only the four folders.

37. The name, entry shape and write format of pseudonyms.json.
    Here: src/exegete/database.py, PSEUDONYMS_JSON_NAME,
    read_project_pseudonyms and read_project_pseudonyms_with_raw.
    Here: src/exegete/server.py, _pseudonyms_json_merge and
    _write_pseudonyms_json_tmp.
    From: pseudonyms.py, lines 64, 90, 92-93 and 122-123.

38. The row layouts of categories, codes and text sources.
    Here: src/exegete/database.py, QualcoderDatabase._hierarchy_maps,
    QualcoderDatabase.fingerprint_rows_category, QualcoderDatabase.add_code
    and QualcoderDatabase.import_text_file.
    From: view_graph.py, line 4492; code_tree.py, lines 753-755;
    ai_mcp_server.py, lines 1522-1525; __main__.py, lines 1791-1792 and
    1816-1824.

39. Where QualCoder's own writers leave attribute placeholder rows.
    Here: src/exegete/database.py, QualcoderDatabase.import_text_file,
    QualcoderDatabase.add_case and QualcoderDatabase.add_attribute_type.
    From: 3.8.2 manage_files.py, lines 1385-1392; 3.8.2 cases.py, lines
    578-589; 3.8.2 attributes.py, lines 123-146.

40. QualCoder's unique keys, its case links, its move update and its
    rename updates.
    Here: src/exegete/database.py, QualcoderDatabase.find_text_coding,
    QualcoderDatabase.link_file_to_case,
    QualcoderDatabase.move_code_to_category,
    QualcoderDatabase._pseudonymise_collisions,
    QualcoderDatabase._pseudonymise_counts,
    QualcoderDatabase._rename_row, QualcoderDatabase.rename_case and
    QualcoderDatabase.rename_file.
    Here: src/exegete/pseudonymise.py, unique_constraint_collisions.
    From: __main__.py, lines 1790-1792, 1800-1801, 1813-1814 and
    1819-1821; code_tree.py, line 1245; cases.py, line 718;
    manage_files.py, line 1502; 3.8.2 manage_files.py, lines 762-764
    and 787; 3.8.2 case_file_manager.py, lines 207-211; 3.8.2 cases.py,
    line 647.

41. The graph clean-up after a delete and the sub-code reparenting after
    a merge, and where a saved graph keeps a category's own node and the
    lines that end on it.
    Here: src/exegete/database.py,
    QualcoderDatabase._cleanup_graph_rows_for_cid,
    QualcoderDatabase._code_graph_preview,
    QualcoderDatabase.merge_codes and
    QualcoderDatabase._CATEGORY_GRAPH_ROWS.
    From: code_tree.py, lines 856-858 and 1565-1570; __main__.py, lines
    1832-1849; view_graph.py, lines 2040, 4959-4963 and 5020-5023.

42. The column order of QualCoder's table reads, and its value_type
    message.
    Here: src/exegete/database.py,
    QualcoderDatabase.get_coded_text_segments,
    QualcoderDatabase.search_files, QualcoderDatabase.list_attribute_types,
    QualcoderDatabase._pseudonymise_rows and
    QualcoderDatabase.add_attribute_type.
    From: __main__.py, lines 1791-1792 and 1819; app.py, line 1530;
    attributes.py, line 78; code_text.py, lines 6174 and 6181;
    ai_mcp_server.py, line 3089.

43. The names of QualCoder's sidecar files, of its _BKUP_ backup
    folders with the hour stamp in them, and of its settings folder.
    Here: src/exegete/database.py, _QUALCODER_BACKUP_STAMP,
    backup_time_from_name and QualcoderDatabase.PSEUDONYMISE_SIDECARS.
    Here: src/exegete/new_project.py, BACKUP_MARKERS,
    backup_siblings and check_parent_folder.
    Here: src/exegete/server.py, discover_projects,
    _collect_backups, restore_backup, _create_project_place_refusal and
    _workspace_start_problem.
    From: view_av.py, lines 149 and 1366-1380; speakers.py, lines
    712-718; app.py, lines 46 and 1611-1612; __main__.py, lines
    2582-2590 (3.8.2 __main__.py, line 205).

44. How a REFI-QDA file carries a project for QualCoder's importer:
    the codebook's nesting of sub-codes, the whole codebook, one user
    per coder, the cases with their variables and links to whole files,
    and journal entries and annotations as notes (format facts).
    Here: src/exegete/refi_export.py, _Users,
    RefiQdaExporter._add_codebook_section,
    RefiQdaExporter._read_attributes,
    RefiQdaExporter._add_cases_section,
    RefiQdaExporter._add_sources_section and
    RefiQdaExporter._add_notes_section.
    From: refi.py, lines 242-254, 3228-3248 and 3279-3290 (the nesting
    of sub-codes); 3209 and 3264-3291 (every code and category);
    3190-3202, 2743 and 2749 (one user per owner, and each row's owner
    as its creator); 439-442 (the importer makes the first user its
    coder name); 2420-2502 (a case's description, variable values and
    source references); 1654-1655 and 1699-1733 (a selection without a
    coding read as an annotation, so no link to part of a file is
    written); 2283 (a numeric attribute as Float, any other as Text);
    2312-2350 and 2131-2133 (a journal entry as a note with its text in
    sources/); 2352-2393 and 2616-2622 (an annotation as a note, with a
    reference in its text source); 1768-1772 and 1842-1847 (the dates
    the importer reads).
    Why: so that QualCoder's importer, and other programs that read the
    format, find each part where QualCoder's own export puts it. Where
    Exegete departs (no attribute for a row with no owner, a reference
    for every annotation, UTF-8 without a byte order mark, local times
    without a zone, numbers in plain decimal notation, positions
    checked), the reason is given in the code beside it.

45. QualCoder's saved graph labels, table displays and filters: their
    tables and columns, and the text in which a saved display and a
    saved filter are stored.
    Here: src/exegete/database.py, saved_display_values,
    saved_filter_values, _SAVED_DISPLAY_OPERATORS,
    _SAVED_FILTER_OPERATORS and QualcoderDatabase.old_name_left_in.
    Here: src/exegete/server.py, OLD_NAME_LEFT_IN_NOTE.
    From: __main__.py, lines 1835-1838 and 1861-1862; manage_files.py,
    lines 745-783 and 1165-1186; report_attributes.py, lines 139-161,
    265-308 and 710-712 (3.8.2 __main__.py, lines 2395-2398 and
    2416-2417; 3.8.2 manage_files.py, lines 184-223 and 591-615; 3.8.2
    report_attributes.py, lines 143-165, 264-307 and 710-712).

46. The documents folder that holds a text's stored copy, and how
    QualCoder finds that copy: by the stored path "/docs/<name>" or,
    for a text with no stored path, by the entry's own name.
    Here: src/exegete/database.py,
    QualcoderDatabase.documents_listing,
    QualcoderDatabase.documents_clash,
    QualcoderDatabase.documents_clashes and
    QualcoderDatabase.own_stored_names.
    Here: src/exegete/server.py, _stored_copy_block.
    From: __main__.py, lines 1775-1776; manage_files.py, lines 2955 and
    3700-3707; refi.py, lines 2111-2117 (3.8.2 manage_files.py, lines
    2206-2207 and 2358-2366).

47. That a file entry's id can be given again to a later entry, and
    that its date is set when the entry is created and is left as it is
    by a rename (withdrawn).
    Withdrawn in 0.15.1: the code it described was removed.

48. The note columns of QualCoder's tables, the key each note is
    updated by, and the two kinds of note QualCoder dates when one is
    edited.
    Here: src/exegete/database.py,
    QualcoderDatabase.PSEUDONYMISE_MEMO_FIELDS,
    QualcoderDatabase.PSEUDONYMISE_MEMO_KEYS,
    QualcoderDatabase.PSEUDONYMISE_MEMO_DATED,
    QualcoderDatabase._pseudonymise_write_memos and
    QualcoderDatabase._set_project_memo.
    From: __main__.py, lines 1788-1826 and 1953; attributes.py, line
    202; code_text.py, lines 1017-1020; journals.py, lines 636-639.

49. The tables, columns, keys, unique groups and views of a new
    project in QualCoder 4.0's format, their order, and its first rows.
    Here: src/exegete/new_project.py, SCHEMA_VERSION, TABLES,
    CODER_NAMES_TABLE, VISIBILITY_VIEW_TABLES, PROJECT_COLUMNS,
    SPEAKER_CODER_NAME, DATE_FORMAT, creation_date,
    creation_statements, table_statement and view_statement.
    From: __main__.py, lines 1787-1867; app.py, lines 1448-1561;
    speakers.py, line 47.
    Why: a project Exegete creates must be, in everything QualCoder
    reads, the project QualCoder 4.0's own New Project creates. The
    table statements are generated from this project's own column
    table, not copied. QualCoder never reads a table's stored text, and
    the tests compare everything it does read with a project that 4.0
    created. The coder_names table, the views' condition and the two
    coder_names inserts read as QualCoder's, being what the format
    requires.


Tests and fixtures
------------------

These are in the repository only; no package contains them. A test's
oracle is what the test takes as the right result. The oracles here
carry QualCoder's code, so that the tests compare Exegete's code with
QualCoder's own rather than with a restatement of it. A fixture is the
data a test starts from. The fixtures here restate QualCoder's schema,
so that the test projects are in QualCoder's own format.

50. The palette, held to QualCoder's (copied values)
    Here: tests/test_v012_palette_idempotent.py, UPSTREAM_COLORS and
    TestPaletteParity.test_palette_identical_to_pinned_upstream.
    From: color_selector.py, lines 52-65.
    Why: to hold item 15 to QualCoder's palette.

51. The palette matcher oracle (copied verbatim)
    Here: tests/test_v012_palette_idempotent.py, upstream_color_matcher.
    From: color_selector.py, lines 144-162.
    Why: item 5 is compared with QualCoder's own function.

52. The palette matcher's named cases (copied values)
    Here: tests/test_v012_palette_idempotent.py,
    TestPaletteParity.test_named_cases.
    From: the results of color_selector.py, lines 144-162, on lines
    52-65.
    Why: named cases, each with the colour QualCoder's own function
    gives.

53. The editor walk oracle (copied verbatim) (withdrawn)
    Withdrawn in 0.15.1: the code it described was removed.

54. The whole-word replacement pattern oracles (copied verbatim)
    Here: tests/test_v012_pseudonymise_engine.py, master_boundary_spans,
    legacy_boundary_spans,
    TestNonChaining.test_why_that_mapping_is_refused_although_one_pass_survives_it
    and TestNonChaining.test_what_upstream_would_have_done_with_that_mapping.
    From: manage_files.py, line 3348; 3.8.2 manage_files.py, line 2039;
    import_survey.py, lines 134-140.
    Why: to test where QualCoder's own whole-word replacement fires, now
    and in its older form.

55. The coder-name harvest statement (copied verbatim)
    Here: tests/test_v012_ai_coder_setting.py, TestUpstreamParity.HARVEST_SQL.
    From: app.py, lines 1479-1495.
    Why: the tests run QualCoder's own harvest of coder names, rather
    than a restatement of it.

56. The open-time repair statements (copied verbatim)
    Here: tests/test_v17_support.py, replay_master_open_repair.
    Here: tests/test_qa_v17_gate_core.py, master_repair_rowcount.
    From: __main__.py, lines 2377-2381.
    Why: fixtures and oracles replay the hierarchy repair QualCoder runs
    when it opens a project.

57. The delete twins, QualCoder 3.8.2 (copied verbatim)
    Here: tests/test_qa_memo_codebook_gotchas.py,
    TestQualCoderFidelityDifferential.test_delete_code_differential and
    TestQualCoderFidelityDifferential.test_delete_category_differential_with_grandchildren.
    From: 3.8.2 code_text.py, lines 2953-2956 and 2988-2994.
    Why: differential tests apply QualCoder's own statements to a copy
    of a project and compare the two databases.

58. The merge twin, QualCoder 3.8.2 (closely ported)
    Here: tests/test_qa_memo_codebook_gotchas.py, _qualcoder_merge.
    From: 3.8.2 code_text.py, lines 2791-2821.
    Why: the differential test for merge.

59. The merge and delete twins, QualCoder master (closely ported)
    Here: tests/test_qa_v17_gate_core.py,
    TestSubcodeWriteDifferentials.test_merge_differential_provenance_reparent_graphs
    and TestSubcodeWriteDifferentials.test_delete_branch_preview_refuse_and_cascade_differential.
    From: code_tree.py, lines 850-858 and 1521-1571.
    Why: the differential tests for master's merge and cascade delete.

60. The move twins, QualCoder master (file-format facts)
    Here: tests/test_qa_v17_gate_core.py,
    TestSubcodeWriteDifferentials.test_move_dual_pointer_differential_and_oracle
    and TestSubcodeWriteDifferentials.test_v14_projects_hierarchy_inert.
    From: code_tree.py, lines 1235 and 1245.
    Why: the differential test for move.

61. The rename, recolour and move twins, QualCoder 3.8.2 (file-format
    facts)
    Here: tests/test_qa_memo_codebook_gotchas.py,
    TestQualCoderFidelityDifferential.test_rename_recolor_move_differential
    and TestQualCoderFidelityDifferential.test_move_category_differential.
    From: 3.8.2 code_text.py, lines 1905, 2740, 3092, 3125 and 3156.
    Why: differential tests.

62. The coder-visibility views fixture (file-format facts)
    Here: tests/test_qc40_visibility.py, _VIEW_DDL, _VISIBILITY_COLUMN_DDL
    and _BROKEN_VIEWS.
    From: app.py, lines 1472 and 1518-1561.
    Why: fixtures build QualCoder's coder-visibility views.

63. The coder_names table fixture (file-format facts)
    Here: tests/test_v012_ai_coder_setting.py,
    TestUpstreamParity.CODER_NAMES_DDL and
    TestUpstreamParity.test_a_name_absent_from_coder_names_is_visible.
    From: app.py, lines 1470-1473 and 1529-1539.
    Why: coder_names and one view, recreated as QualCoder makes them.

64. The pseudonymisation fixture schema (file-format facts)
    Here: tests/test_v012_pseudonymise_tool.py, SCHEMA, VISIBILITY_COLUMN
    and VISIBILITY_VIEWS.
    From: 3.8.2 __main__.py, lines 2347-2388; app.py, lines 1472 and
    1518-1561.
    Why: a fixture with QualCoder's real tables and constraints.

65. The schema-version ladder fixture (file-format facts)
    Here: tests/test_v17_support.py, BASE_SCHEMA, _migrate_v15,
    _migrate_v16 and _migrate_v17.
    From: 3.8.2 __main__.py, lines 2348-2418; __main__.py, lines
    2308-2343.
    Why: fixtures at QualCoder's schema versions 15, 16 and 17.

66. The scale and media fixture schema (file-format facts)
    Here: tests/track6_build.py, DDL.
    From: 3.8.2 __main__.py, lines 2348-2388; __main__.py, lines
    1813-1828.
    Why: the scale and media test builders.

67. The shared fixture schemas (file-format facts)
    Here: tests/conftest.py.
    Here: tests/test_database_reads.py.
    Here: tests/test_database_writes.py.
    Here: tests/test_fault_injection.py.
    Here: tests/test_qa_toolset_gate.py.
    Here: tests/test_toolset_modes.py.
    Here: tests/test_transport.py.
    Here: tests/track5_helpers.py.
    Here: tests/test_v17_support.py.
    Here: tests/test_v013_rename_tools.py.
    From: 3.8.2 __main__.py, lines 2347-2417; app.py, lines 1470-1473;
    and, at the root of QualCoder's repository, tests/test_qualcoder.py,
    lines 47-107.
    Why: each of these builds its own project fixture in QualCoder's
    format.

68. A project QualCoder 4.0 created, as an oracle (file-format facts)
    Here: tests/fixtures/qc40_new_project.json.
    Here: tests/test_v014_create_project_format.py,
    TestTheOracleItself.test_the_fixture_is_four_point_zero_s_project.
    From: a project that QualCoder 4.0's own New Project wrote (at the
    4.0 tag, __main__.py, lines 2141-2330), recorded as facts: objects,
    columns, indexes, header fields, first rows and what the views
    return. The fixture holds no QualCoder source text. The test checks
    the fixture's counts and its project row. QualCoder takes that row's
    about value from app.py, line 119, at the same tag.
    Why: the format tests compare every project Exegete creates with
    it.

69. Planted rows and old-version markers (file-format facts)
    Here: tests/test_v012_pseudonymise_tool.py.
    Here: tests/test_v012_duplicate_inserts.py.
    Here: tests/test_fix_wave.py.
    Here: tests/test_qa_round2_regressions.py.
    Here: tests/test_v013_names_left_in_text.py.
    Here: tests/test_v013_one_file_per_call.py.
    Here: tests/test_v013_rename_tools.py.
    Here: tests/test_v08_attributes.py.
    Here: tests/test_qa_v08_d1d2_attack.py.
    Here: tests/test_v014_create_project.py.
    Here: tests/test_v014_existing_projects.py.
    Here: tests/test_v014_reads_exports.py.
    Here: tests/test_v014_server_wide.py.
    Here: scripts/create_test_project.py.
    From: __main__.py, lines 1788-1828, 1862 and 1953; app.py, lines
    1611-1612; code_text.py, line 4319; manage_files.py, lines
    3301-3308; pseudonyms.py, lines 64, 90 and 93; view_graph.py, lines
    4959-4963 and 5020-5023; 3.8.2 __main__.py, lines 2985-2992; 3.8.2
    case_file_manager.py, lines 208-222; 3.8.2 manage_files.py, lines
    755-764 and 2010-2020.
    Why: these statements name QualCoder's tables and columns to plant
    rows and old schema versions in fixtures. Among them are saved
    graphs, PDF sources and whole-file case links in the shapes
    QualCoder writes them, and backup folders under QualCoder's names.
    Similar one-line statements occur elsewhere in tests/.

70. The backup and merge-label checks (copied values)
    Here: tests/test_qc40_backup_parity.py,
    TestIgnoreSetPinned.test_qualcoder_set_is_byte_exact_with_upstream.
    Here: tests/test_qc40_memo_privacy.py,
    TestMergeCategoryProvenance.test_source_memo_carried_with_parity_recipe.
    From: app.py, lines 1619-1625; code_tree.py, lines 1413-1417.
    Why: to hold items 16 and 10 to QualCoder's.

71. The Manage Cases name-edit oracle (copied verbatim)
    Here: tests/test_v013_rename_tools.py, upstream_manage_cases.
    From: cases.py, lines 706 and 710-715 (3.8.2 cases.py, lines
    637-644).
    Author: Colin Curtain (QualCoder's git history, 4 of the 7 lines;
    the other 3 are recorded under the identity "QualCoder Developer").
    Why: rename_case is compared with Manage Cases' own rule, and every
    difference must be one of the departures the CHANGELOG names.

72. The "Rename database entry" oracle (closely ported)
    Here: tests/test_v013_rename_tools.py, upstream_rename_entry.
    From: manage_files.py, lines 1492-1494; add_item_name.py, lines
    58-59 and 76-77 (3.8.2 manage_files.py, lines 777-779; 3.8.2
    add_item_name.py, lines 60-61 and 78-79).
    Author: Colin Curtain (QualCoder's git history, for the
    add_item_name.py lines); the manage_files.py lines are recorded
    under the identity "QC Developer".
    Why: rename_file is compared with QualCoder's rename and its
    dialog's duplicate check.

73. The saved display and saved filter builders, and saved rows
    QualCoder wrote (file-format facts)
    Here: tests/test_v013_rename_tools.py, _save_display, _save_filter,
    QUALCODER_DISPLAYS and QUALCODER_FILTERS.
    From: manage_files.py, lines 752, 1169, 1176 and 1183;
    report_attributes.py, lines 143 and 270-308 (3.8.2 manage_files.py,
    lines 191, 591, 601 and 615; 3.8.2 report_attributes.py, lines 147
    and 269-307).
    Why: fixtures hold saved displays and filters in the text QualCoder
    writes, and saved rows that QualCoder 4.0 and 3.8.2 wrote through
    their own code. So the reading of item 45 is tested on QualCoder's
    own format.

74. QualCoder's pseudonym import loop (copied verbatim)
    Here: tests/test_v012_pseudonymise_tool.py, upstream_apply and
    TestSavingTheMappingIntoPseudonymsJson.test_qualcoder_applying_the_written_file_gives_this_runs_text.
    From: manage_files.py, lines 2510-2512 and 3344-3349.
    Why: the test applies the pseudonyms.json that Exegete saves with
    QualCoder's own loop, one entry at a time in file order, rather than
    a restatement of that loop. It checks that the result is the text
    Exegete's run gave.

75. QualCoder's pseudonyms.json write statement (file-format facts)
    Here: tests/test_v012_pseudonymise_tool.py,
    TestSavingTheMappingIntoPseudonymsJson.test_pseudonyms_json_is_byte_identical_to_qualcoders_own_write.
    From: pseudonyms.py, lines 92-93.
    Why: to test that the file Exegete saves is, byte for byte, the file
    QualCoder's own statement writes from the same list.

The REFI-QDA schema in tests/fixtures/refi_qda/Project.xsd is not
QualCoder's: it is the REFI-QDA standard's own, copyright 2019 REFI
(www.qdasoftware.org) under the MIT licence, whose notice the file
carries. Its README records where it came from.


Documents
---------

The three items below are in early documents that the repository
held up to its release 0.15.2-alpha. No package contained those
documents. Git's history keeps the documents: each address below opens
one as it was at that release.

76. A REFI-QDA coded-segment example (copied verbatim)
    Here: https://github.com/nicotem/exegete/blob/v0.15.2-alpha/docs/history/QUALCODER_IMPORT_CAPABILITIES.md.
    From: refi.py, lines 1660-1669 (the example in load_codings_for_text's
    docstring).
    Why: to show the REFI-QDA coded-segment format.

77. A case CSV example (copied values)
    Here: https://github.com/nicotem/exegete/blob/v0.15.2-alpha/docs/history/QUALCODER_IMPORT_CAPABILITIES.md.
    From: 3.8.2 Examples/cases.csv, lines 1-4, at the root of
    QualCoder's repository.
    Why: to show the case CSV import format.

78. Notes on QualCoder's schema (file-format facts)
    Here: https://github.com/nicotem/exegete/blob/v0.15.2-alpha/docs/history/RESEARCH_SUMMARY.md.
    From: __main__.py, lines 1788-1828; 3.8.2 __main__.py, lines
    2347-2417.
    Why: research notes on QualCoder's tables and columns, in prose.


Added for document import (0.14.3)
----------------------------------

79. QualCoder's Word text walk (closely ported, with switches)
    Here: src/exegete/doc_readers.py, getdocumenttext.
    From: docx.py, lines 85-110.
    Author: Mike MacCana (python-docx, MIT licence, whose notice the
    file carries), modified in 2022 by Colin Curtain for QualCoder.
    Why: so that, with none of Exegete's named departures switched on, a
    Word file's text is QualCoder's to the character: every paragraph at
    any depth, its text and tab elements in document order. The walk is
    QualCoder's, except that Word's namespace is written as a constant
    where QualCoder builds it from its prefix table, and the comments are
    rewrapped. Each departure switched on leaves out a kind of element or
    reads one more, and TOOLS.md lists them. With the departure that
    moves notes to the end, a note's reference becomes its label in
    square brackets. The tree is parsed with defusedxml instead of the
    standard parser.

80. QualCoder's OpenDocument text recipe (closely ported)
    Here: src/exegete/doc_readers.py, _ODT_REPLACEMENTS, _ODT_TAG_STARTS,
    _ODT_ENTITIES and odt_recipe.
    From: manage_files.py, lines 3415-3467.
    Why: so that, with none of Exegete's named departures switched on,
    an OpenDocument file's text is QualCoder's to the character. The
    replacements and the tag rule are QualCoder's, and the character
    loop runs by chunks. Each departure switched on is a step before or
    after them, and TOOLS.md lists them.

81. The order of QualCoder's import steps (closely ported)
    Here: scripts/qualcoder_parity.py, QualCoder.stored and
    QualCoder._transcript.
    From: manage_files.py, lines 3213-3341 and 2484-2505.
    Why: to record what QualCoder's import stores from each test
    document. The extraction functions themselves are loaded at run time
    from a QualCoder source tree, and are not in this repository.


Libraries for document import
=============================

Every install fetches defusedxml (Python Software Foundation licence)
and striprtf (BSD-3-Clause) from PyPI. The optional part `pdf-epub`
adds PyMuPDF and EbookLib, both under the GNU Affero General Public
License, version 3, and lxml (BSD-3-Clause), which EbookLib needs. The
lock, `uv.lock`, gives the releases that the Claude Desktop extension
installs. It gives EbookLib 0.20, which also needs six (MIT).
EbookLib's next release, 0.21, does not need six. The lock also gives
lxml 6.1.3, whose packages contain zlib (Zlib), iconv (LGPL-2.1),
and libxml2, libxslt and libexslt (each MIT), as lxml's LICENSES.txt
states. An install with that part is, taken as a whole, under the
AGPL's terms, as QualCoder 4.0's own installs are.
Exegete's own code stays under the LGPL. That is the maintainer's
decision of 6 October 2026, which also switches the part on in the
Claude Desktop extension. None of these libraries is contained in
Exegete's package or its Claude Desktop extension file: they are
fetched from PyPI on the researcher's computer.

With Python 3.11 to 3.14 on Windows x64, on Linux (x86-64 and Arm) and
on Macs with Apple silicon, the part brings PyMuPDF 2: from v0.15.1 on
Windows x64 and Linux, and from v0.15.3 on those Macs. With PyMuPDF 2
come nine more libraries, also fetched from PyPI. The licence files and
metadata inside the packages that the lock gives those computers state
these licences:

- onnxruntime: MIT, Microsoft Corporation; its packages also carry
  Microsoft's notices for the third-party software they contain
  (onnxruntime/ThirdPartyNotices.txt).
- numpy: BSD-3-Clause, NumPy Developers, with parts under 0BSD, MIT,
  Zlib and CC0-1.0. Its packages for Windows and Linux also contain
  OpenBLAS (BSD-3-Clause) with LAPACK (BSD-3-Clause-Open-MPI), the GCC
  runtime libraries (GPL-3.0-or-later with the GCC Runtime Library
  Exception), and on Linux x86-64 libquadmath (LGPL-2.1-or-later). The
  package's LICENSE.txt names each of these, and its packages for
  Windows also contain Microsoft's C++ runtime library (msvcp140.dll),
  which LICENSE.txt does not name. That library is Microsoft's
  redistributable code, under Microsoft's own terms. Its packages for
  Macs with Apple silicon at macOS 14, which an install from the lock
  takes there, contain none of these libraries and use Apple's
  Accelerate framework, part of macOS. Their LICENSE.txt names OpenBLAS
  and the GCC runtime libraries all the same.
- networkx: BSD-3-Clause, NetworkX Developers.
- psutil: BSD-3-Clause, Jay Loden, Dave Daeschler and Giampaolo Rodola.
- protobuf: BSD-3-Clause, Google Inc.
- PyYAML: MIT, Ingy döt Net and Kirill Simonov.
- tabulate: MIT, Sergey Astanin and contributors.
- flatbuffers: Apache-2.0, as its metadata says (its package carries no
  licence file).
- packaging: Apache-2.0 or BSD-2-Clause, Donald Stufft and individual
  contributors.

Two more packages, pymupdf-layout and pymupdf4llm, are empty at 2.0.
Their code and its trained models are inside PyMuPDF's own package,
under PyMuPDF's statement, "Dual Licensed - GNU AFFERO GPL 3.0 or
Artifex Commercial License". Every other computer, Intel Macs and
Windows on Arm among them, takes PyMuPDF 1.x and none of these.
