diff --git a/.github/workflows/phase0.yml b/.github/workflows/phase0.yml new file mode 100644 index 0000000..03ca0e1 --- /dev/null +++ b/.github/workflows/phase0.yml @@ -0,0 +1,35 @@ +name: Phase 0 Contract + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python: ["3.12", "3.14"] + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + cache: pip + + - name: Install pinned validator dependencies + run: python3 -m pip install --require-hashes -r requirements.txt + + - name: Validate research contract + run: python3 scripts/validate_phase0.py + + - name: Run tests + run: python3 -m unittest discover -s tests -v diff --git a/HYPOTHESES.md b/HYPOTHESES.md index 8b13789..2c61466 100644 --- a/HYPOTHESES.md +++ b/HYPOTHESES.md @@ -1 +1,63 @@ +# Hypotheses +CONSTRAINT-SHIFT begins with six falsifiable hypotheses. A hypothesis may be supported, weakened, rejected, split, or replaced by evidence. It must not be silently reworded to fit results. + +## H1 — Specification Primacy + +**Claim:** As independently measured AI implementation capability increases, the relative benefit of persistent specifications and machine-checkable contracts over transient prompt-only workflows increases for preserving, regenerating, and changing intended software behaviour. + +**Candidate measurements:** predeclared AI capability strata measured independently of H1 outcomes; regeneration success against a fixed acceptance suite; change-propagation success; human implementation editing after regeneration; behavioural divergence across repeated implementations; and the interaction between capability level and workflow condition. + +**Falsification pressure:** H1 is weakened if the persistent-specification advantage is flat, decreases, or disappears as independently measured implementation capability increases under matched tasks and resource budgets. A benefit observed at only one capability level supports specification utility under that condition but does not by itself support H1's directional claim. + +## H2 — Verification Selection + +**Claim:** AI-assisted development disproportionately benefits languages and toolchains that provide precise, machine-actionable diagnostics and enforceable constraints. + +**Candidate measurements:** attempts to first successful compile; repair iterations; diagnostic-to-fix conversion; human interventions; static-analysis defects; token and wall-clock cost. + +**Falsification pressure:** H2 is weakened if stronger machine-checkable constraints and diagnostics provide no reproducible improvement after controlling for model familiarity, ecosystem maturity, and task suitability. + +## H3 — Legacy Preservation Paradox + +**Claim:** AI assistance can extend the operational lifetime of legacy languages and systems by reducing maintenance cost associated with scarce human expertise. + +**Candidate measurements:** predeclared expertise/scarcity strata measured independently of H3 outcomes; matched AI-assisted versus unassisted conventional-maintenance outcomes within each stratum; scarce-expert consultation hours consumed; defect-localization and repair success; active human effort; verification burden; behavioural regressions; and a predeclared maintenance-viability horizon or equivalent lifecycle proxy under a fixed cumulative maintenance budget. + +**Falsification pressure:** H3 is weakened if AI assistance does not reduce maintenance burden relative to the matched unassisted baseline under scarce-expertise conditions, if the benefit does not persist or increase as expert access becomes scarcer, if the predeclared maintenance-viability horizon is not extended, or if apparent gains are offset by verification burden or behavioural risk. Short experiments may support only the declared lifecycle proxy, not literal calendar-year lifetime claims. + +## H4 — Modding Mutation + +**Claim:** AI-assisted development reduces the technical barrier to software modification and increases the number and diversity of executable modification attempts under a fixed effort budget. + +**Candidate measurements:** executable modifications per unit time; human technical actions; distinct completed changes; failure rate; behavioural diversity. + +**Falsification pressure:** H4 is weakened if AI-assisted workflows do not increase executable modification throughput or diversity once setup, debugging, and correction costs are included. + +## H5 — Recombination Acceleration + +**Claim:** When implementation cost is reduced while design intent and quality criteria are held fixed, the rate at which mechanics, systems, genres, and implementation patterns are recombined into executable prototypes increases under a fixed total effort budget. + +**Candidate measurements:** a predeclared outcome-independent implementation-input measure recorded per attempt, such as active human time, wall-clock time, tool/agent steps, or reliable compute/token expenditure; successful executable recombinations per fixed total effort budget; retained source concepts; integration defects; and behavioural verification. Derived efficiency metrics such as cost per accepted prototype may be secondary outcomes but cannot establish the H5 mechanism. + +**Falsification pressure:** H5 is weakened if an implementation-focused intervention fails to reduce the predeclared implementation-cost measure, if measured cost reduction does not increase executable recombination rate under the fixed total budget, or if recombination increases only when design ideation changes while implementation cost remains unchanged. + +## H6 — Tool Legitimacy Gap + +**Claim:** For otherwise equivalent software artifacts, disclosure of AI participation can alter perceived legitimacy independently of demonstrated artifact quality. + +**Candidate measurements:** perceived quality, creativity, originality, authenticity, technical competence, trust, and willingness to use or recommend. + +**Falsification pressure:** H6 is weakened if disclosure produces no reproducible difference, or if observed differences are explained by uncontrolled artifact, wording, sampling, or expectancy effects. + +## Status language + +Results should use restrained labels: + +- **untested** +- **inconclusive** +- **supported under tested conditions** +- **weakened** +- **falsified under tested conditions** + +“Proven” is not the default label for an empirical result. diff --git a/INVARIANTS.md b/INVARIANTS.md index 8b13789..3f2f9af 100644 --- a/INVARIANTS.md +++ b/INVARIANTS.md @@ -1 +1,45 @@ +# Research Invariants +These rules constrain every experimental phase unless a later change explicitly amends the research contract and documents compatibility with prior evidence. + +## I1 — No conclusion by model assertion +An LLM output, prediction, ranking, or explanation is not evidence for a project hypothesis by itself. + +## I2 — Equivalent-task comparisons +Cross-language, cross-tool, and cross-agent comparisons must begin from an equivalent task contract. Language-specific accommodations must be declared rather than hidden. + +## I3 — Preserve failures +Failed generations, compiler failures, abandoned repair paths, timeouts, and invalid outputs are part of the evidence and must not be silently discarded. + +## I4 — Record intervention +Human intervention must be recorded at a useful granularity. Manual fixes may not be attributed to an agent. + +## I5 — Record environment +Experiments must retain enough environment information to interpret the result, including model/tool identity, toolchain versions, task/specification identity, and execution platform where relevant. + +## I6 — Separate generation from verification +A system that generates an implementation must not be treated as independent verification merely because it also says the implementation is correct. + +## I7 — Observable contracts before equivalence claims +Implementation fungibility or behavioural equivalence claims require an explicit observable contract and a declared comparison procedure. + +## I8 — No predetermined language winner +The project must not choose metric weights or task suites solely to produce a preferred language ranking. + +## I9 — Negative results are publishable results +A result that contradicts the central thesis remains valid project output if the method and evidence are sound. + +## I10 — Social experiments control the artifact +Tool-legitimacy experiments must hold the evaluated artifact constant across disclosure conditions unless artifact variation is itself the declared independent variable. + +## I11 — Human-subject safeguards +Before recruitment or participant data collection begins, human-participant experiments must have a documented protocol covering informed consent, privacy and data minimization, data handling and retention, and any applicable ethics or institutional review. No participant data may be collected until required approvals are in place and the consent process is ready for use; if formal review is not required, that determination should be documented before recruitment. + +## I12 — Motivation is not evidence +Historical analogies, anecdotes, popularity trends, and project origin stories may motivate a hypothesis but do not count as experimental confirmation. + +## I13 — Reproducible validators +Machine-derived claims should be accompanied by reproducible validators or analysis code where practical. + +## I14 — Contract changes are explicit +Changes to hypotheses, terminology, methodology, or invariants that affect interpretation of existing results must be versioned and explained. diff --git a/METHODOLOGY.md b/METHODOLOGY.md index 8b13789..053eff5 100644 --- a/METHODOLOGY.md +++ b/METHODOLOGY.md @@ -1 +1,74 @@ +# Methodology +## Purpose +CONSTRAINT-SHIFT is designed as an empirical research repository, not a collection of AI-development predictions. Each experiment should connect a falsifiable hypothesis to an operational definition, controlled procedure, retained evidence, and bounded conclusion. + +## Common experiment lifecycle + +1. **Select hypothesis.** Identify the exact hypothesis and sub-claim under test. +2. **Freeze task and analysis contract.** Before outcome inspection, define inputs, outputs, acceptance conditions, resource limits, stopping rules, inclusion/exclusion criteria, invalid-trial and timeout rules, missing-data handling, and the denominators used for primary rates. +3. **Declare factors.** Record independent variables such as language, compiler, agent, disclosure condition, or assistance mode. +4. **Declare controls.** Identify variables held constant and unavoidable differences. +5. **Run trials.** Preserve successful and failed trials. +6. **Verify independently.** Use compilers, tests, static checks, formal tools, or blinded evaluation appropriate to the claim. +7. **Retain evidence.** Store machine-readable results plus enough provenance to reproduce interpretation. +8. **Analyze.** Report distributions and uncertainty rather than only best-case examples. +9. **Bound the conclusion.** State what was tested and what was not. +10. **Attempt replication.** Prefer conclusions that survive reruns, alternative tasks, and changed models/toolchains. + +## Experimental unit +The experimental unit must be explicit: one generation attempt, repair trajectory, task-language pair, legacy-maintenance task, modding task, participant evaluation, or another declared unit. + +Repeated attempts from the same underlying task are not automatically independent observations. + +## Comparability +Cross-language experiments must distinguish same specification from same implementation strategy, language-required boilerplate from task logic, ecosystem effects from core language/toolchain effects, model familiarity from language semantics, and compile-time failure from behavioural failure. + +## AI execution metadata +Where available and permitted, record model/provider identifier, date, agent/tool version, relevant instructions, iteration count, compiler/tool cycles, reliable token/cost measures, human intervention, and termination reason. + +If exact parameters are unavailable, record that limitation rather than inventing them. + +## Toolchain metadata +Record language version, compiler/interpreter version, build flags, dependency lock state, analysis tools, operating system/architecture, test command, and materially relevant hardware. + +## Evidence classes + +### E0 — Motivation +Anecdote, historical example, external observation, or qualitative rationale. Useful for choosing a question; not confirmation. + +### E1 — Single controlled trial +A reproducible result under one task/environment condition. + +### E2 — Replicated controlled evidence +Repeated results under the same contract with retained failures and stable analysis. + +### E3 — Cross-condition evidence +Results reproduced across materially different tasks, models, toolchains, systems, or participant samples. + +## Metrics +Metrics are hypothesis-specific. Phase 0 approves categories, not fixed weights: success/failure, compile attempts, repair iterations, test-pass rate, static/formal outcomes, wall-clock duration, reliable token/cost data, human interventions, behavioural divergence, modification throughput, recombination completion, and controlled perception ratings. + +Any composite score must publish its formula, normalization, weights, missing-data policy, and sensitivity to alternative weights. + +## Statistical reporting +Inclusion, exclusion, invalid-trial, timeout, missing-data, and primary-denominator rules must be frozen before outcome inspection. Failures retained under I3 must not be reclassified after results are known merely to improve a reported rate. + +When sample size permits, report sample count, distributions, uncertainty, exploratory versus confirmatory status, all exclusions and invalid trials with reasons, missingness, and dependence between repeated attempts. + +Any deviation from the predeclared trial-handling or missing-data rules must be identified explicitly, justified, and accompanied by a sensitivity analysis showing the result under the original rule where technically possible. + +## Human evaluation +Before recruitment or data collection, a human-participant protocol must define informed-consent procedures, privacy and data-minimization safeguards, data handling and retention, primary outcomes, and any applicable ethics or institutional review. Required approvals must be in place before recruitment or collection begins; if formal review is not required, document that determination beforehand. + +Once those prerequisites are satisfied, studies should randomize or counterbalance where appropriate and hold the artifact constant when testing disclosure. + +## Legacy-code safety +Legacy experiments should use public, synthetic, redistributable, or otherwise authorized code. Toy experiments cannot establish safety for production banking, trading, medical, industrial, or other critical systems. + +## Reproducibility target +A retained experiment should make it possible for an independent operator to answer: + +> What was attempted, under which contract, with which tools, what happened, and how was the result judged? + +If those questions cannot be answered, the experiment is incomplete. diff --git a/README.md b/README.md index 26f96d1..0ce8c3e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,100 @@ # CONSTRAINT-SHIFT -Research and experiments on how AI shifts software development from code authorship toward specification, constraints, verification, and machine-generated implementation. + +**Research and experiments on how AI shifts software development from code authorship toward specification, constraints, verification, and machine-generated implementation.** + +CONSTRAINT-SHIFT treats a simple observation as a research problem: when implementation becomes cheap to generate, the scarce work may move upward into defining intent, constraining admissible solutions, verifying outcomes, and selecting among candidate implementations. + +## Central thesis + +> AI does not merely automate programming. It can change the unit of software authorship from implementation production toward specification, constraint design, verification, and selection among machine-generated implementations. + +This is a hypothesis-driven repository. The thesis is **not treated as established fact**. Claims must earn support through reproducible experiments and retained evidence. + +## Research tracks + +- [Specification primacy](research/specification-primacy.md) +- [Programming-language selection](research/language-selection.md) +- [Legacy preservation](research/legacy-preservation.md) +- [AI-assisted modding](research/ai-modding.md) +- [Tool legitimacy gap](research/tool-legitimacy-gap.md) + +## Foundation + +Phase 0 defines the research contract: + +- [Hypotheses](HYPOTHESES.md) — six falsifiable hypotheses. +- [Terminology](TERMINOLOGY.md) — stable definitions for project terms. +- [Methodology](METHODOLOGY.md) — common experiment and evidence rules. +- [Invariants](INVARIANTS.md) — non-negotiable research constraints. +- [Roadmap](ROADMAP.md) — implementation sequence from foundation to archival record. + +The repository intentionally separates **motivation**, **hypothesis**, **measurement**, **evidence**, and **conclusion**. + +## Phase 0 validation + +The foundational contract uses Python 3.12 or 3.14 and a pinned CommonMark parser. +Install the two hashed runtime dependencies before running the validator or tests: + +```bash +python3 -m venv .venv +. .venv/bin/activate +python3 -m pip install --require-hashes -r requirements.txt +python3 scripts/validate_phase0.py +python3 -m unittest discover -s tests -v +``` + +The validator checks required files, exact ordered hypothesis/invariant/terminology +heading inventories, two required prose statements, the Phase 0 roadmap heading, +and a non-whitespace source-size heuristic. It does not assess the completeness +of section bodies or establish empirical support for any hypothesis. + +[Validation policy](docs/VALIDATION.md) defines eligible headings, decoded prose, +excluded metadata, parser compatibility corrections, and regression coverage. + +## Planned experimental flow + +```text +human intent + | + v +specification + | + v +constraints / contracts + | + v +machine-generated candidate implementation + | + v +compiler + tests + static / formal checks + | + v +retained evidence + | + v +supported, weakened, or falsified claim +``` + +## Current status + +**Phase 0 — Foundational Research Contract** + +No language ranking, legacy-survival claim, modding claim, or social-perception claim is considered established merely because it appears in project motivation. Empirical phases begin after the research contract is merged. + +## Scope + +The project is interested in: + +- spec-driven and contract-driven AI development; +- compiler and toolchain feedback as machine guidance; +- machine-verifiable programming-language properties; +- legacy-language maintenance and preservation; +- implementation fungibility across languages and toolchains; +- AI-assisted game modification and mechanic recombination; +- disclosure effects on perceptions of AI-assisted work. + +The project is **not** a leaderboard for preferred programming languages and is not designed to prove that AI-generated software is inherently better or worse than human-authored software. + +## License + +See [LICENSE](LICENSE). diff --git a/ROADMAP.md b/ROADMAP.md index 8b13789..a09c0e8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1 +1,51 @@ +# Roadmap +The roadmap deliberately separates the research contract from empirical work. + +## Phase 0 — Foundational Research Contract + +**Status: implemented by the foundational PR** + +Deliverables: + +- [x] central thesis and scope; +- [x] six falsifiable hypotheses; +- [x] stable initial terminology; +- [x] methodology; +- [x] research invariants; +- [x] five research-track briefs; +- [x] machine-checkable Phase 0 validator; +- [x] standard-library `unittest` runner with pinned CommonMark parsing; +- [x] CI validation. + +Exit criterion: the Phase 0 validator and test suite pass on the merged default branch. + +## Phase 1 — Experiment Schema +Define a versioned machine-readable record for tasks, agents, languages, toolchains, trials, interventions, verification outcomes, and retained evidence. + +## Phase 2 — Language Harness +Build a minimal multi-language harness for equivalent task execution. Initial candidates may include C, C++, Rust, Go, Python, TypeScript, Java, Ada, Fortran, and COBOL where reproducible toolchains are practical. + +## Phase 3 — Compiler Feedback Experiment +Test H2 by measuring whether diagnostic and constraint feedback changes agent repair performance. + +## Phase 4 — Legacy Preservation +Test H3 using authorized legacy or legacy-style code with constrained maintenance tasks. + +## Phase 5 — Implementation Fungibility +Test how far implementations can be regenerated or translated while preserving observable behaviour. + +## Phase 6 — AI Modding and Recombination +Test H4 and H5 using controlled modification and mechanic-recombination tasks. + +## Phase 7 — Tool Legitimacy Gap +Design and, where appropriate, run a controlled disclosure study for H6. + +## Phase 8 — AI Language Fitness Model +Derive a multidimensional language/toolchain comparison from accumulated measurements. Do not assign a universal scalar ranking unless data justify a declared aggregation method. + +## Phase 9 — Replication and Sensitivity +Repeat key experiments across alternative tasks, models, versions, toolchains, and analysis choices. + +## Phase 10 — Technical Record and Archival Release +Produce a bounded technical synthesis, release artifacts, machine-readable evidence, reproducibility instructions, and archival metadata suitable for long-term citation. diff --git a/TERMINOLOGY.md b/TERMINOLOGY.md new file mode 100644 index 0000000..34db40a --- /dev/null +++ b/TERMINOLOGY.md @@ -0,0 +1,52 @@ +# Terminology + +Phase 0 fixes the initial vocabulary used by CONSTRAINT-SHIFT. Definitions may be revised only when the change is explicit and the effect on prior experiments is documented. + +## Constraint Shift +**Constraint Shift** is the hypothesized movement of software-development effort away from direct implementation authoring and toward specification, constraint design, verification, evidence production, and selection among generated implementations as AI implementation capability increases. + +## Specification Primacy +**Specification Primacy** is the condition in which a persistent specification becomes a principal carrier of intended behaviour and can drive implementation, testing, regeneration, or verification. + +A repository containing documentation is not sufficient evidence of specification primacy. The specification must materially constrain or generate downstream work. + +## Implementation Fungibility +**Implementation Fungibility** is the degree to which one implementation can be replaced, regenerated, translated, or substantially rewritten while preserving externally specified behaviour and required verified properties. + +Fungibility is always relative to an explicit contract. + +## AI Language Fitness +**AI Language Fitness** is a multidimensional description of how well a programming language and its toolchain support AI-assisted generation, diagnosis, repair, verification, interoperability, and deployment for a defined workload. + +Phase 0 deliberately does **not** define a universal scalar score or fixed weights. + +## Machine Verifiability +**Machine Verifiability** is the extent to which properties of an implementation can be checked automatically by compilers, type systems, static analyzers, proof systems, tests, model checkers, or other reproducible tools. + +## Diagnostic Feedback Quality +**Diagnostic Feedback Quality** is the usefulness of machine-produced feedback for locating and repairing implementation defects. + +## Legacy Preservation Paradox +**Legacy Preservation Paradox** is the hypothesis that AI assistance can extend the useful lifetime of legacy systems and languages by reducing maintenance cost associated with scarce human expertise, even when the human programmer population for those languages is declining. + +## Second Modding Revolution +**Second Modding Revolution** is the proposed analogy between earlier tool-enabled modding ecosystems and AI-assisted modification, where natural-language direction and agentic tooling may reduce the technical barrier to experimentation and increase the rate of software mutation and recombination. + +## Modding Mutation Rate +**Modding Mutation Rate** is the number of distinct, executable modification attempts produced per unit of constrained effort under a defined experimental protocol. + +## Recombination Acceleration +**Recombination Acceleration** is an increase in the rate at which previously separate mechanics, systems, genres, interfaces, or implementation patterns are combined into executable prototypes. + +## Tool Legitimacy Gap +**Tool Legitimacy Gap** is a difference in evaluation of an otherwise equivalent artifact that is attributable to disclosed production method rather than demonstrated artifact behaviour. + +## AI Disclosure Penalty +For an outcome variable R, an **AI Disclosure Penalty** may be represented as: + +D_AI = R_undisclosed - R_AI-disclosed + +A positive value indicates lower ratings after AI disclosure for that outcome. + +## Research Contract +The **Research Contract** is the combination of hypotheses, definitions, methodology, invariants, and validation rules that constrain how CONSTRAINT-SHIFT can turn observations into claims. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..7ee0487 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,64 @@ +# Third-party notices + +The `reference`, `getNextLine`, `link`, `image`, and `paragraph` functions in +`scripts/validate_phase0.py` are adapted from markdown-it-py 4.2.0: + +- https://github.com/executablebooks/markdown-it-py/tree/v4.2.0/markdown_it/rules_block +- https://github.com/executablebooks/markdown-it-py/tree/v4.2.0/markdown_it/rules_inline + +Only raw reference-label length guards and ASCII paragraph trimming differ. +They are registered as rules on the single CommonMark parser. Revisit them when +upgrading the dependency; remove each correction when upstream behavior passes +its retained regression cases. These rules retain their original MIT licensing. + +```text +MIT License + +Copyright (c) 2020 ExecutableBookProject + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +``` + +```text +Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +``` + diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md new file mode 100644 index 0000000..8315842 --- /dev/null +++ b/docs/VALIDATION.md @@ -0,0 +1,94 @@ +# Phase 0 validation policy + +The validator checks the foundational research contract. It parses each required +Markdown file once per validation, then derives heading inventories and separate +prose blocks from that same token stream. Reference resolution and quote/list +ownership belong to the parser. There are no independent visibility, reference, +code-masking, HTML-state or prose-assembly scans. + +## Installation and dialect + +Use Python 3.12 or 3.14 and install `requirements.txt` with +`python3 -m pip install --require-hashes -r requirements.txt`. +`markdown-it-py==4.2.0` and its runtime dependency `mdurl==0.1.2` are pinned to +universal-wheel hashes. The standard-library `unittest` runner remains in use; +the validator and tests now require the parser dependency. + +The dialect is the explicit `commonmark` preset with HTML enabled and no GFM +extensions. Task-list markers are ordinary paragraph text. HTML parsing is +needed to identify hidden regions and reference-definition boundaries, even +though HTML nodes cannot satisfy prose requirements. + +Two compatibility corrections run as rules inside the same parser: + +- Reference labels at definitions and uses are limited to 999 source characters + before whitespace normalization. Oversized syntax remains literal text. +- Paragraphs trim only ASCII Markdown whitespace, preserving Unicode content. + +The corrected rules retain upstream 4.2.0 code and state; only length guards and +one trimming operation differ. They are not an alternative parser. Attribution +and removal criteria are in [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md). +When upgrading the dependency, rerun all behavioral tests and remove corrections +that upstream has made unnecessary. + +## Contract checks + +- All eleven required files must be readable, valid UTF-8, and nonempty. +- Level-2 **ATX** heading inventories must preserve exact titles, order and + multiplicity for hypotheses, invariants and terminology. Nested headings are + eligible. Setext headings are deliberately excluded. +- Heading titles use decoded text: equivalent entities, escapes, emphasis and + visible link labels are accepted. Excluded inline nodes retain a boundary. +- The roadmap must contain exactly one `Phase 0 — Foundational Research Contract` + heading and `machine-checkable Phase 0 validator` in eligible prose. +- The README must contain `The thesis is not treated as established fact` in + eligible prose. Literal emphasis delimiters are not required. +- Each supplied artifact must have at least 80 non-whitespace source characters. + This is a size heuristic, not proof that its sections have meaningful bodies. + +Eligible prose consists of decoded paragraph text and ATX heading text. Soft and +hard breaks normalize to spaces within a leaf block. Emphasis and visible link +labels contribute text. Inline/fenced/indented code, raw HTML, comments, images +(including alt text), reference definitions, and link destinations/titles do not. +Omitted nodes create boundaries. Distinct paragraphs, items and headings never +combine to manufacture a phrase. Required wording may appear within a larger +eligible text block; it need not be that block's entire contents. + +The validator does not prove that a term has a substantive definition, that +methodological safeguards are enforced, or that a hypothesis is supported. +Missing/empty files, invalid encoding and other read failures produce diagnostics +and a nonzero CLI exit status. Heading mismatches include expected/observed +inventories and parser-provided source locations. + +## Behavioral changes and verification + +The previous scanners' tests included source-format assumptions and some +incorrect container expectations. The replacement preserves their fixtures but +updates these outcomes explicitly: + +| Fixture | Expected behavior | +| --- | --- | +| Escaped backticks surrounding an incomplete inline comment before an ATX heading | Heading remains visible; the incomplete comment cannot cross that leaf boundary. | +| A bullet item followed by a dedented ordered list, including inside a quote | Ordered list is a new sibling block; its ATX heading remains visible. | +| A definition inside a list with tab-padded continuation destination/title | Reference metadata stays hidden. | +| An ordered item followed by a lazy `` continuation and a new ATX heading | The pinned dialect keeps the span inline; the heading remains visible. | +| HTML tags between visible words | Separate eligible prose segments preserve the omitted-node boundary. | +| An unquoted ordered item after a quoted paragraph | The list is a separate block; its source marker is not prose. | + +The last two parser container cases are tested as explicit behavior of the pinned +dialect, not a claim that every CommonMark implementation agrees on every +possible combination. Full CommonMark conformance is not asserted. + +The suite retains existing contract mutations and Markdown regressions, promotes +all eighteen bundled review cases, covers the four latest PR findings, and tests +294 generated sibling/continuation transitions (including tabs). It also checks +code delimiter/backslash combinations, metadata exclusion, text-block separation, +reference-label limits, file failures, CLI status, and exactly one parse per +required file. A subprocess timeout bounds a 16,000-line inline-code paragraph to +15 seconds; it is a regression budget, not a benchmark claim. + +The checked-in official CommonMark 0.31.2 corpus supplies 652 expected HTML +examples. CI compares parser output with that independently supplied HTML, +normalizing only the renderer's newline formatting in empty blockquotes. Forty +selected examples additionally have explicit expected adapter headings/prose, +including the project's exclusions. Corpus attribution is retained alongside it. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cf4ae41 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +# Only universal wheels, with both runtime dependencies pinned and hashed. +--only-binary=:all: +markdown-it-py==4.2.0 \ + --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 diff --git a/research/ai-modding.md b/research/ai-modding.md index 8b13789..d8b209a 100644 --- a/research/ai-modding.md +++ b/research/ai-modding.md @@ -1 +1,84 @@ +# AI-Assisted Modding and Recombination +## Question +Does AI-assisted development reduce the technical barrier to modifying games and other interactive software, and does measured implementation-cost reduction increase executable experimentation and mechanic recombination? + +## Hypothesis link +Primary: **H4 — Modding Mutation** + +Primary: **H5 — Recombination Acceleration** + +## Historical frame +The early modding scenes around engines such as Quake motivate this track because improved access to scripting, editors, and engine internals enabled users to treat a finished game as a platform for mutation. + +CONSTRAINT-SHIFT uses this as an analogy, not as proof. + +## Proposed measurable quantities + +### Modification barrier +Observe programming actions, setup actions, asset work, debugging cycles, integration failures, and elapsed effort. No universal scalar barrier is assumed in Phase 0. + +### Modding mutation rate +Count distinct executable modification attempts completed under a fixed effort budget. Throughput must be reported separately from quality. + +### Implementation cost +Predeclare at least one **outcome-independent implementation-input measure** as the primary H5 mechanism measure before outcome inspection. It must be measured per attempt regardless of whether that attempt succeeds. Suitable primary measures include active human implementation time per attempt, wall-clock implementation time per attempt, tool/agent steps per attempt, or reliable token/compute expenditure per attempt. + +Metrics derived from the number of successful outputs — including cost per accepted executable prototype — may be reported as secondary efficiency outcomes, but they cannot serve as the primary mechanism measure because they are algebraically coupled to recombination success under a fixed budget. + +Do not collapse unlike cost measures into a single score unless the aggregation rule and weights are declared in advance. + +### Recombination +A mashup task specifies mechanics or systems drawn from multiple design sources. Completion requires behavioural checks showing requested components are actually present. + +## H5 mechanism isolation + +H5 attributes recombination acceleration specifically to reduced **implementation cost**, so design quality must not be allowed to change silently with the implementation condition. + +Use mashup specifications frozen before condition assignment and compare, at minimum: + +- **Conventional implementation:** implement the fixed mashup specification without generative AI. +- **AI implementation assistance:** use AI for implementation while the design specification, mechanics, quality criteria, and acceptance tests remain fixed. + +An optional **AI design-only control** may allow AI to improve or propose the design while implementation remains conventional. This helps distinguish a better-ideas mechanism from an implementation-cost mechanism. + +Use the same total effort budget and acceptance criteria across the primary implementation conditions. H5 receives mechanism-consistent support only if the implementation-focused intervention reduces the predeclared implementation-cost measure and the lower-cost condition produces more accepted executable recombinations per fixed total budget, or an equivalent predeclared analysis links the measured cost reduction to increased recombination rate. + +If AI produces better mashups without reducing implementation cost, that may support a separate design-assistance claim but does not support H5's stated cost mechanism. + +## Candidate experiments + +### M1 — Matched mod task +Compare the same modification goal under conventional tooling and AI-assisted tooling. Measure the modification barrier, successful executable changes, failures, and retained behavioural quality. + +M1 primarily informs H4 unless its design also satisfies the H5 mechanism-isolation requirements. + +### M2 — Mechanic mashup cost experiment +Freeze a structured specification combining independent mechanics before condition assignment. + +Run conventional-implementation and AI-implementation-assistance conditions under the same total effort budget. Measure the predeclared outcome-independent implementation input **for every attempt**, accepted executable prototypes, integration defects, retained mechanics, and behavioural verification. Cost per accepted prototype may be reported only as a secondary efficiency statistic. + +Primary H5 analysis requires an independently measured reduction in implementation input per attempt and compares that reduction with the executable recombination-rate difference. A throughput increase without an independent per-attempt input reduction does not support H5's implementation-cost mechanism. Likewise, a measured input reduction without increased recombination weakens the stated mechanism. + +### M3 — Design-only mechanism control +Where resources permit, add a condition in which AI may alter or propose the mashup design but implementation remains conventional. + +If this condition increases recombination while implementation cost is unchanged, report that as evidence for an ideation/design mechanism rather than the H5 implementation-cost mechanism. + +### M4 — Matched mutation diversity +Freeze the same broad design goal, acceptance criteria, repetition count, diversity metric, and total effort budget before condition assignment. + +Run matched repeated attempts under at least: + +- **Conventional condition:** conventional tooling without generative AI. +- **AI-assisted condition:** the same task and budget with the predeclared AI assistance available. + +Use the same diversity measure in both conditions and retain failed attempts in the trial record. Compare behavioural diversity among accepted executable outputs while also reporting completion rate and the number of accepted outputs, so a condition cannot appear more diverse merely because it produced a smaller selectively successful subset. + +This comparison tests the diversity component of H4. Report it separately from H5 unless the implementation-cost mechanism is also identified. + +## Important distinction +A higher mutation or recombination rate can coexist with lower average quality. Throughput, quality, diversity, and implementation cost are separate outcomes and should not be collapsed without a predeclared aggregation rule. + +## Phase 0 position +The “Second Modding Revolution” is a named hypothesis frame, not a conclusion. diff --git a/research/language-selection.md b/research/language-selection.md index 8b13789..a0fa579 100644 --- a/research/language-selection.md +++ b/research/language-selection.md @@ -1 +1,92 @@ +# Programming-Language Selection Under AI +## Question +Does AI-assisted development change which properties make a programming language and toolchain attractive for a workload? + +## Hypothesis link +Primary: **H2 — Verification Selection** + +## Working model +Traditional language choice includes ecosystem, performance, interoperability, deployment constraints, maintainability, and human ergonomics. + +CONSTRAINT-SHIFT adds AI-relevant dimensions without assuming they dominate: machine verifiability, diagnostic feedback quality, generation success, repairability, tool automation, model familiarity, and ecosystem accessibility to agents. + +## AI Language Fitness is a vector first +Phase 0 rejects a premature universal score. A language/toolchain observation should initially preserve separate measured dimensions. Definitions and measurement procedures must precede aggregation. + +## Experimental design + +H2 contains a causal idea — that machine-actionable diagnostics and enforceable constraints improve AI-assisted development — so the project must not infer that cause from a raw cross-language ranking alone. + +### A — Cross-language outcome study + +Give a fixed model/agent an equivalent task contract across several languages and toolchains. + +Record first-generation validity, compile attempts, diagnostic cycles, test failures, repair iterations, human interventions, completion outcome, static/formal check outcomes, and time/token/cost data where reliable. + +This study is **descriptive and associational**. It can show that language/toolchain outcomes differ under the tested conditions, but by itself it cannot establish that diagnostic quality or constraint strength caused the difference. + +### B — Diagnostic-feedback ablation + +Test diagnostic feedback within the same language, compiler/toolchain, task, model, dependency set, resource budget, and acceptance criteria. + +Where technically practical, randomize otherwise matched trials between predeclared feedback conditions such as: + +- full native compiler diagnostics; +- normalized diagnostics with nonessential explanatory detail removed; +- location/error-class information without full diagnostic prose; +- compile success/failure status with diagnostic text withheld. + +The compiler's acceptance decision and task contract remain unchanged; only the diagnostic information exposed to the agent is manipulated. + +Measure repair success, repair iterations, invalid edits, human intervention, time, and token/cost data where reliable. + +A reproducible gradient across these conditions can support a causal claim about **diagnostic feedback** more directly than cross-language comparison. + +### C — Constraint ablation + +Constraint effects require a separate controlled design. Where a language/toolchain exposes a check that can be enabled or disabled without changing the task specification, compare matched trials with that check enforced/exposed to the agent versus withheld from the agent. Both arms must still be evaluated against the same external behavioural contract. + +After each final artifact is frozen, run the **same withheld constraint check offline on artifacts from both arms** without feeding that result back into the control arm. Report: + +- behavioural-contract success; +- final constraint-check pass/fail and defect count for both arms; +- repair iterations and effort incurred while the check was exposed/enforced; +- defects found only by the common offline evaluation. + +This prevents the control arm from being counted as equally correct merely because an unchecked constraint violation escaped behavioural tests. If the checked property can instead be encoded into a common independent acceptance contract, that is also acceptable, but the property must be measured identically in both arms. + +Examples may include optional static-analysis, lint, contract, or type-checking modes where the manipulation is well-defined. The exact check, common offline evaluation procedure, and semantic consequences must be documented before trials begin. + +If constraint strength cannot be varied cleanly within a toolchain, the project must report cross-language results as association rather than causal evidence for H2. + +## Confound control + +For controlled H2 experiments, hold constant where practical: + +- model and model version; +- agent/tool version and system instructions; +- task specification and acceptance suite; +- dependency versions and allowed libraries; +- hardware/OS execution environment; +- resource and stopping budgets; +- initial context, aside from the declared feedback manipulation. + +Randomize or counterbalance trial order, use fresh contexts where carry-over could occur, repeat across multiple tasks, and retain all failed trials. + +Training-corpus familiarity and ecosystem maturity may remain residual confounds in cross-language studies. They should be measured or discussed rather than silently attributed to diagnostics. + +## Important confounds +- training-corpus imbalance; +- package/library availability; +- task-language suitability; +- compiler maturity; +- agent-specific tool integration; +- differing safety guarantees; +- version skew. + +## Legacy languages +COBOL, Fortran, Ada, C, and older C++ should not be evaluated solely by greenfield popularity. Installed-base value and maintenance constraints belong to the legacy-preservation track. + +## Phase 0 position +No language is declared an AI-era winner or loser. diff --git a/research/legacy-preservation.md b/research/legacy-preservation.md index 8b13789..278be46 100644 --- a/research/legacy-preservation.md +++ b/research/legacy-preservation.md @@ -1 +1,95 @@ +# Legacy Preservation +## Question +Can AI reduce the maintenance burden created by declining human expertise in legacy languages and systems? + +## Hypothesis link +Primary: **H3 — Legacy Preservation Paradox** + +## Motivation +A language can lose new adopters while remaining operationally important because valuable systems, numerical models, embedded software, or institutional workflows already depend on it. + +The question is whether AI changes the economics and risk of maintaining software whose expert population is scarce. + +## Candidate languages +Depending on reproducible toolchain availability and suitable corpora: COBOL, Fortran, Ada, C, older C++, and other historically important languages with maintained compilers or interpreters. + +## Primary assistance contrast + +H3 is comparative. Successful AI-assisted maintenance alone cannot establish reduced maintenance burden. + +For each primary legacy-maintenance task, compare matched conditions: + +- **AI-assisted maintenance:** the maintainer may use the predeclared AI assistant and the conventional tools allowed by the protocol. +- **Unassisted conventional maintenance:** no generative AI is available; the maintainer receives the same code, task contract, behavioural tests, documentation access, and conventional compiler/debugging tools. + +Hold the task, acceptance suite, effort or time budget, execution environment, and allowed non-AI tools constant where practical. Match or stratify maintainer expertise. If the same participants experience both conditions, counterbalance tasks and condition order to reduce learning and carry-over effects; otherwise randomize matched participants/tasks where feasible. + +Predeclare the primary burden measures, such as active human time, elapsed time, interventions, verification effort, or another justified cost measure. Do not switch to a more favorable burden metric after inspecting results. + +## Scarcity and lifecycle operationalization + +H3 specifically concerns **scarce human expertise** and **operational lifetime**, so neither may be inferred from one short repair task with plentiful experts. + +Before inspecting H3 outcomes, predeclare at least two expertise/scarcity strata. Suitable designs include: + +- maintainers stratified by independently established language-specific expertise; +- conditions with high versus constrained access to qualified expert consultation under a fixed expert-hour budget; +- a combination of maintainer expertise and expert-access limits. + +Scarcity strata must be defined independently of the AI-assisted outcomes. Age, language popularity, or anecdotal claims about a shrinking workforce are not substitutes for a measured experimental scarcity condition. + +Also predeclare a **maintenance-viability horizon** or equivalent lifecycle proxy. One suitable proxy is the number of sequential maintenance tasks that can be completed while preserving the behavioural contract before a fixed cumulative maintenance budget, scarce-expert-hour budget, or stopping threshold is exhausted. + +This horizon is a laboratory proxy for retention pressure, not a direct estimate of calendar years of production lifetime. Any extrapolation from the proxy to real system retirement must be reported separately and cannot be treated as established by these experiments. + +Primary H3 analysis should compare AI-assisted and unassisted maintenance **within each scarcity stratum** and test whether AI reduces scarce-expert consumption and/or extends the predeclared maintenance-viability horizon as expert access becomes more constrained. + +## Candidate experiments + +### L1 — Explain and localize across scarcity strata +Provide matched AI-assisted and unassisted maintainers with an unfamiliar legacy codebase plus the same failing behavioural test in each predeclared expertise/scarcity stratum. + +Measure defect-localization accuracy, time or effort to a correct localization, scarce-expert consultation consumed, unsupported assumptions, and failure rate under the same stopping rule. + +### L2 — Constrained repair across scarcity strata +Require a minimal repair preserving a fixed behavioural contract in both assistance conditions and repeat across the predeclared expertise/scarcity strata. + +Measure successful repair rate, behavioural regressions, compiler/test cycles, active human effort, scarce-expert consultation consumed, elapsed time, verification burden, and unsupported assumptions introduced during the repair. + +### L3 — Repair versus rewrite +Use a factorial comparison where practical: + +- assistance: AI-assisted versus unassisted; +- strategy: constrained repair versus rewrite into a declared modern target. + +All cells use the same observable behavioural contract and declared non-functional requirements. This distinguishes the effect of AI assistance from the separate repair-versus-rewrite decision. + +### L4 — Maintenance-viability horizon +Run a predeclared sequence of representative maintenance tasks under a fixed cumulative maintenance budget and scarce-expert-hour budget. + +Compare AI-assisted and unassisted conditions on: + +- number of tasks completed while preserving the behavioural contract; +- cumulative scarce-expert consultation consumed; +- cumulative verification effort; +- the task or threshold at which the maintenance budget is exhausted. + +This experiment supplies the lifecycle proxy required by H3. It does not by itself establish a real-world calendar lifetime. + +## Interpretation + +Evidence for H3 requires an improvement relative to the matched unassisted baseline **under predeclared scarce-expertise conditions** and evidence on the declared maintenance-viability horizon or equivalent lifecycle proxy. An AI-assisted success rate reported without those comparisons is descriptive evidence about AI performance, not evidence for the full Legacy Preservation Paradox. + +Any apparent time, expert-access, or effort savings must be reported alongside verification cost and behavioural regressions. A faster patch that requires enough additional verification to erase the savings does not support the maintenance-cost mechanism. A short benchmark must not be described as directly proving extra calendar years of operational life. + +## Safety boundary +Toy or public experiments cannot establish that autonomous maintenance is safe for production banking, trading, medical, industrial, defence, or other mission-critical systems. + +Human-participant maintenance studies are also subject to I11 and the human-evaluation safeguards in METHODOLOGY.md. + +## Falsification pressure +The paradox is weakened if AI-assisted conditions do not reduce burden relative to unassisted conventional maintenance under scarce-expertise strata, if AI does not reduce scarce-expert consumption as access tightens, if the predeclared maintenance-viability horizon is not extended, or if apparent gains are offset by verification burden or behavioural risk. + +## Phase 0 position +Untested. diff --git a/research/specification-primacy.md b/research/specification-primacy.md index 8b13789..16dfa2c 100644 --- a/research/specification-primacy.md +++ b/research/specification-primacy.md @@ -1 +1,90 @@ +# Specification Primacy +## Question +Does the relative value of a persistent specification increase as independently measured AI implementation capability increases? + +## Hypothesis link +Primary: **H1 — Specification Primacy** + +## Operational distinction +A **prompt** is an instruction used for one interaction. A **persistent specification** is a retained artifact that defines required behaviour, constraints, acceptance conditions, or interfaces and is reused across implementation or verification steps. + +H1 is directional. Demonstrating that specifications help one fixed model is not enough. The experiment must vary independently measured implementation capability and test whether the specification advantage changes with it. + +## Capability operationalization + +Before evaluating H1 outcomes, define at least two and preferably three capability strata. + +A capability stratum is a frozen model/agent configuration whose implementation capability is measured on a **held-out capability battery** that is separate from the H1 task set. The battery should use the same broad execution conditions and resource accounting as the H1 study, but its tasks must not be reused as H1 outcomes. + +The capability measure and stratum boundaries must be declared before inspecting the specification-versus-transient workflow comparison. Acceptable capability measures may include: + +- behavioural task completion against hidden acceptance tests; +- successful repair of independently seeded implementation defects; +- implementation success under a fixed tool and iteration budget. + +Model name, release date, parameter count, price, or reputation alone does not establish a capability stratum. + +Each stratum must freeze the relevant model version, agent/tool version, system instructions, allowed tools, and resource budget. If these differ for unavoidable reasons, the differences must be declared as limitations. + +## Primary experimental design + +Use a factorial comparison: + +- **Capability condition:** predeclared low / medium / high strata, or another predeclared ordered set with at least two levels. +- **Workflow condition:** persistent specification versus a matched transient prompt-only workflow with no durable specification available after the initial instruction. + +Tasks, acceptance suites, resource budgets, starting repositories, and allowed tools should be matched within each capability stratum. Randomize or counterbalance task assignment where practical and use fresh contexts to prevent leakage between workflow conditions. + +Define the persistent-specification advantage for a primary outcome before analysis. For a success-rate outcome, for example: + +Delta(c) = success_spec(c) - success_transient(c) + +H1 predicts that Delta(c) increases with independently measured capability c. Secondary outcomes can include human correction, behavioural divergence, repair iterations, or change-propagation success, but their direction must be predeclared. + +The analysis should estimate the **capability × workflow interaction**, not merely compare overall averages. + +## Candidate experiments + +### S1 — Regeneration across capability strata +Create a fixed behavioural specification and matched transient instruction, then generate multiple independent implementations at each capability stratum. + +Measure acceptance-suite pass rate, behavioural divergence, required human correction, and differences not permitted by the specification. Test whether the persistent-specification advantage changes across strata. + +### S2 — Change propagation across capability strata +Modify one requirement and compare: + +- a workflow where the durable specification is updated and reused; +- a matched workflow where the change exists only in transient conversational instruction. + +Repeat at each capability stratum and test the capability × workflow interaction. + +### S3 — Implementation replacement across capability strata +Replace an implementation with a newly generated implementation in the same or a different language while preserving an observable contract. + +Repeat under the predeclared capability strata. Measure whether higher-capability configurations derive a larger relative benefit from the persistent contract when replacing or regenerating implementation code. + +## Evidence needed +A specification advantage at one capability level demonstrates specification utility under that tested condition. + +Evidence for H1 specifically requires: + +1. capability levels established independently of H1 outcomes; +2. matched persistent-specification and transient workflow conditions at each level; +3. repeated observations with failures retained; +4. an estimated capability × workflow interaction or equivalent predeclared trend test. + +If capability and workflow are both changed at the same time, H1 is not identified. + +## Failure modes +- capability strata are defined after viewing H1 results; +- model identity or marketing labels are substituted for measured capability; +- capability-battery tasks leak into the H1 task set; +- acceptance tests encode behaviour not stated in the task contract; +- the specification merely restates implementation details; +- conversational context leaks into supposedly transient or specification-only trials; +- higher-capability conditions receive larger budgets or additional tools without those differences being controlled or declared; +- repeated generations are scored subjectively instead of against a fixed contract. + +## Phase 0 position +Untested. diff --git a/research/tool-legitimacy-gap.md b/research/tool-legitimacy-gap.md index 8b13789..bc29cd3 100644 --- a/research/tool-legitimacy-gap.md +++ b/research/tool-legitimacy-gap.md @@ -1 +1,62 @@ +# Tool Legitimacy Gap +## Question +Does knowledge that AI participated in producing a software artifact change how people evaluate that artifact independently of its demonstrated behaviour? + +## Hypothesis link +Primary: **H6 — Tool Legitimacy Gap** + +## Core design principle +When testing disclosure, both the evaluated artifact **and its actual production history** must remain constant. The primary manipulation is whether truthful production-method information is disclosed. + +A minimal randomized disclosure design for an artifact that was in fact created with AI assistance uses: + +- **Undisclosed control:** participants receive no production-method information. +- **AI-disclosed treatment:** participants receive a predeclared, truthful statement that the same artifact was created with AI-assisted development. + +This contrast changes disclosure while holding the artifact and underlying authorship/provenance constant. It can therefore estimate a disclosure effect under the tested wording and population. + +Descriptions that imply different degrees of authorship — for example “human-created,” “AI-assisted,” and “primarily AI-generated” — answer a different framing question because they vary the stated production process as well as disclosure. Such conditions may be studied separately, but they must not be used to estimate the primary AI Disclosure Penalty unless the design independently identifies those effects. + +## Candidate outcomes +- perceived quality; +- creativity; +- originality; +- authenticity; +- technical competence; +- trust; +- willingness to download or use; +- willingness to recommend or contribute. + +## AI Disclosure Penalty +For a declared rating variable R: + +D_AI = R_undisclosed - R_AI-disclosed + +The two terms must refer to the same artifact and actual production history. The only intended difference in the primary contrast is disclosure of the truthful AI-assistance statement. + +The direction and size must be reported with uncertainty. The result is conditional on the tested disclosure wording, artifact, sampling frame, and study population. + +## Controls +- identical artifact across disclosure groups; +- identical actual production history across disclosure groups; +- truthful disclosure wording in the disclosed condition; +- an undisclosed baseline for the D_AI contrast; +- randomized or appropriately counterbalanced assignment; +- same presentation environment; +- same information other than the disclosure manipulation; +- predefined primary outcomes; +- inclusion, exclusion, invalid-trial, timeout, denominator, and missing-data rules frozen before outcome inspection. + +## Ethics +Before recruitment or participant data collection begins, the study protocol must define the informed-consent process, privacy and data-minimization safeguards, data handling and retention, and any applicable ethics or institutional review. Required approvals must be in place before recruitment or collection; if formal review is not required, that determination must be documented beforehand. + +A study must not falsely attribute an artifact to a human or AI merely to create a treatment condition unless such deception is independently justified, approved before recruitment where required, and explicitly handled in the consent/debriefing protocol. The preferred Phase 7 design uses truthful disclosure versus non-disclosure. + +## Alternative explanations +Observed differences could reflect beliefs about labour displacement, copyright or training-data concerns, expectations about quality, dislike of a tool, perceived effort, or prior AI familiarity. + +These should be measured or discussed rather than collapsed into a single “anti-AI” interpretation. + +## Phase 0 position +Untested. diff --git a/scripts/validate_phase0.py b/scripts/validate_phase0.py new file mode 100644 index 0000000..40e5644 --- /dev/null +++ b/scripts/validate_phase0.py @@ -0,0 +1,916 @@ +#!/usr/bin/env python3 +"""Validate the Phase 0 research contract using one CommonMark parse per file.""" + +from __future__ import annotations + +from pathlib import Path +import re +import logging +import sys +from typing import NamedTuple + +try: + from markdown_it import MarkdownIt + from markdown_it.token import Token + from markdown_it.common.utils import charCodeAt, isSpace, isStrSpace, normalizeReference + from markdown_it.rules_block import StateBlock + from markdown_it.rules_inline import StateInline +except ModuleNotFoundError as exc: + raise SystemExit( + "Phase 0 validator dependencies are missing; run " + "python3 -m pip install --require-hashes -r requirements.txt" + ) from exc + +ROOT = Path(__file__).resolve().parents[1] + +REQUIRED_FILES = ( + "README.md", + "HYPOTHESES.md", + "INVARIANTS.md", + "METHODOLOGY.md", + "ROADMAP.md", + "TERMINOLOGY.md", + "research/specification-primacy.md", + "research/language-selection.md", + "research/legacy-preservation.md", + "research/ai-modding.md", + "research/tool-legitimacy-gap.md", +) + +HYPOTHESES = ( + "H1 — Specification Primacy", + "H2 — Verification Selection", + "H3 — Legacy Preservation Paradox", + "H4 — Modding Mutation", + "H5 — Recombination Acceleration", + "H6 — Tool Legitimacy Gap", +) + +TERMS = ( + "Constraint Shift", + "Specification Primacy", + "Implementation Fungibility", + "AI Language Fitness", + "Machine Verifiability", + "Diagnostic Feedback Quality", + "Legacy Preservation Paradox", + "Second Modding Revolution", + "Modding Mutation Rate", + "Recombination Acceleration", + "Tool Legitimacy Gap", + "AI Disclosure Penalty", + "Research Contract", +) + +INVARIANTS = ( + "I1 — No conclusion by model assertion", + "I2 — Equivalent-task comparisons", + "I3 — Preserve failures", + "I4 — Record intervention", + "I5 — Record environment", + "I6 — Separate generation from verification", + "I7 — Observable contracts before equivalence claims", + "I8 — No predetermined language winner", + "I9 — Negative results are publishable results", + "I10 — Social experiments control the artifact", + "I11 — Human-subject safeguards", + "I12 — Motivation is not evidence", + "I13 — Reproducible validators", + "I14 — Contract changes are explicit", +) + +HYPOTHESIS_HEADING_RE = re.compile(r"^H\d+ — .+$") +INVARIANT_HEADING_RE = re.compile(r"^I\d+ — .+$") + + +class Heading(NamedTuple): + title: str + line: int + + +class ProseBlock(NamedTuple): + text: str + line: int + + +class Document(NamedTuple): + headings: tuple[Heading, ...] + prose: tuple[ProseBlock, ...] + + +# The four rules below are adapted from markdown-it-py 4.2.0 (MIT). +# See THIRD_PARTY_NOTICES.md. They run in the same parser state, not as +# independent scans. Changes: raw reference-label length <=999, and trim +# only CommonMark whitespace from paragraphs. All other parsing is upstream. +LOGGER = logging.getLogger(__name__) + +def reference(state: StateBlock, startLine: int, _endLine: int, silent: bool) -> bool: + LOGGER.debug( + "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent + ) + + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + nextLine = startLine + 1 + + if state.is_code_block(startLine): + return False + + if state.src[pos] != "[": + return False + + string = state.src[pos : maximum + 1] + + # string = state.getLines(startLine, nextLine, state.blkIndent, False).strip() + maximum = len(string) + + labelEnd = None + pos = 1 + while pos < maximum: + ch = charCodeAt(string, pos) + if ch == 0x5B: # /* [ */ + return False + elif ch == 0x5D: # /* ] */ + labelEnd = pos + break + elif ch == 0x0A: # /* \n */ + if (lineContent := getNextLine(state, nextLine)) is not None: + string += lineContent + maximum = len(string) + nextLine += 1 + elif ch == 0x5C: # /* \ */ + pos += 1 + if ( + pos < maximum + and charCodeAt(string, pos) == 0x0A + and (lineContent := getNextLine(state, nextLine)) is not None + ): + string += lineContent + maximum = len(string) + nextLine += 1 + pos += 1 + + if ( + labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A + ): # /* : */ + return False + + # [label]: destination 'title' + # ^^^ skip optional whitespace here + pos = labelEnd + 2 + while pos < maximum: + ch = charCodeAt(string, pos) + if ch == 0x0A: + if (lineContent := getNextLine(state, nextLine)) is not None: + string += lineContent + maximum = len(string) + nextLine += 1 + elif isSpace(ch): + pass + else: + break + pos += 1 + + # [label]: destination 'title' + # ^^^^^^^^^^^ parse this + destRes = state.md.helpers.parseLinkDestination(string, pos, maximum) + if not destRes.ok: + return False + + href = state.md.normalizeLink(destRes.str) + if not state.md.validateLink(href): + return False + + pos = destRes.pos + + # save cursor state, we could require to rollback later + destEndPos = pos + destEndLineNo = nextLine + + # [label]: destination 'title' + # ^^^ skipping those spaces + start = pos + while pos < maximum: + ch = charCodeAt(string, pos) + if ch == 0x0A: + if (lineContent := getNextLine(state, nextLine)) is not None: + string += lineContent + maximum = len(string) + nextLine += 1 + elif isSpace(ch): + pass + else: + break + pos += 1 + + # [label]: destination 'title' + # ^^^^^^^ parse this + titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, None) + while titleRes.can_continue: + if (lineContent := getNextLine(state, nextLine)) is None: + break + string += lineContent + pos = maximum + maximum = len(string) + nextLine += 1 + titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, titleRes) + + if pos < maximum and start != pos and titleRes.ok: + title = titleRes.str + pos = titleRes.pos + else: + title = "" + pos = destEndPos + nextLine = destEndLineNo + + # skip trailing spaces until the rest of the line + while pos < maximum: + ch = charCodeAt(string, pos) + if not isSpace(ch): + break + pos += 1 + + if pos < maximum and charCodeAt(string, pos) != 0x0A and title: + # garbage at the end of the line after title, + # but it could still be a valid reference if we roll back + title = "" + pos = destEndPos + nextLine = destEndLineNo + while pos < maximum: + ch = charCodeAt(string, pos) + if not isSpace(ch): + break + pos += 1 + + if pos < maximum and charCodeAt(string, pos) != 0x0A: + # garbage at the end of the line + return False + + # Compatibility correction: length is checked before label normalization. + if labelEnd - 1 > 999: + return False + label = normalizeReference(string[1:labelEnd]) + if not label: + # CommonMark 0.20 disallows empty labels + return False + + # Reference can not terminate anything. This check is for safety only. + if silent: + return True + + if "references" not in state.env: + state.env["references"] = {} + + state.line = nextLine + + # note, this is not part of markdown-it JS, but is useful for renderers + if state.md.options.get("inline_definitions", False): + token = state.push("definition", "", 0) + token.meta = { + "id": label, + "title": title, + "url": href, + "label": string[1:labelEnd], + } + token.map = [startLine, state.line] + + if label not in state.env["references"]: + state.env["references"][label] = { + "title": title, + "href": href, + "map": [startLine, state.line], + } + else: + state.env.setdefault("duplicate_refs", []).append( + { + "title": title, + "href": href, + "label": label, + "map": [startLine, state.line], + } + ) + + return True + + +def getNextLine(state: StateBlock, nextLine: int) -> None | str: + endLine = state.lineMax + + if nextLine >= endLine or state.isEmpty(nextLine): + # empty line or end of input + return None + + isContinuation = False + + # this would be a code block normally, but after paragraph + # it's considered a lazy continuation regardless of what's there + if state.is_code_block(nextLine): + isContinuation = True + + # quirk for blockquotes, this line should already be checked by that rule + if state.sCount[nextLine] < 0: + isContinuation = True + + if not isContinuation: + terminatorRules = state.md.block.ruler.getRules("reference") + oldParentType = state.parentType + state.parentType = "reference" + + # Some tags can terminate paragraph without empty line. + terminate = False + for terminatorRule in terminatorRules: + if terminatorRule(state, nextLine, endLine, True): + terminate = True + break + + state.parentType = oldParentType + + if terminate: + # terminated by another block + return None + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + maximum = state.eMarks[nextLine] + + # max + 1 explicitly includes the newline + return state.src[pos : maximum + 1] + + +def link(state: StateInline, silent: bool) -> bool: + href = "" + title = "" + label = None + oldPos = state.pos + maximum = state.posMax + start = state.pos + parseReference = True + + if state.src[state.pos] != "[": + return False + + labelStart = state.pos + 1 + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, True) + + # parser failed to find ']', so it's not a valid link + if labelEnd < 0: + return False + + pos = labelEnd + 1 + + if pos < maximum and state.src[pos] == "(": + # + # Inline link + # + + # might have found a valid shortcut link, disable reference parsing + parseReference = False + + # [link]( "title" ) + # ^^ skipping these spaces + pos += 1 + while pos < maximum: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + if pos >= maximum: + return False + + # [link]( "title" ) + # ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if res.ok: + href = state.md.normalizeLink(res.str) + if state.md.validateLink(href): + pos = res.pos + else: + href = "" + + # [link]( "title" ) + # ^^ skipping these spaces + start = pos + while pos < maximum: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + # [link]( "title" ) + # ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax) + if pos < maximum and start != pos and res.ok: + title = res.str + pos = res.pos + + # [link]( "title" ) + # ^^ skipping these spaces + while pos < maximum: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + if pos >= maximum or state.src[pos] != ")": + # parsing a valid shortcut link failed, fallback to reference + parseReference = True + + pos += 1 + + if parseReference: + # + # Link reference + # + if "references" not in state.env: + return False + + if pos < maximum and state.src[pos] == "[": + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if pos >= 0: + label = state.src[start:pos] + pos += 1 + else: + pos = labelEnd + 1 + + else: + pos = labelEnd + 1 + + # covers label == '' and label == undefined + # (collapsed reference link and shortcut reference link respectively) + if not label: + label = state.src[labelStart:labelEnd] + + # Compatibility correction: explicit, collapsed and shortcut labels. + if len(label) > 999: + state.pos = oldPos + return False + label = normalizeReference(label) + + ref = state.env["references"].get(label, None) + if not ref: + state.pos = oldPos + return False + + href = ref["href"] + title = ref["title"] + + # + # We found the end of the link, and know for a fact it's a valid link + # so all that's left to do is to call tokenizer. + # + if not silent: + state.pos = labelStart + state.posMax = labelEnd + + token = state.push("link_open", "a", 1) + token.attrs = {"href": href} + + if title: + token.attrSet("title", title) + + # note, this is not part of markdown-it JS, but is useful for renderers + if label and state.md.options.get("store_labels", False): + token.meta["label"] = label + + state.linkLevel += 1 + state.md.inline.tokenize(state) + state.linkLevel -= 1 + + token = state.push("link_close", "a", -1) + + state.pos = pos + state.posMax = maximum + return True + + +def image(state: StateInline, silent: bool) -> bool: + label = None + href = "" + oldPos = state.pos + max = state.posMax + + if state.src[state.pos] != "!": + return False + + if state.pos + 1 < state.posMax and state.src[state.pos + 1] != "[": + return False + + labelStart = state.pos + 2 + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, False) + + # parser failed to find ']', so it's not a valid link + if labelEnd < 0: + return False + + pos = labelEnd + 1 + + if pos < max and state.src[pos] == "(": + # + # Inline link + # + + # [link]( "title" ) + # ^^ skipping these spaces + pos += 1 + while pos < max: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + if pos >= max: + return False + + # [link]( "title" ) + # ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if res.ok: + href = state.md.normalizeLink(res.str) + if state.md.validateLink(href): + pos = res.pos + else: + href = "" + + # [link]( "title" ) + # ^^ skipping these spaces + start = pos + while pos < max: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + # [link]( "title" ) + # ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax, None) + if pos < max and start != pos and res.ok: + title = res.str + pos = res.pos + + # [link]( "title" ) + # ^^ skipping these spaces + while pos < max: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + else: + title = "" + + if pos >= max or state.src[pos] != ")": + state.pos = oldPos + return False + + pos += 1 + + else: + # + # Link reference + # + if "references" not in state.env: + return False + + # /* [ */ + if pos < max and state.src[pos] == "[": + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if pos >= 0: + label = state.src[start:pos] + pos += 1 + else: + pos = labelEnd + 1 + else: + pos = labelEnd + 1 + + # covers label == '' and label == undefined + # (collapsed reference link and shortcut reference link respectively) + if not label: + label = state.src[labelStart:labelEnd] + + # Compatibility correction: explicit, collapsed and shortcut labels. + if len(label) > 999: + state.pos = oldPos + return False + label = normalizeReference(label) + + ref = state.env["references"].get(label, None) + if not ref: + state.pos = oldPos + return False + + href = ref["href"] + title = ref["title"] + + # + # We found the end of the link, and know for a fact it's a valid link + # so all that's left to do is to call tokenizer. + # + if not silent: + content = state.src[labelStart:labelEnd] + + tokens: list[Token] = [] + state.md.inline.parse(content, state.md, state.env, tokens) + + token = state.push("image", "img", 0) + token.attrs = {"src": href, "alt": ""} + token.children = tokens or None + token.content = content + + if title: + token.attrSet("title", title) + + # note, this is not part of markdown-it JS, but is useful for renderers + if label and state.md.options.get("store_labels", False): + token.meta["label"] = label + + state.pos = pos + state.posMax = max + return True + + +def paragraph(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug( + "entering paragraph: %s, %s, %s, %s", state, startLine, endLine, silent + ) + + nextLine = startLine + 1 + ruler = state.md.block.ruler + terminatorRules = ruler.getRules("paragraph") + endLine = state.lineMax + + oldParentType = state.parentType + state.parentType = "paragraph" + + # jump line-by-line until empty one or EOF + while nextLine < endLine: + if state.isEmpty(nextLine): + break + # this would be a code block normally, but after paragraph + # it's considered a lazy continuation regardless of what's there + if state.sCount[nextLine] - state.blkIndent > 3: + nextLine += 1 + continue + + # quirk for blockquotes, this line should already be checked by that rule + if state.sCount[nextLine] < 0: + nextLine += 1 + continue + + # Some tags can terminate paragraph without empty line. + terminate = False + for terminatorRule in terminatorRules: + if terminatorRule(state, nextLine, endLine, True): + terminate = True + break + + if terminate: + break + + nextLine += 1 + + content = state.getLines(startLine, nextLine, state.blkIndent, False).strip(" \t\r\n") + + state.line = nextLine + + token = state.push("paragraph_open", "p", 1) + token.map = [startLine, state.line] + + token = state.push("inline", "", 0) + token.content = content + token.map = [startLine, state.line] + token.children = [] + + token = state.push("paragraph_close", "p", -1) + + state.parentType = oldParentType + + return True + + +# HTML must be parsed so block ownership and hidden references are understood. +# No GFM plugins: task-list markers remain ordinary paragraph text. +PARSER = MarkdownIt("commonmark", {"html": True}) +PARSER.block.ruler.at("reference", reference) +PARSER.block.ruler.at("paragraph", paragraph) +PARSER.inline.ruler.at("link", link) +PARSER.inline.ruler.at("image", image) + + +def read_text(relative: str) -> str: + path = ROOT / relative + try: + text = path.read_text(encoding="utf-8") + except FileNotFoundError as exc: + raise AssertionError(f"missing required file: {relative}") from exc + except (UnicodeDecodeError, OSError) as exc: + raise AssertionError(f"cannot read required file: {relative}: {exc}") from exc + if not text.strip(): + raise AssertionError(f"required file is empty: {relative}") + return text + + +def load_contract_texts() -> tuple[dict[str, str], list[str]]: + texts: dict[str, str] = {} + errors: list[str] = [] + for relative in REQUIRED_FILES: + try: + texts[relative] = read_text(relative) + except AssertionError as exc: + errors.append(str(exc)) + return texts, errors + + +def inline_prose(children: list[Token]) -> tuple[str, ...]: + """Project decoded text, preserving boundaries around excluded inline nodes. + + The parser owns escapes, entities, emphasis eligibility and resolved links. + Code, images (including alt text), HTML and unknown nodes split the text; + they cannot disappear in a way that manufactures a required phrase. + """ + parts: list[str] = [] + blocks: list[str] = [] + + def flush() -> None: + text = re.sub(r"[ \t\r\n]+", " ", "".join(parts)).strip(" \t") + if text: + blocks.append(text) + parts.clear() + + for child in children: + if child.type == "text": + parts.append(child.content) + elif child.type in {"softbreak", "hardbreak"}: + parts.append(" ") + elif child.type in { + "em_open", "em_close", "strong_open", "strong_close", + "link_open", "link_close", + }: + continue + else: + flush() + flush() + return tuple(blocks) + + +def parse_document(text: str) -> Document: + """One parse provides block ownership, references, headings and prose. + + Only level-2 ATX headings count toward inventories; Setext headings are + intentionally ineligible. ATX headings at any level can supply prose. + Separate leaf blocks and excluded inline nodes never join into a phrase. + """ + tokens = PARSER.parse(text) + headings: list[Heading] = [] + prose: list[ProseBlock] = [] + for index, token in enumerate(tokens): + if token.type != "inline": + continue + owner = tokens[index - 1] + atx = owner.type == "heading_open" and owner.markup.startswith("#") + if owner.type != "paragraph_open" and not atx: + continue + line = (token.map or owner.map or [0])[0] + 1 + segments = inline_prose(token.children or []) + prose.extend(ProseBlock(segment, line) for segment in segments) + if atx and owner.tag == "h2" and segments: + headings.append(Heading("\n".join(segments), line)) + return Document(tuple(headings), tuple(prose)) + + + +def markdown_level2_headings(text: str) -> tuple[str, ...]: + return tuple(heading.title for heading in parse_document(text).headings) + + +def hypothesis_headings(text: str) -> tuple[str, ...]: + return tuple(h for h in markdown_level2_headings(text) + if HYPOTHESIS_HEADING_RE.fullmatch(h)) + + +def terminology_headings(text: str) -> tuple[str, ...]: + return markdown_level2_headings(text) + + +def invariant_headings(text: str) -> tuple[str, ...]: + return tuple(h for h in markdown_level2_headings(text) + if INVARIANT_HEADING_RE.fullmatch(h)) + + +def markdown_rendered_prose_text(text: str) -> str: + return "\n".join(block.text for block in parse_document(text).prose) + + +def validate_texts(texts: dict[str, str]) -> list[str]: + errors: list[str] = [] + + missing = [relative for relative in REQUIRED_FILES if relative not in texts] + if missing: + errors.extend(f"missing required file: {relative}" for relative in missing) + return errors + + documents = {name: parse_document(texts[name]) for name in REQUIRED_FILES} + observed_hypotheses = tuple( + heading.title for heading in documents["HYPOTHESES.md"].headings + if HYPOTHESIS_HEADING_RE.fullmatch(heading.title) + ) + if observed_hypotheses != HYPOTHESES: + errors.append( + "hypothesis headings must exactly match the Phase 0 contract " + f"(expected {HYPOTHESES!r}, observed {observed_hypotheses!r})" + f"; HYPOTHESES.md headings at lines " + + ", ".join(str(h.line) for h in documents["HYPOTHESES.md"].headings) + ) + + observed_terms = tuple(h.title for h in documents["TERMINOLOGY.md"].headings) + if observed_terms != TERMS: + for term in TERMS: + if term not in observed_terms: + errors.append(f"missing terminology definition: {term}") + duplicates = tuple( + term for term in observed_terms if observed_terms.count(term) > 1 + ) + if duplicates: + errors.append( + "duplicate terminology definitions: " + + ", ".join(dict.fromkeys(duplicates)) + ) + unexpected = tuple(term for term in observed_terms if term not in TERMS) + if unexpected: + errors.append( + "unexpected terminology definitions: " + + ", ".join(unexpected) + ) + if not any( + error.startswith( + ( + "missing terminology definition:", + "duplicate terminology definitions:", + "unexpected terminology definitions:", + ) + ) + for error in errors + ): + errors.append( + "terminology headings must exactly match the Phase 0 contract " + f"(expected {TERMS!r}, observed {observed_terms!r})" + ) + + observed_invariants = tuple( + heading.title for heading in documents["INVARIANTS.md"].headings + if INVARIANT_HEADING_RE.fullmatch(heading.title) + ) + if observed_invariants != INVARIANTS: + errors.append( + "invariant headings must exactly match the Phase 0 contract " + f"(expected {INVARIANTS!r}, observed {observed_invariants!r})" + f"; INVARIANTS.md headings at lines " + + ", ".join(str(h.line) for h in documents["INVARIANTS.md"].headings) + ) + + roadmap_headings = tuple(h.title for h in documents["ROADMAP.md"].headings) + phase0_heading = "Phase 0 — Foundational Research Contract" + if roadmap_headings.count(phase0_heading) != 1: + errors.append("roadmap does not define exactly one Phase 0 foundational contract") + + if not any( + "machine-checkable Phase 0 validator" in block.text + for block in documents["ROADMAP.md"].prose + ): + errors.append("roadmap does not require Phase 0 validator") + + if not any( + "The thesis is not treated as established fact" in prose.text + for prose in documents["README.md"].prose + ): + errors.append("README must explicitly separate thesis from established fact") + + for path, text in texts.items(): + content_size = sum(1 for char in text if not char.isspace()) + if content_size < 80: + errors.append(f"required artifact is suspiciously small: {path}") + + return errors + + +def validate_repo() -> list[str]: + texts, errors = load_contract_texts() + if errors: + return errors + return validate_texts(texts) + + +def main() -> int: + errors = validate_repo() + if errors: + for error in errors: + print(f"ERROR: {error}", file=sys.stderr) + return 1 + print("Phase 0 research contract: OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/fixtures/COMMONMARK-ATTRIBUTION.md b/tests/fixtures/COMMONMARK-ATTRIBUTION.md new file mode 100644 index 0000000..e03d183 --- /dev/null +++ b/tests/fixtures/COMMONMARK-ATTRIBUTION.md @@ -0,0 +1,5 @@ +`commonmark-0.31.2-spec.json` is the unmodified official CommonMark 0.31.2 example corpus downloaded from [the CommonMark specification](https://spec.commonmark.org/0.31.2/spec.json). + +The specification is by John MacFarlane and is distributed under [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/), as stated on [the official specification page](https://spec.commonmark.org/0.31.2/). Preserve this attribution with the corpus. The generated corpus-structure audit includes examples derived from that corpus under the same attribution/license. + +Independent parser documentation consulted: [markdown-it-py usage](https://markdown-it-py.readthedocs.io/en/latest/using.html). The audit configures the explicit `commonmark` preset. diff --git a/tests/fixtures/commonmark-0.31.2-spec.json b/tests/fixtures/commonmark-0.31.2-spec.json new file mode 100644 index 0000000..1f89e66 --- /dev/null +++ b/tests/fixtures/commonmark-0.31.2-spec.json @@ -0,0 +1,5218 @@ +[ + { + "markdown": "\tfoo\tbaz\t\tbim\n", + "html": "
foo\tbaz\t\tbim\n
\n", + "example": 1, + "start_line": 355, + "end_line": 360, + "section": "Tabs" + }, + { + "markdown": " \tfoo\tbaz\t\tbim\n", + "html": "
foo\tbaz\t\tbim\n
\n", + "example": 2, + "start_line": 362, + "end_line": 367, + "section": "Tabs" + }, + { + "markdown": " a\ta\n ὐ\ta\n", + "html": "
a\ta\nὐ\ta\n
\n", + "example": 3, + "start_line": 369, + "end_line": 376, + "section": "Tabs" + }, + { + "markdown": " - foo\n\n\tbar\n", + "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", + "example": 4, + "start_line": 382, + "end_line": 393, + "section": "Tabs" + }, + { + "markdown": "- foo\n\n\t\tbar\n", + "html": "
    \n
  • \n

    foo

    \n
      bar\n
    \n
  • \n
\n", + "example": 5, + "start_line": 395, + "end_line": 407, + "section": "Tabs" + }, + { + "markdown": ">\t\tfoo\n", + "html": "
\n
  foo\n
\n
\n", + "example": 6, + "start_line": 418, + "end_line": 425, + "section": "Tabs" + }, + { + "markdown": "-\t\tfoo\n", + "html": "
    \n
  • \n
      foo\n
    \n
  • \n
\n", + "example": 7, + "start_line": 427, + "end_line": 436, + "section": "Tabs" + }, + { + "markdown": " foo\n\tbar\n", + "html": "
foo\nbar\n
\n", + "example": 8, + "start_line": 439, + "end_line": 446, + "section": "Tabs" + }, + { + "markdown": " - foo\n - bar\n\t - baz\n", + "html": "
    \n
  • foo\n
      \n
    • bar\n
        \n
      • baz
      • \n
      \n
    • \n
    \n
  • \n
\n", + "example": 9, + "start_line": 448, + "end_line": 464, + "section": "Tabs" + }, + { + "markdown": "#\tFoo\n", + "html": "

Foo

\n", + "example": 10, + "start_line": 466, + "end_line": 470, + "section": "Tabs" + }, + { + "markdown": "*\t*\t*\t\n", + "html": "
\n", + "example": 11, + "start_line": 472, + "end_line": 476, + "section": "Tabs" + }, + { + "markdown": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n", + "html": "

!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~

\n", + "example": 12, + "start_line": 489, + "end_line": 493, + "section": "Backslash escapes" + }, + { + "markdown": "\\\t\\A\\a\\ \\3\\φ\\«\n", + "html": "

\\\t\\A\\a\\ \\3\\φ\\«

\n", + "example": 13, + "start_line": 499, + "end_line": 503, + "section": "Backslash escapes" + }, + { + "markdown": "\\*not emphasized*\n\\
not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\ö not a character entity\n", + "html": "

*not emphasized*\n<br/> not a tag\n[not a link](/foo)\n`not code`\n1. not a list\n* not a list\n# not a heading\n[foo]: /url "not a reference"\n&ouml; not a character entity

\n", + "example": 14, + "start_line": 509, + "end_line": 529, + "section": "Backslash escapes" + }, + { + "markdown": "\\\\*emphasis*\n", + "html": "

\\emphasis

\n", + "example": 15, + "start_line": 534, + "end_line": 538, + "section": "Backslash escapes" + }, + { + "markdown": "foo\\\nbar\n", + "html": "

foo
\nbar

\n", + "example": 16, + "start_line": 543, + "end_line": 549, + "section": "Backslash escapes" + }, + { + "markdown": "`` \\[\\` ``\n", + "html": "

\\[\\`

\n", + "example": 17, + "start_line": 555, + "end_line": 559, + "section": "Backslash escapes" + }, + { + "markdown": " \\[\\]\n", + "html": "
\\[\\]\n
\n", + "example": 18, + "start_line": 562, + "end_line": 567, + "section": "Backslash escapes" + }, + { + "markdown": "~~~\n\\[\\]\n~~~\n", + "html": "
\\[\\]\n
\n", + "example": 19, + "start_line": 570, + "end_line": 577, + "section": "Backslash escapes" + }, + { + "markdown": "\n", + "html": "

https://example.com?find=\\*

\n", + "example": 20, + "start_line": 580, + "end_line": 584, + "section": "Backslash escapes" + }, + { + "markdown": "\n", + "html": "\n", + "example": 21, + "start_line": 587, + "end_line": 591, + "section": "Backslash escapes" + }, + { + "markdown": "[foo](/bar\\* \"ti\\*tle\")\n", + "html": "

foo

\n", + "example": 22, + "start_line": 597, + "end_line": 601, + "section": "Backslash escapes" + }, + { + "markdown": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n", + "html": "

foo

\n", + "example": 23, + "start_line": 604, + "end_line": 610, + "section": "Backslash escapes" + }, + { + "markdown": "``` foo\\+bar\nfoo\n```\n", + "html": "
foo\n
\n", + "example": 24, + "start_line": 613, + "end_line": 620, + "section": "Backslash escapes" + }, + { + "markdown": "  & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸\n", + "html": "

  & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸

\n", + "example": 25, + "start_line": 649, + "end_line": 657, + "section": "Entity and numeric character references" + }, + { + "markdown": "# Ӓ Ϡ �\n", + "html": "

# Ӓ Ϡ �

\n", + "example": 26, + "start_line": 668, + "end_line": 672, + "section": "Entity and numeric character references" + }, + { + "markdown": "" ആ ಫ\n", + "html": "

" ആ ಫ

\n", + "example": 27, + "start_line": 681, + "end_line": 685, + "section": "Entity and numeric character references" + }, + { + "markdown": "  &x; &#; &#x;\n�\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n", + "html": "

&nbsp &x; &#; &#x;\n&#87654321;\n&#abcdef0;\n&ThisIsNotDefined; &hi?;

\n", + "example": 28, + "start_line": 690, + "end_line": 700, + "section": "Entity and numeric character references" + }, + { + "markdown": "©\n", + "html": "

&copy

\n", + "example": 29, + "start_line": 707, + "end_line": 711, + "section": "Entity and numeric character references" + }, + { + "markdown": "&MadeUpEntity;\n", + "html": "

&MadeUpEntity;

\n", + "example": 30, + "start_line": 717, + "end_line": 721, + "section": "Entity and numeric character references" + }, + { + "markdown": "\n", + "html": "\n", + "example": 31, + "start_line": 728, + "end_line": 732, + "section": "Entity and numeric character references" + }, + { + "markdown": "[foo](/föö \"föö\")\n", + "html": "

foo

\n", + "example": 32, + "start_line": 735, + "end_line": 739, + "section": "Entity and numeric character references" + }, + { + "markdown": "[foo]\n\n[foo]: /föö \"föö\"\n", + "html": "

foo

\n", + "example": 33, + "start_line": 742, + "end_line": 748, + "section": "Entity and numeric character references" + }, + { + "markdown": "``` föö\nfoo\n```\n", + "html": "
foo\n
\n", + "example": 34, + "start_line": 751, + "end_line": 758, + "section": "Entity and numeric character references" + }, + { + "markdown": "`föö`\n", + "html": "

f&ouml;&ouml;

\n", + "example": 35, + "start_line": 764, + "end_line": 768, + "section": "Entity and numeric character references" + }, + { + "markdown": " föfö\n", + "html": "
f&ouml;f&ouml;\n
\n", + "example": 36, + "start_line": 771, + "end_line": 776, + "section": "Entity and numeric character references" + }, + { + "markdown": "*foo*\n*foo*\n", + "html": "

*foo*\nfoo

\n", + "example": 37, + "start_line": 783, + "end_line": 789, + "section": "Entity and numeric character references" + }, + { + "markdown": "* foo\n\n* foo\n", + "html": "

* foo

\n
    \n
  • foo
  • \n
\n", + "example": 38, + "start_line": 791, + "end_line": 800, + "section": "Entity and numeric character references" + }, + { + "markdown": "foo bar\n", + "html": "

foo\n\nbar

\n", + "example": 39, + "start_line": 802, + "end_line": 808, + "section": "Entity and numeric character references" + }, + { + "markdown": " foo\n", + "html": "

\tfoo

\n", + "example": 40, + "start_line": 810, + "end_line": 814, + "section": "Entity and numeric character references" + }, + { + "markdown": "[a](url "tit")\n", + "html": "

[a](url "tit")

\n", + "example": 41, + "start_line": 817, + "end_line": 821, + "section": "Entity and numeric character references" + }, + { + "markdown": "- `one\n- two`\n", + "html": "
    \n
  • `one
  • \n
  • two`
  • \n
\n", + "example": 42, + "start_line": 840, + "end_line": 848, + "section": "Precedence" + }, + { + "markdown": "***\n---\n___\n", + "html": "
\n
\n
\n", + "example": 43, + "start_line": 879, + "end_line": 887, + "section": "Thematic breaks" + }, + { + "markdown": "+++\n", + "html": "

+++

\n", + "example": 44, + "start_line": 892, + "end_line": 896, + "section": "Thematic breaks" + }, + { + "markdown": "===\n", + "html": "

===

\n", + "example": 45, + "start_line": 899, + "end_line": 903, + "section": "Thematic breaks" + }, + { + "markdown": "--\n**\n__\n", + "html": "

--\n**\n__

\n", + "example": 46, + "start_line": 908, + "end_line": 916, + "section": "Thematic breaks" + }, + { + "markdown": " ***\n ***\n ***\n", + "html": "
\n
\n
\n", + "example": 47, + "start_line": 921, + "end_line": 929, + "section": "Thematic breaks" + }, + { + "markdown": " ***\n", + "html": "
***\n
\n", + "example": 48, + "start_line": 934, + "end_line": 939, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n ***\n", + "html": "

Foo\n***

\n", + "example": 49, + "start_line": 942, + "end_line": 948, + "section": "Thematic breaks" + }, + { + "markdown": "_____________________________________\n", + "html": "
\n", + "example": 50, + "start_line": 953, + "end_line": 957, + "section": "Thematic breaks" + }, + { + "markdown": " - - -\n", + "html": "
\n", + "example": 51, + "start_line": 962, + "end_line": 966, + "section": "Thematic breaks" + }, + { + "markdown": " ** * ** * ** * **\n", + "html": "
\n", + "example": 52, + "start_line": 969, + "end_line": 973, + "section": "Thematic breaks" + }, + { + "markdown": "- - - -\n", + "html": "
\n", + "example": 53, + "start_line": 976, + "end_line": 980, + "section": "Thematic breaks" + }, + { + "markdown": "- - - - \n", + "html": "
\n", + "example": 54, + "start_line": 985, + "end_line": 989, + "section": "Thematic breaks" + }, + { + "markdown": "_ _ _ _ a\n\na------\n\n---a---\n", + "html": "

_ _ _ _ a

\n

a------

\n

---a---

\n", + "example": 55, + "start_line": 994, + "end_line": 1004, + "section": "Thematic breaks" + }, + { + "markdown": " *-*\n", + "html": "

-

\n", + "example": 56, + "start_line": 1010, + "end_line": 1014, + "section": "Thematic breaks" + }, + { + "markdown": "- foo\n***\n- bar\n", + "html": "
    \n
  • foo
  • \n
\n
\n
    \n
  • bar
  • \n
\n", + "example": 57, + "start_line": 1019, + "end_line": 1031, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n***\nbar\n", + "html": "

Foo

\n
\n

bar

\n", + "example": 58, + "start_line": 1036, + "end_line": 1044, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n---\nbar\n", + "html": "

Foo

\n

bar

\n", + "example": 59, + "start_line": 1053, + "end_line": 1060, + "section": "Thematic breaks" + }, + { + "markdown": "* Foo\n* * *\n* Bar\n", + "html": "
    \n
  • Foo
  • \n
\n
\n
    \n
  • Bar
  • \n
\n", + "example": 60, + "start_line": 1066, + "end_line": 1078, + "section": "Thematic breaks" + }, + { + "markdown": "- Foo\n- * * *\n", + "html": "
    \n
  • Foo
  • \n
  • \n
    \n
  • \n
\n", + "example": 61, + "start_line": 1083, + "end_line": 1093, + "section": "Thematic breaks" + }, + { + "markdown": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n", + "html": "

foo

\n

foo

\n

foo

\n

foo

\n
foo
\n
foo
\n", + "example": 62, + "start_line": 1112, + "end_line": 1126, + "section": "ATX headings" + }, + { + "markdown": "####### foo\n", + "html": "

####### foo

\n", + "example": 63, + "start_line": 1131, + "end_line": 1135, + "section": "ATX headings" + }, + { + "markdown": "#5 bolt\n\n#hashtag\n", + "html": "

#5 bolt

\n

#hashtag

\n", + "example": 64, + "start_line": 1146, + "end_line": 1153, + "section": "ATX headings" + }, + { + "markdown": "\\## foo\n", + "html": "

## foo

\n", + "example": 65, + "start_line": 1158, + "end_line": 1162, + "section": "ATX headings" + }, + { + "markdown": "# foo *bar* \\*baz\\*\n", + "html": "

foo bar *baz*

\n", + "example": 66, + "start_line": 1167, + "end_line": 1171, + "section": "ATX headings" + }, + { + "markdown": "# foo \n", + "html": "

foo

\n", + "example": 67, + "start_line": 1176, + "end_line": 1180, + "section": "ATX headings" + }, + { + "markdown": " ### foo\n ## foo\n # foo\n", + "html": "

foo

\n

foo

\n

foo

\n", + "example": 68, + "start_line": 1185, + "end_line": 1193, + "section": "ATX headings" + }, + { + "markdown": " # foo\n", + "html": "
# foo\n
\n", + "example": 69, + "start_line": 1198, + "end_line": 1203, + "section": "ATX headings" + }, + { + "markdown": "foo\n # bar\n", + "html": "

foo\n# bar

\n", + "example": 70, + "start_line": 1206, + "end_line": 1212, + "section": "ATX headings" + }, + { + "markdown": "## foo ##\n ### bar ###\n", + "html": "

foo

\n

bar

\n", + "example": 71, + "start_line": 1217, + "end_line": 1223, + "section": "ATX headings" + }, + { + "markdown": "# foo ##################################\n##### foo ##\n", + "html": "

foo

\n
foo
\n", + "example": 72, + "start_line": 1228, + "end_line": 1234, + "section": "ATX headings" + }, + { + "markdown": "### foo ### \n", + "html": "

foo

\n", + "example": 73, + "start_line": 1239, + "end_line": 1243, + "section": "ATX headings" + }, + { + "markdown": "### foo ### b\n", + "html": "

foo ### b

\n", + "example": 74, + "start_line": 1250, + "end_line": 1254, + "section": "ATX headings" + }, + { + "markdown": "# foo#\n", + "html": "

foo#

\n", + "example": 75, + "start_line": 1259, + "end_line": 1263, + "section": "ATX headings" + }, + { + "markdown": "### foo \\###\n## foo #\\##\n# foo \\#\n", + "html": "

foo ###

\n

foo ###

\n

foo #

\n", + "example": 76, + "start_line": 1269, + "end_line": 1277, + "section": "ATX headings" + }, + { + "markdown": "****\n## foo\n****\n", + "html": "
\n

foo

\n
\n", + "example": 77, + "start_line": 1283, + "end_line": 1291, + "section": "ATX headings" + }, + { + "markdown": "Foo bar\n# baz\nBar foo\n", + "html": "

Foo bar

\n

baz

\n

Bar foo

\n", + "example": 78, + "start_line": 1294, + "end_line": 1302, + "section": "ATX headings" + }, + { + "markdown": "## \n#\n### ###\n", + "html": "

\n

\n

\n", + "example": 79, + "start_line": 1307, + "end_line": 1315, + "section": "ATX headings" + }, + { + "markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n", + "html": "

Foo bar

\n

Foo bar

\n", + "example": 80, + "start_line": 1347, + "end_line": 1356, + "section": "Setext headings" + }, + { + "markdown": "Foo *bar\nbaz*\n====\n", + "html": "

Foo bar\nbaz

\n", + "example": 81, + "start_line": 1361, + "end_line": 1368, + "section": "Setext headings" + }, + { + "markdown": " Foo *bar\nbaz*\t\n====\n", + "html": "

Foo bar\nbaz

\n", + "example": 82, + "start_line": 1375, + "end_line": 1382, + "section": "Setext headings" + }, + { + "markdown": "Foo\n-------------------------\n\nFoo\n=\n", + "html": "

Foo

\n

Foo

\n", + "example": 83, + "start_line": 1387, + "end_line": 1396, + "section": "Setext headings" + }, + { + "markdown": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n", + "html": "

Foo

\n

Foo

\n

Foo

\n", + "example": 84, + "start_line": 1402, + "end_line": 1415, + "section": "Setext headings" + }, + { + "markdown": " Foo\n ---\n\n Foo\n---\n", + "html": "
Foo\n---\n\nFoo\n
\n
\n", + "example": 85, + "start_line": 1420, + "end_line": 1433, + "section": "Setext headings" + }, + { + "markdown": "Foo\n ---- \n", + "html": "

Foo

\n", + "example": 86, + "start_line": 1439, + "end_line": 1444, + "section": "Setext headings" + }, + { + "markdown": "Foo\n ---\n", + "html": "

Foo\n---

\n", + "example": 87, + "start_line": 1449, + "end_line": 1455, + "section": "Setext headings" + }, + { + "markdown": "Foo\n= =\n\nFoo\n--- -\n", + "html": "

Foo\n= =

\n

Foo

\n
\n", + "example": 88, + "start_line": 1460, + "end_line": 1471, + "section": "Setext headings" + }, + { + "markdown": "Foo \n-----\n", + "html": "

Foo

\n", + "example": 89, + "start_line": 1476, + "end_line": 1481, + "section": "Setext headings" + }, + { + "markdown": "Foo\\\n----\n", + "html": "

Foo\\

\n", + "example": 90, + "start_line": 1486, + "end_line": 1491, + "section": "Setext headings" + }, + { + "markdown": "`Foo\n----\n`\n\n\n", + "html": "

`Foo

\n

`

\n

<a title="a lot

\n

of dashes"/>

\n", + "example": 91, + "start_line": 1497, + "end_line": 1510, + "section": "Setext headings" + }, + { + "markdown": "> Foo\n---\n", + "html": "
\n

Foo

\n
\n
\n", + "example": 92, + "start_line": 1516, + "end_line": 1524, + "section": "Setext headings" + }, + { + "markdown": "> foo\nbar\n===\n", + "html": "
\n

foo\nbar\n===

\n
\n", + "example": 93, + "start_line": 1527, + "end_line": 1537, + "section": "Setext headings" + }, + { + "markdown": "- Foo\n---\n", + "html": "
    \n
  • Foo
  • \n
\n
\n", + "example": 94, + "start_line": 1540, + "end_line": 1548, + "section": "Setext headings" + }, + { + "markdown": "Foo\nBar\n---\n", + "html": "

Foo\nBar

\n", + "example": 95, + "start_line": 1555, + "end_line": 1562, + "section": "Setext headings" + }, + { + "markdown": "---\nFoo\n---\nBar\n---\nBaz\n", + "html": "
\n

Foo

\n

Bar

\n

Baz

\n", + "example": 96, + "start_line": 1568, + "end_line": 1580, + "section": "Setext headings" + }, + { + "markdown": "\n====\n", + "html": "

====

\n", + "example": 97, + "start_line": 1585, + "end_line": 1590, + "section": "Setext headings" + }, + { + "markdown": "---\n---\n", + "html": "
\n
\n", + "example": 98, + "start_line": 1597, + "end_line": 1603, + "section": "Setext headings" + }, + { + "markdown": "- foo\n-----\n", + "html": "
    \n
  • foo
  • \n
\n
\n", + "example": 99, + "start_line": 1606, + "end_line": 1614, + "section": "Setext headings" + }, + { + "markdown": " foo\n---\n", + "html": "
foo\n
\n
\n", + "example": 100, + "start_line": 1617, + "end_line": 1624, + "section": "Setext headings" + }, + { + "markdown": "> foo\n-----\n", + "html": "
\n

foo

\n
\n
\n", + "example": 101, + "start_line": 1627, + "end_line": 1635, + "section": "Setext headings" + }, + { + "markdown": "\\> foo\n------\n", + "html": "

> foo

\n", + "example": 102, + "start_line": 1641, + "end_line": 1646, + "section": "Setext headings" + }, + { + "markdown": "Foo\n\nbar\n---\nbaz\n", + "html": "

Foo

\n

bar

\n

baz

\n", + "example": 103, + "start_line": 1672, + "end_line": 1682, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n\n---\n\nbaz\n", + "html": "

Foo\nbar

\n
\n

baz

\n", + "example": 104, + "start_line": 1688, + "end_line": 1700, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n* * *\nbaz\n", + "html": "

Foo\nbar

\n
\n

baz

\n", + "example": 105, + "start_line": 1706, + "end_line": 1716, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n\\---\nbaz\n", + "html": "

Foo\nbar\n---\nbaz

\n", + "example": 106, + "start_line": 1721, + "end_line": 1731, + "section": "Setext headings" + }, + { + "markdown": " a simple\n indented code block\n", + "html": "
a simple\n  indented code block\n
\n", + "example": 107, + "start_line": 1749, + "end_line": 1756, + "section": "Indented code blocks" + }, + { + "markdown": " - foo\n\n bar\n", + "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", + "example": 108, + "start_line": 1763, + "end_line": 1774, + "section": "Indented code blocks" + }, + { + "markdown": "1. foo\n\n - bar\n", + "html": "
    \n
  1. \n

    foo

    \n
      \n
    • bar
    • \n
    \n
  2. \n
\n", + "example": 109, + "start_line": 1777, + "end_line": 1790, + "section": "Indented code blocks" + }, + { + "markdown": "
\n *hi*\n\n - one\n", + "html": "
<a/>\n*hi*\n\n- one\n
\n", + "example": 110, + "start_line": 1797, + "end_line": 1808, + "section": "Indented code blocks" + }, + { + "markdown": " chunk1\n\n chunk2\n \n \n \n chunk3\n", + "html": "
chunk1\n\nchunk2\n\n\n\nchunk3\n
\n", + "example": 111, + "start_line": 1813, + "end_line": 1830, + "section": "Indented code blocks" + }, + { + "markdown": " chunk1\n \n chunk2\n", + "html": "
chunk1\n  \n  chunk2\n
\n", + "example": 112, + "start_line": 1836, + "end_line": 1845, + "section": "Indented code blocks" + }, + { + "markdown": "Foo\n bar\n\n", + "html": "

Foo\nbar

\n", + "example": 113, + "start_line": 1851, + "end_line": 1858, + "section": "Indented code blocks" + }, + { + "markdown": " foo\nbar\n", + "html": "
foo\n
\n

bar

\n", + "example": 114, + "start_line": 1865, + "end_line": 1872, + "section": "Indented code blocks" + }, + { + "markdown": "# Heading\n foo\nHeading\n------\n foo\n----\n", + "html": "

Heading

\n
foo\n
\n

Heading

\n
foo\n
\n
\n", + "example": 115, + "start_line": 1878, + "end_line": 1893, + "section": "Indented code blocks" + }, + { + "markdown": " foo\n bar\n", + "html": "
    foo\nbar\n
\n", + "example": 116, + "start_line": 1898, + "end_line": 1905, + "section": "Indented code blocks" + }, + { + "markdown": "\n \n foo\n \n\n", + "html": "
foo\n
\n", + "example": 117, + "start_line": 1911, + "end_line": 1920, + "section": "Indented code blocks" + }, + { + "markdown": " foo \n", + "html": "
foo  \n
\n", + "example": 118, + "start_line": 1925, + "end_line": 1930, + "section": "Indented code blocks" + }, + { + "markdown": "```\n<\n >\n```\n", + "html": "
<\n >\n
\n", + "example": 119, + "start_line": 1980, + "end_line": 1989, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~\n<\n >\n~~~\n", + "html": "
<\n >\n
\n", + "example": 120, + "start_line": 1994, + "end_line": 2003, + "section": "Fenced code blocks" + }, + { + "markdown": "``\nfoo\n``\n", + "html": "

foo

\n", + "example": 121, + "start_line": 2007, + "end_line": 2013, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n~~~\n```\n", + "html": "
aaa\n~~~\n
\n", + "example": 122, + "start_line": 2018, + "end_line": 2027, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~\naaa\n```\n~~~\n", + "html": "
aaa\n```\n
\n", + "example": 123, + "start_line": 2030, + "end_line": 2039, + "section": "Fenced code blocks" + }, + { + "markdown": "````\naaa\n```\n``````\n", + "html": "
aaa\n```\n
\n", + "example": 124, + "start_line": 2044, + "end_line": 2053, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~\naaa\n~~~\n~~~~\n", + "html": "
aaa\n~~~\n
\n", + "example": 125, + "start_line": 2056, + "end_line": 2065, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n", + "html": "
\n", + "example": 126, + "start_line": 2071, + "end_line": 2075, + "section": "Fenced code blocks" + }, + { + "markdown": "`````\n\n```\naaa\n", + "html": "
\n```\naaa\n
\n", + "example": 127, + "start_line": 2078, + "end_line": 2088, + "section": "Fenced code blocks" + }, + { + "markdown": "> ```\n> aaa\n\nbbb\n", + "html": "
\n
aaa\n
\n
\n

bbb

\n", + "example": 128, + "start_line": 2091, + "end_line": 2102, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n\n \n```\n", + "html": "
\n  \n
\n", + "example": 129, + "start_line": 2107, + "end_line": 2116, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n```\n", + "html": "
\n", + "example": 130, + "start_line": 2121, + "end_line": 2126, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\naaa\n```\n", + "html": "
aaa\naaa\n
\n", + "example": 131, + "start_line": 2133, + "end_line": 2142, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\naaa\n aaa\naaa\n ```\n", + "html": "
aaa\naaa\naaa\n
\n", + "example": 132, + "start_line": 2145, + "end_line": 2156, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\n aaa\n aaa\n ```\n", + "html": "
aaa\n aaa\naaa\n
\n", + "example": 133, + "start_line": 2159, + "end_line": 2170, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\n ```\n", + "html": "
```\naaa\n```\n
\n", + "example": 134, + "start_line": 2175, + "end_line": 2184, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n ```\n", + "html": "
aaa\n
\n", + "example": 135, + "start_line": 2190, + "end_line": 2197, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\naaa\n ```\n", + "html": "
aaa\n
\n", + "example": 136, + "start_line": 2200, + "end_line": 2207, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n ```\n", + "html": "
aaa\n    ```\n
\n", + "example": 137, + "start_line": 2212, + "end_line": 2220, + "section": "Fenced code blocks" + }, + { + "markdown": "``` ```\naaa\n", + "html": "

\naaa

\n", + "example": 138, + "start_line": 2226, + "end_line": 2232, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~~~\naaa\n~~~ ~~\n", + "html": "
aaa\n~~~ ~~\n
\n", + "example": 139, + "start_line": 2235, + "end_line": 2243, + "section": "Fenced code blocks" + }, + { + "markdown": "foo\n```\nbar\n```\nbaz\n", + "html": "

foo

\n
bar\n
\n

baz

\n", + "example": 140, + "start_line": 2249, + "end_line": 2260, + "section": "Fenced code blocks" + }, + { + "markdown": "foo\n---\n~~~\nbar\n~~~\n# baz\n", + "html": "

foo

\n
bar\n
\n

baz

\n", + "example": 141, + "start_line": 2266, + "end_line": 2278, + "section": "Fenced code blocks" + }, + { + "markdown": "```ruby\ndef foo(x)\n return 3\nend\n```\n", + "html": "
def foo(x)\n  return 3\nend\n
\n", + "example": 142, + "start_line": 2288, + "end_line": 2299, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n", + "html": "
def foo(x)\n  return 3\nend\n
\n", + "example": 143, + "start_line": 2302, + "end_line": 2313, + "section": "Fenced code blocks" + }, + { + "markdown": "````;\n````\n", + "html": "
\n", + "example": 144, + "start_line": 2316, + "end_line": 2321, + "section": "Fenced code blocks" + }, + { + "markdown": "``` aa ```\nfoo\n", + "html": "

aa\nfoo

\n", + "example": 145, + "start_line": 2326, + "end_line": 2332, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~ aa ``` ~~~\nfoo\n~~~\n", + "html": "
foo\n
\n", + "example": 146, + "start_line": 2337, + "end_line": 2344, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n``` aaa\n```\n", + "html": "
``` aaa\n
\n", + "example": 147, + "start_line": 2349, + "end_line": 2356, + "section": "Fenced code blocks" + }, + { + "markdown": "
\n
\n**Hello**,\n\n_world_.\n
\n
\n", + "html": "
\n
\n**Hello**,\n

world.\n

\n
\n", + "example": 148, + "start_line": 2428, + "end_line": 2443, + "section": "HTML blocks" + }, + { + "markdown": "\n \n \n \n
\n hi\n
\n\nokay.\n", + "html": "\n \n \n \n
\n hi\n
\n

okay.

\n", + "example": 149, + "start_line": 2457, + "end_line": 2476, + "section": "HTML blocks" + }, + { + "markdown": "
\n*foo*\n", + "example": 151, + "start_line": 2492, + "end_line": 2498, + "section": "HTML blocks" + }, + { + "markdown": "
\n\n*Markdown*\n\n
\n", + "html": "
\n

Markdown

\n
\n", + "example": 152, + "start_line": 2503, + "end_line": 2513, + "section": "HTML blocks" + }, + { + "markdown": "
\n
\n", + "html": "
\n
\n", + "example": 153, + "start_line": 2519, + "end_line": 2527, + "section": "HTML blocks" + }, + { + "markdown": "
\n
\n", + "html": "
\n
\n", + "example": 154, + "start_line": 2530, + "end_line": 2538, + "section": "HTML blocks" + }, + { + "markdown": "
\n*foo*\n\n*bar*\n", + "html": "
\n*foo*\n

bar

\n", + "example": 155, + "start_line": 2542, + "end_line": 2551, + "section": "HTML blocks" + }, + { + "markdown": "
\n", + "html": "\n", + "example": 159, + "start_line": 2591, + "end_line": 2595, + "section": "HTML blocks" + }, + { + "markdown": "
\nfoo\n
\n", + "html": "
\nfoo\n
\n", + "example": 160, + "start_line": 2598, + "end_line": 2606, + "section": "HTML blocks" + }, + { + "markdown": "
\n``` c\nint x = 33;\n```\n", + "html": "
\n``` c\nint x = 33;\n```\n", + "example": 161, + "start_line": 2615, + "end_line": 2625, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 162, + "start_line": 2632, + "end_line": 2640, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 163, + "start_line": 2645, + "end_line": 2653, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 164, + "start_line": 2656, + "end_line": 2664, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n", + "html": "\n*bar*\n", + "example": 165, + "start_line": 2667, + "end_line": 2673, + "section": "HTML blocks" + }, + { + "markdown": "\n*foo*\n\n", + "html": "\n*foo*\n\n", + "example": 166, + "start_line": 2682, + "end_line": 2690, + "section": "HTML blocks" + }, + { + "markdown": "\n\n*foo*\n\n\n", + "html": "\n

foo

\n
\n", + "example": 167, + "start_line": 2697, + "end_line": 2707, + "section": "HTML blocks" + }, + { + "markdown": "*foo*\n", + "html": "

foo

\n", + "example": 168, + "start_line": 2715, + "end_line": 2719, + "section": "HTML blocks" + }, + { + "markdown": "
\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
\nokay\n", + "html": "
\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
\n

okay

\n", + "example": 169, + "start_line": 2731, + "end_line": 2747, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

okay

\n", + "example": 170, + "start_line": 2752, + "end_line": 2766, + "section": "HTML blocks" + }, + { + "markdown": "\n", + "html": "\n", + "example": 171, + "start_line": 2771, + "end_line": 2787, + "section": "HTML blocks" + }, + { + "markdown": "\nh1 {color:red;}\n\np {color:blue;}\n\nokay\n", + "html": "\nh1 {color:red;}\n\np {color:blue;}\n\n

okay

\n", + "example": 172, + "start_line": 2791, + "end_line": 2807, + "section": "HTML blocks" + }, + { + "markdown": "\n\nfoo\n", + "html": "\n\nfoo\n", + "example": 173, + "start_line": 2814, + "end_line": 2824, + "section": "HTML blocks" + }, + { + "markdown": ">
\n> foo\n\nbar\n", + "html": "
\n
\nfoo\n
\n

bar

\n", + "example": 174, + "start_line": 2827, + "end_line": 2838, + "section": "HTML blocks" + }, + { + "markdown": "-
\n- foo\n", + "html": "
    \n
  • \n
    \n
  • \n
  • foo
  • \n
\n", + "example": 175, + "start_line": 2841, + "end_line": 2851, + "section": "HTML blocks" + }, + { + "markdown": "\n*foo*\n", + "html": "\n

foo

\n", + "example": 176, + "start_line": 2856, + "end_line": 2862, + "section": "HTML blocks" + }, + { + "markdown": "*bar*\n*baz*\n", + "html": "*bar*\n

baz

\n", + "example": 177, + "start_line": 2865, + "end_line": 2871, + "section": "HTML blocks" + }, + { + "markdown": "1. *bar*\n", + "html": "1. *bar*\n", + "example": 178, + "start_line": 2877, + "end_line": 2885, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

okay

\n", + "example": 179, + "start_line": 2890, + "end_line": 2902, + "section": "HTML blocks" + }, + { + "markdown": "';\n\n?>\nokay\n", + "html": "';\n\n?>\n

okay

\n", + "example": 180, + "start_line": 2908, + "end_line": 2922, + "section": "HTML blocks" + }, + { + "markdown": "\n", + "html": "\n", + "example": 181, + "start_line": 2927, + "end_line": 2931, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

okay

\n", + "example": 182, + "start_line": 2936, + "end_line": 2964, + "section": "HTML blocks" + }, + { + "markdown": " \n\n \n", + "html": " \n
<!-- foo -->\n
\n", + "example": 183, + "start_line": 2970, + "end_line": 2978, + "section": "HTML blocks" + }, + { + "markdown": "
\n\n
\n", + "html": "
\n
<div>\n
\n", + "example": 184, + "start_line": 2981, + "end_line": 2989, + "section": "HTML blocks" + }, + { + "markdown": "Foo\n
\nbar\n
\n", + "html": "

Foo

\n
\nbar\n
\n", + "example": 185, + "start_line": 2995, + "end_line": 3005, + "section": "HTML blocks" + }, + { + "markdown": "
\nbar\n
\n*foo*\n", + "html": "
\nbar\n
\n*foo*\n", + "example": 186, + "start_line": 3012, + "end_line": 3022, + "section": "HTML blocks" + }, + { + "markdown": "Foo\n\nbaz\n", + "html": "

Foo\n\nbaz

\n", + "example": 187, + "start_line": 3027, + "end_line": 3035, + "section": "HTML blocks" + }, + { + "markdown": "
\n\n*Emphasized* text.\n\n
\n", + "html": "
\n

Emphasized text.

\n
\n", + "example": 188, + "start_line": 3068, + "end_line": 3078, + "section": "HTML blocks" + }, + { + "markdown": "
\n*Emphasized* text.\n
\n", + "html": "
\n*Emphasized* text.\n
\n", + "example": 189, + "start_line": 3081, + "end_line": 3089, + "section": "HTML blocks" + }, + { + "markdown": "\n\n\n\n\n\n\n\n
\nHi\n
\n", + "html": "\n\n\n\n
\nHi\n
\n", + "example": 190, + "start_line": 3103, + "end_line": 3123, + "section": "HTML blocks" + }, + { + "markdown": "\n\n \n\n \n\n \n\n
\n Hi\n
\n", + "html": "\n \n
<td>\n  Hi\n</td>\n
\n \n
\n", + "example": 191, + "start_line": 3130, + "end_line": 3151, + "section": "HTML blocks" + }, + { + "markdown": "[foo]: /url \"title\"\n\n[foo]\n", + "html": "

foo

\n", + "example": 192, + "start_line": 3179, + "end_line": 3185, + "section": "Link reference definitions" + }, + { + "markdown": " [foo]: \n /url \n 'the title' \n\n[foo]\n", + "html": "

foo

\n", + "example": 193, + "start_line": 3188, + "end_line": 3196, + "section": "Link reference definitions" + }, + { + "markdown": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n", + "html": "

Foo*bar]

\n", + "example": 194, + "start_line": 3199, + "end_line": 3205, + "section": "Link reference definitions" + }, + { + "markdown": "[Foo bar]:\n\n'title'\n\n[Foo bar]\n", + "html": "

Foo bar

\n", + "example": 195, + "start_line": 3208, + "end_line": 3216, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n", + "html": "

foo

\n", + "example": 196, + "start_line": 3221, + "end_line": 3235, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n", + "html": "

[foo]: /url 'title

\n

with blank line'

\n

[foo]

\n", + "example": 197, + "start_line": 3240, + "end_line": 3250, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]:\n/url\n\n[foo]\n", + "html": "

foo

\n", + "example": 198, + "start_line": 3255, + "end_line": 3262, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]:\n\n[foo]\n", + "html": "

[foo]:

\n

[foo]

\n", + "example": 199, + "start_line": 3267, + "end_line": 3274, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: <>\n\n[foo]\n", + "html": "

foo

\n", + "example": 200, + "start_line": 3279, + "end_line": 3285, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: (baz)\n\n[foo]\n", + "html": "

[foo]: (baz)

\n

[foo]

\n", + "example": 201, + "start_line": 3290, + "end_line": 3297, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n", + "html": "

foo

\n", + "example": 202, + "start_line": 3303, + "end_line": 3309, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n[foo]: url\n", + "html": "

foo

\n", + "example": 203, + "start_line": 3314, + "end_line": 3320, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n[foo]: first\n[foo]: second\n", + "html": "

foo

\n", + "example": 204, + "start_line": 3326, + "end_line": 3333, + "section": "Link reference definitions" + }, + { + "markdown": "[FOO]: /url\n\n[Foo]\n", + "html": "

Foo

\n", + "example": 205, + "start_line": 3339, + "end_line": 3345, + "section": "Link reference definitions" + }, + { + "markdown": "[ΑΓΩ]: /φου\n\n[αγω]\n", + "html": "

αγω

\n", + "example": 206, + "start_line": 3348, + "end_line": 3354, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n", + "html": "", + "example": 207, + "start_line": 3363, + "end_line": 3366, + "section": "Link reference definitions" + }, + { + "markdown": "[\nfoo\n]: /url\nbar\n", + "html": "

bar

\n", + "example": 208, + "start_line": 3371, + "end_line": 3378, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url \"title\" ok\n", + "html": "

[foo]: /url "title" ok

\n", + "example": 209, + "start_line": 3384, + "end_line": 3388, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n\"title\" ok\n", + "html": "

"title" ok

\n", + "example": 210, + "start_line": 3393, + "end_line": 3398, + "section": "Link reference definitions" + }, + { + "markdown": " [foo]: /url \"title\"\n\n[foo]\n", + "html": "
[foo]: /url "title"\n
\n

[foo]

\n", + "example": 211, + "start_line": 3404, + "end_line": 3412, + "section": "Link reference definitions" + }, + { + "markdown": "```\n[foo]: /url\n```\n\n[foo]\n", + "html": "
[foo]: /url\n
\n

[foo]

\n", + "example": 212, + "start_line": 3418, + "end_line": 3428, + "section": "Link reference definitions" + }, + { + "markdown": "Foo\n[bar]: /baz\n\n[bar]\n", + "html": "

Foo\n[bar]: /baz

\n

[bar]

\n", + "example": 213, + "start_line": 3433, + "end_line": 3442, + "section": "Link reference definitions" + }, + { + "markdown": "# [Foo]\n[foo]: /url\n> bar\n", + "html": "

Foo

\n
\n

bar

\n
\n", + "example": 214, + "start_line": 3448, + "end_line": 3457, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\nbar\n===\n[foo]\n", + "html": "

bar

\n

foo

\n", + "example": 215, + "start_line": 3459, + "end_line": 3467, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n===\n[foo]\n", + "html": "

===\nfoo

\n", + "example": 216, + "start_line": 3469, + "end_line": 3476, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n", + "html": "

foo,\nbar,\nbaz

\n", + "example": 217, + "start_line": 3482, + "end_line": 3495, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n> [foo]: /url\n", + "html": "

foo

\n
\n
\n", + "example": 218, + "start_line": 3503, + "end_line": 3511, + "section": "Link reference definitions" + }, + { + "markdown": "aaa\n\nbbb\n", + "html": "

aaa

\n

bbb

\n", + "example": 219, + "start_line": 3525, + "end_line": 3532, + "section": "Paragraphs" + }, + { + "markdown": "aaa\nbbb\n\nccc\nddd\n", + "html": "

aaa\nbbb

\n

ccc\nddd

\n", + "example": 220, + "start_line": 3537, + "end_line": 3548, + "section": "Paragraphs" + }, + { + "markdown": "aaa\n\n\nbbb\n", + "html": "

aaa

\n

bbb

\n", + "example": 221, + "start_line": 3553, + "end_line": 3561, + "section": "Paragraphs" + }, + { + "markdown": " aaa\n bbb\n", + "html": "

aaa\nbbb

\n", + "example": 222, + "start_line": 3566, + "end_line": 3572, + "section": "Paragraphs" + }, + { + "markdown": "aaa\n bbb\n ccc\n", + "html": "

aaa\nbbb\nccc

\n", + "example": 223, + "start_line": 3578, + "end_line": 3586, + "section": "Paragraphs" + }, + { + "markdown": " aaa\nbbb\n", + "html": "

aaa\nbbb

\n", + "example": 224, + "start_line": 3592, + "end_line": 3598, + "section": "Paragraphs" + }, + { + "markdown": " aaa\nbbb\n", + "html": "
aaa\n
\n

bbb

\n", + "example": 225, + "start_line": 3601, + "end_line": 3608, + "section": "Paragraphs" + }, + { + "markdown": "aaa \nbbb \n", + "html": "

aaa
\nbbb

\n", + "example": 226, + "start_line": 3615, + "end_line": 3621, + "section": "Paragraphs" + }, + { + "markdown": " \n\naaa\n \n\n# aaa\n\n \n", + "html": "

aaa

\n

aaa

\n", + "example": 227, + "start_line": 3632, + "end_line": 3644, + "section": "Blank lines" + }, + { + "markdown": "> # Foo\n> bar\n> baz\n", + "html": "
\n

Foo

\n

bar\nbaz

\n
\n", + "example": 228, + "start_line": 3700, + "end_line": 3710, + "section": "Block quotes" + }, + { + "markdown": "># Foo\n>bar\n> baz\n", + "html": "
\n

Foo

\n

bar\nbaz

\n
\n", + "example": 229, + "start_line": 3715, + "end_line": 3725, + "section": "Block quotes" + }, + { + "markdown": " > # Foo\n > bar\n > baz\n", + "html": "
\n

Foo

\n

bar\nbaz

\n
\n", + "example": 230, + "start_line": 3730, + "end_line": 3740, + "section": "Block quotes" + }, + { + "markdown": " > # Foo\n > bar\n > baz\n", + "html": "
> # Foo\n> bar\n> baz\n
\n", + "example": 231, + "start_line": 3745, + "end_line": 3754, + "section": "Block quotes" + }, + { + "markdown": "> # Foo\n> bar\nbaz\n", + "html": "
\n

Foo

\n

bar\nbaz

\n
\n", + "example": 232, + "start_line": 3760, + "end_line": 3770, + "section": "Block quotes" + }, + { + "markdown": "> bar\nbaz\n> foo\n", + "html": "
\n

bar\nbaz\nfoo

\n
\n", + "example": 233, + "start_line": 3776, + "end_line": 3786, + "section": "Block quotes" + }, + { + "markdown": "> foo\n---\n", + "html": "
\n

foo

\n
\n
\n", + "example": 234, + "start_line": 3800, + "end_line": 3808, + "section": "Block quotes" + }, + { + "markdown": "> - foo\n- bar\n", + "html": "
\n
    \n
  • foo
  • \n
\n
\n
    \n
  • bar
  • \n
\n", + "example": 235, + "start_line": 3820, + "end_line": 3832, + "section": "Block quotes" + }, + { + "markdown": "> foo\n bar\n", + "html": "
\n
foo\n
\n
\n
bar\n
\n", + "example": 236, + "start_line": 3838, + "end_line": 3848, + "section": "Block quotes" + }, + { + "markdown": "> ```\nfoo\n```\n", + "html": "
\n
\n
\n

foo

\n
\n", + "example": 237, + "start_line": 3851, + "end_line": 3861, + "section": "Block quotes" + }, + { + "markdown": "> foo\n - bar\n", + "html": "
\n

foo\n- bar

\n
\n", + "example": 238, + "start_line": 3867, + "end_line": 3875, + "section": "Block quotes" + }, + { + "markdown": ">\n", + "html": "
\n
\n", + "example": 239, + "start_line": 3891, + "end_line": 3896, + "section": "Block quotes" + }, + { + "markdown": ">\n> \n> \n", + "html": "
\n
\n", + "example": 240, + "start_line": 3899, + "end_line": 3906, + "section": "Block quotes" + }, + { + "markdown": ">\n> foo\n> \n", + "html": "
\n

foo

\n
\n", + "example": 241, + "start_line": 3911, + "end_line": 3919, + "section": "Block quotes" + }, + { + "markdown": "> foo\n\n> bar\n", + "html": "
\n

foo

\n
\n
\n

bar

\n
\n", + "example": 242, + "start_line": 3924, + "end_line": 3935, + "section": "Block quotes" + }, + { + "markdown": "> foo\n> bar\n", + "html": "
\n

foo\nbar

\n
\n", + "example": 243, + "start_line": 3946, + "end_line": 3954, + "section": "Block quotes" + }, + { + "markdown": "> foo\n>\n> bar\n", + "html": "
\n

foo

\n

bar

\n
\n", + "example": 244, + "start_line": 3959, + "end_line": 3968, + "section": "Block quotes" + }, + { + "markdown": "foo\n> bar\n", + "html": "

foo

\n
\n

bar

\n
\n", + "example": 245, + "start_line": 3973, + "end_line": 3981, + "section": "Block quotes" + }, + { + "markdown": "> aaa\n***\n> bbb\n", + "html": "
\n

aaa

\n
\n
\n
\n

bbb

\n
\n", + "example": 246, + "start_line": 3987, + "end_line": 3999, + "section": "Block quotes" + }, + { + "markdown": "> bar\nbaz\n", + "html": "
\n

bar\nbaz

\n
\n", + "example": 247, + "start_line": 4005, + "end_line": 4013, + "section": "Block quotes" + }, + { + "markdown": "> bar\n\nbaz\n", + "html": "
\n

bar

\n
\n

baz

\n", + "example": 248, + "start_line": 4016, + "end_line": 4025, + "section": "Block quotes" + }, + { + "markdown": "> bar\n>\nbaz\n", + "html": "
\n

bar

\n
\n

baz

\n", + "example": 249, + "start_line": 4028, + "end_line": 4037, + "section": "Block quotes" + }, + { + "markdown": "> > > foo\nbar\n", + "html": "
\n
\n
\n

foo\nbar

\n
\n
\n
\n", + "example": 250, + "start_line": 4044, + "end_line": 4056, + "section": "Block quotes" + }, + { + "markdown": ">>> foo\n> bar\n>>baz\n", + "html": "
\n
\n
\n

foo\nbar\nbaz

\n
\n
\n
\n", + "example": 251, + "start_line": 4059, + "end_line": 4073, + "section": "Block quotes" + }, + { + "markdown": "> code\n\n> not code\n", + "html": "
\n
code\n
\n
\n
\n

not code

\n
\n", + "example": 252, + "start_line": 4081, + "end_line": 4093, + "section": "Block quotes" + }, + { + "markdown": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n", + "html": "

A paragraph\nwith two lines.

\n
indented code\n
\n
\n

A block quote.

\n
\n", + "example": 253, + "start_line": 4135, + "end_line": 4150, + "section": "List items" + }, + { + "markdown": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", + "example": 254, + "start_line": 4157, + "end_line": 4176, + "section": "List items" + }, + { + "markdown": "- one\n\n two\n", + "html": "
    \n
  • one
  • \n
\n

two

\n", + "example": 255, + "start_line": 4190, + "end_line": 4199, + "section": "List items" + }, + { + "markdown": "- one\n\n two\n", + "html": "
    \n
  • \n

    one

    \n

    two

    \n
  • \n
\n", + "example": 256, + "start_line": 4202, + "end_line": 4213, + "section": "List items" + }, + { + "markdown": " - one\n\n two\n", + "html": "
    \n
  • one
  • \n
\n
 two\n
\n", + "example": 257, + "start_line": 4216, + "end_line": 4226, + "section": "List items" + }, + { + "markdown": " - one\n\n two\n", + "html": "
    \n
  • \n

    one

    \n

    two

    \n
  • \n
\n", + "example": 258, + "start_line": 4229, + "end_line": 4240, + "section": "List items" + }, + { + "markdown": " > > 1. one\n>>\n>> two\n", + "html": "
\n
\n
    \n
  1. \n

    one

    \n

    two

    \n
  2. \n
\n
\n
\n", + "example": 259, + "start_line": 4251, + "end_line": 4266, + "section": "List items" + }, + { + "markdown": ">>- one\n>>\n > > two\n", + "html": "
\n
\n
    \n
  • one
  • \n
\n

two

\n
\n
\n", + "example": 260, + "start_line": 4278, + "end_line": 4291, + "section": "List items" + }, + { + "markdown": "-one\n\n2.two\n", + "html": "

-one

\n

2.two

\n", + "example": 261, + "start_line": 4297, + "end_line": 4304, + "section": "List items" + }, + { + "markdown": "- foo\n\n\n bar\n", + "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", + "example": 262, + "start_line": 4310, + "end_line": 4322, + "section": "List items" + }, + { + "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n", + "html": "
    \n
  1. \n

    foo

    \n
    bar\n
    \n

    baz

    \n
    \n

    bam

    \n
    \n
  2. \n
\n", + "example": 263, + "start_line": 4327, + "end_line": 4349, + "section": "List items" + }, + { + "markdown": "- Foo\n\n bar\n\n\n baz\n", + "html": "
    \n
  • \n

    Foo

    \n
    bar\n\n\nbaz\n
    \n
  • \n
\n", + "example": 264, + "start_line": 4355, + "end_line": 4373, + "section": "List items" + }, + { + "markdown": "123456789. ok\n", + "html": "
    \n
  1. ok
  2. \n
\n", + "example": 265, + "start_line": 4377, + "end_line": 4383, + "section": "List items" + }, + { + "markdown": "1234567890. not ok\n", + "html": "

1234567890. not ok

\n", + "example": 266, + "start_line": 4386, + "end_line": 4390, + "section": "List items" + }, + { + "markdown": "0. ok\n", + "html": "
    \n
  1. ok
  2. \n
\n", + "example": 267, + "start_line": 4395, + "end_line": 4401, + "section": "List items" + }, + { + "markdown": "003. ok\n", + "html": "
    \n
  1. ok
  2. \n
\n", + "example": 268, + "start_line": 4404, + "end_line": 4410, + "section": "List items" + }, + { + "markdown": "-1. not ok\n", + "html": "

-1. not ok

\n", + "example": 269, + "start_line": 4415, + "end_line": 4419, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
    \n
  • \n

    foo

    \n
    bar\n
    \n
  • \n
\n", + "example": 270, + "start_line": 4438, + "end_line": 4450, + "section": "List items" + }, + { + "markdown": " 10. foo\n\n bar\n", + "html": "
    \n
  1. \n

    foo

    \n
    bar\n
    \n
  2. \n
\n", + "example": 271, + "start_line": 4455, + "end_line": 4467, + "section": "List items" + }, + { + "markdown": " indented code\n\nparagraph\n\n more code\n", + "html": "
indented code\n
\n

paragraph

\n
more code\n
\n", + "example": 272, + "start_line": 4474, + "end_line": 4486, + "section": "List items" + }, + { + "markdown": "1. indented code\n\n paragraph\n\n more code\n", + "html": "
    \n
  1. \n
    indented code\n
    \n

    paragraph

    \n
    more code\n
    \n
  2. \n
\n", + "example": 273, + "start_line": 4489, + "end_line": 4505, + "section": "List items" + }, + { + "markdown": "1. indented code\n\n paragraph\n\n more code\n", + "html": "
    \n
  1. \n
     indented code\n
    \n

    paragraph

    \n
    more code\n
    \n
  2. \n
\n", + "example": 274, + "start_line": 4511, + "end_line": 4527, + "section": "List items" + }, + { + "markdown": " foo\n\nbar\n", + "html": "

foo

\n

bar

\n", + "example": 275, + "start_line": 4538, + "end_line": 4545, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
    \n
  • foo
  • \n
\n

bar

\n", + "example": 276, + "start_line": 4548, + "end_line": 4557, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
    \n
  • \n

    foo

    \n

    bar

    \n
  • \n
\n", + "example": 277, + "start_line": 4565, + "end_line": 4576, + "section": "List items" + }, + { + "markdown": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n", + "html": "
    \n
  • foo
  • \n
  • \n
    bar\n
    \n
  • \n
  • \n
    baz\n
    \n
  • \n
\n", + "example": 278, + "start_line": 4592, + "end_line": 4613, + "section": "List items" + }, + { + "markdown": "- \n foo\n", + "html": "
    \n
  • foo
  • \n
\n", + "example": 279, + "start_line": 4618, + "end_line": 4625, + "section": "List items" + }, + { + "markdown": "-\n\n foo\n", + "html": "
    \n
  • \n
\n

foo

\n", + "example": 280, + "start_line": 4632, + "end_line": 4641, + "section": "List items" + }, + { + "markdown": "- foo\n-\n- bar\n", + "html": "
    \n
  • foo
  • \n
  • \n
  • bar
  • \n
\n", + "example": 281, + "start_line": 4646, + "end_line": 4656, + "section": "List items" + }, + { + "markdown": "- foo\n- \n- bar\n", + "html": "
    \n
  • foo
  • \n
  • \n
  • bar
  • \n
\n", + "example": 282, + "start_line": 4661, + "end_line": 4671, + "section": "List items" + }, + { + "markdown": "1. foo\n2.\n3. bar\n", + "html": "
    \n
  1. foo
  2. \n
  3. \n
  4. bar
  5. \n
\n", + "example": 283, + "start_line": 4676, + "end_line": 4686, + "section": "List items" + }, + { + "markdown": "*\n", + "html": "
    \n
  • \n
\n", + "example": 284, + "start_line": 4691, + "end_line": 4697, + "section": "List items" + }, + { + "markdown": "foo\n*\n\nfoo\n1.\n", + "html": "

foo\n*

\n

foo\n1.

\n", + "example": 285, + "start_line": 4701, + "end_line": 4712, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", + "example": 286, + "start_line": 4723, + "end_line": 4742, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", + "example": 287, + "start_line": 4747, + "end_line": 4766, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", + "example": 288, + "start_line": 4771, + "end_line": 4790, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
1.  A paragraph\n    with two lines.\n\n        indented code\n\n    > A block quote.\n
\n", + "example": 289, + "start_line": 4795, + "end_line": 4810, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
    \n
  1. \n

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n
  2. \n
\n", + "example": 290, + "start_line": 4825, + "end_line": 4844, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n", + "html": "
    \n
  1. A paragraph\nwith two lines.
  2. \n
\n", + "example": 291, + "start_line": 4849, + "end_line": 4857, + "section": "List items" + }, + { + "markdown": "> 1. > Blockquote\ncontinued here.\n", + "html": "
\n
    \n
  1. \n
    \n

    Blockquote\ncontinued here.

    \n
    \n
  2. \n
\n
\n", + "example": 292, + "start_line": 4862, + "end_line": 4876, + "section": "List items" + }, + { + "markdown": "> 1. > Blockquote\n> continued here.\n", + "html": "
\n
    \n
  1. \n
    \n

    Blockquote\ncontinued here.

    \n
    \n
  2. \n
\n
\n", + "example": 293, + "start_line": 4879, + "end_line": 4893, + "section": "List items" + }, + { + "markdown": "- foo\n - bar\n - baz\n - boo\n", + "html": "
    \n
  • foo\n
      \n
    • bar\n
        \n
      • baz\n
          \n
        • boo
        • \n
        \n
      • \n
      \n
    • \n
    \n
  • \n
\n", + "example": 294, + "start_line": 4907, + "end_line": 4928, + "section": "List items" + }, + { + "markdown": "- foo\n - bar\n - baz\n - boo\n", + "html": "
    \n
  • foo
  • \n
  • bar
  • \n
  • baz
  • \n
  • boo
  • \n
\n", + "example": 295, + "start_line": 4933, + "end_line": 4945, + "section": "List items" + }, + { + "markdown": "10) foo\n - bar\n", + "html": "
    \n
  1. foo\n
      \n
    • bar
    • \n
    \n
  2. \n
\n", + "example": 296, + "start_line": 4950, + "end_line": 4961, + "section": "List items" + }, + { + "markdown": "10) foo\n - bar\n", + "html": "
    \n
  1. foo
  2. \n
\n
    \n
  • bar
  • \n
\n", + "example": 297, + "start_line": 4966, + "end_line": 4976, + "section": "List items" + }, + { + "markdown": "- - foo\n", + "html": "
    \n
  • \n
      \n
    • foo
    • \n
    \n
  • \n
\n", + "example": 298, + "start_line": 4981, + "end_line": 4991, + "section": "List items" + }, + { + "markdown": "1. - 2. foo\n", + "html": "
    \n
  1. \n
      \n
    • \n
        \n
      1. foo
      2. \n
      \n
    • \n
    \n
  2. \n
\n", + "example": 299, + "start_line": 4994, + "end_line": 5008, + "section": "List items" + }, + { + "markdown": "- # Foo\n- Bar\n ---\n baz\n", + "html": "
    \n
  • \n

    Foo

    \n
  • \n
  • \n

    Bar

    \nbaz
  • \n
\n", + "example": 300, + "start_line": 5013, + "end_line": 5027, + "section": "List items" + }, + { + "markdown": "- foo\n- bar\n+ baz\n", + "html": "
    \n
  • foo
  • \n
  • bar
  • \n
\n
    \n
  • baz
  • \n
\n", + "example": 301, + "start_line": 5249, + "end_line": 5261, + "section": "Lists" + }, + { + "markdown": "1. foo\n2. bar\n3) baz\n", + "html": "
    \n
  1. foo
  2. \n
  3. bar
  4. \n
\n
    \n
  1. baz
  2. \n
\n", + "example": 302, + "start_line": 5264, + "end_line": 5276, + "section": "Lists" + }, + { + "markdown": "Foo\n- bar\n- baz\n", + "html": "

Foo

\n
    \n
  • bar
  • \n
  • baz
  • \n
\n", + "example": 303, + "start_line": 5283, + "end_line": 5293, + "section": "Lists" + }, + { + "markdown": "The number of windows in my house is\n14. The number of doors is 6.\n", + "html": "

The number of windows in my house is\n14. The number of doors is 6.

\n", + "example": 304, + "start_line": 5360, + "end_line": 5366, + "section": "Lists" + }, + { + "markdown": "The number of windows in my house is\n1. The number of doors is 6.\n", + "html": "

The number of windows in my house is

\n
    \n
  1. The number of doors is 6.
  2. \n
\n", + "example": 305, + "start_line": 5370, + "end_line": 5378, + "section": "Lists" + }, + { + "markdown": "- foo\n\n- bar\n\n\n- baz\n", + "html": "
    \n
  • \n

    foo

    \n
  • \n
  • \n

    bar

    \n
  • \n
  • \n

    baz

    \n
  • \n
\n", + "example": 306, + "start_line": 5384, + "end_line": 5403, + "section": "Lists" + }, + { + "markdown": "- foo\n - bar\n - baz\n\n\n bim\n", + "html": "
    \n
  • foo\n
      \n
    • bar\n
        \n
      • \n

        baz

        \n

        bim

        \n
      • \n
      \n
    • \n
    \n
  • \n
\n", + "example": 307, + "start_line": 5405, + "end_line": 5427, + "section": "Lists" + }, + { + "markdown": "- foo\n- bar\n\n\n\n- baz\n- bim\n", + "html": "
    \n
  • foo
  • \n
  • bar
  • \n
\n\n
    \n
  • baz
  • \n
  • bim
  • \n
\n", + "example": 308, + "start_line": 5435, + "end_line": 5453, + "section": "Lists" + }, + { + "markdown": "- foo\n\n notcode\n\n- foo\n\n\n\n code\n", + "html": "
    \n
  • \n

    foo

    \n

    notcode

    \n
  • \n
  • \n

    foo

    \n
  • \n
\n\n
code\n
\n", + "example": 309, + "start_line": 5456, + "end_line": 5479, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n", + "html": "
    \n
  • a
  • \n
  • b
  • \n
  • c
  • \n
  • d
  • \n
  • e
  • \n
  • f
  • \n
  • g
  • \n
\n", + "example": 310, + "start_line": 5487, + "end_line": 5505, + "section": "Lists" + }, + { + "markdown": "1. a\n\n 2. b\n\n 3. c\n", + "html": "
    \n
  1. \n

    a

    \n
  2. \n
  3. \n

    b

    \n
  4. \n
  5. \n

    c

    \n
  6. \n
\n", + "example": 311, + "start_line": 5508, + "end_line": 5526, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n - d\n - e\n", + "html": "
    \n
  • a
  • \n
  • b
  • \n
  • c
  • \n
  • d\n- e
  • \n
\n", + "example": 312, + "start_line": 5532, + "end_line": 5546, + "section": "Lists" + }, + { + "markdown": "1. a\n\n 2. b\n\n 3. c\n", + "html": "
    \n
  1. \n

    a

    \n
  2. \n
  3. \n

    b

    \n
  4. \n
\n
3. c\n
\n", + "example": 313, + "start_line": 5552, + "end_line": 5569, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n- c\n", + "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n

    b

    \n
  • \n
  • \n

    c

    \n
  • \n
\n", + "example": 314, + "start_line": 5575, + "end_line": 5592, + "section": "Lists" + }, + { + "markdown": "* a\n*\n\n* c\n", + "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n
  • \n

    c

    \n
  • \n
\n", + "example": 315, + "start_line": 5597, + "end_line": 5612, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n c\n- d\n", + "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n

    b

    \n

    c

    \n
  • \n
  • \n

    d

    \n
  • \n
\n", + "example": 316, + "start_line": 5619, + "end_line": 5638, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n [ref]: /url\n- d\n", + "html": "
    \n
  • \n

    a

    \n
  • \n
  • \n

    b

    \n
  • \n
  • \n

    d

    \n
  • \n
\n", + "example": 317, + "start_line": 5641, + "end_line": 5659, + "section": "Lists" + }, + { + "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n", + "html": "
    \n
  • a
  • \n
  • \n
    b\n\n\n
    \n
  • \n
  • c
  • \n
\n", + "example": 318, + "start_line": 5664, + "end_line": 5683, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n\n c\n- d\n", + "html": "
    \n
  • a\n
      \n
    • \n

      b

      \n

      c

      \n
    • \n
    \n
  • \n
  • d
  • \n
\n", + "example": 319, + "start_line": 5690, + "end_line": 5708, + "section": "Lists" + }, + { + "markdown": "* a\n > b\n >\n* c\n", + "html": "
    \n
  • a\n
    \n

    b

    \n
    \n
  • \n
  • c
  • \n
\n", + "example": 320, + "start_line": 5714, + "end_line": 5728, + "section": "Lists" + }, + { + "markdown": "- a\n > b\n ```\n c\n ```\n- d\n", + "html": "
    \n
  • a\n
    \n

    b

    \n
    \n
    c\n
    \n
  • \n
  • d
  • \n
\n", + "example": 321, + "start_line": 5734, + "end_line": 5752, + "section": "Lists" + }, + { + "markdown": "- a\n", + "html": "
    \n
  • a
  • \n
\n", + "example": 322, + "start_line": 5757, + "end_line": 5763, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n", + "html": "
    \n
  • a\n
      \n
    • b
    • \n
    \n
  • \n
\n", + "example": 323, + "start_line": 5766, + "end_line": 5777, + "section": "Lists" + }, + { + "markdown": "1. ```\n foo\n ```\n\n bar\n", + "html": "
    \n
  1. \n
    foo\n
    \n

    bar

    \n
  2. \n
\n", + "example": 324, + "start_line": 5783, + "end_line": 5797, + "section": "Lists" + }, + { + "markdown": "* foo\n * bar\n\n baz\n", + "html": "
    \n
  • \n

    foo

    \n
      \n
    • bar
    • \n
    \n

    baz

    \n
  • \n
\n", + "example": 325, + "start_line": 5802, + "end_line": 5817, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n\n- d\n - e\n - f\n", + "html": "
    \n
  • \n

    a

    \n
      \n
    • b
    • \n
    • c
    • \n
    \n
  • \n
  • \n

    d

    \n
      \n
    • e
    • \n
    • f
    • \n
    \n
  • \n
\n", + "example": 326, + "start_line": 5820, + "end_line": 5845, + "section": "Lists" + }, + { + "markdown": "`hi`lo`\n", + "html": "

hilo`

\n", + "example": 327, + "start_line": 5854, + "end_line": 5858, + "section": "Inlines" + }, + { + "markdown": "`foo`\n", + "html": "

foo

\n", + "example": 328, + "start_line": 5886, + "end_line": 5890, + "section": "Code spans" + }, + { + "markdown": "`` foo ` bar ``\n", + "html": "

foo ` bar

\n", + "example": 329, + "start_line": 5897, + "end_line": 5901, + "section": "Code spans" + }, + { + "markdown": "` `` `\n", + "html": "

``

\n", + "example": 330, + "start_line": 5907, + "end_line": 5911, + "section": "Code spans" + }, + { + "markdown": "` `` `\n", + "html": "

``

\n", + "example": 331, + "start_line": 5915, + "end_line": 5919, + "section": "Code spans" + }, + { + "markdown": "` a`\n", + "html": "

a

\n", + "example": 332, + "start_line": 5924, + "end_line": 5928, + "section": "Code spans" + }, + { + "markdown": "` b `\n", + "html": "

 b 

\n", + "example": 333, + "start_line": 5933, + "end_line": 5937, + "section": "Code spans" + }, + { + "markdown": "` `\n` `\n", + "html": "

 \n

\n", + "example": 334, + "start_line": 5941, + "end_line": 5947, + "section": "Code spans" + }, + { + "markdown": "``\nfoo\nbar \nbaz\n``\n", + "html": "

foo bar baz

\n", + "example": 335, + "start_line": 5952, + "end_line": 5960, + "section": "Code spans" + }, + { + "markdown": "``\nfoo \n``\n", + "html": "

foo

\n", + "example": 336, + "start_line": 5962, + "end_line": 5968, + "section": "Code spans" + }, + { + "markdown": "`foo bar \nbaz`\n", + "html": "

foo bar baz

\n", + "example": 337, + "start_line": 5973, + "end_line": 5978, + "section": "Code spans" + }, + { + "markdown": "`foo\\`bar`\n", + "html": "

foo\\bar`

\n", + "example": 338, + "start_line": 5990, + "end_line": 5994, + "section": "Code spans" + }, + { + "markdown": "``foo`bar``\n", + "html": "

foo`bar

\n", + "example": 339, + "start_line": 6001, + "end_line": 6005, + "section": "Code spans" + }, + { + "markdown": "` foo `` bar `\n", + "html": "

foo `` bar

\n", + "example": 340, + "start_line": 6007, + "end_line": 6011, + "section": "Code spans" + }, + { + "markdown": "*foo`*`\n", + "html": "

*foo*

\n", + "example": 341, + "start_line": 6019, + "end_line": 6023, + "section": "Code spans" + }, + { + "markdown": "[not a `link](/foo`)\n", + "html": "

[not a link](/foo)

\n", + "example": 342, + "start_line": 6028, + "end_line": 6032, + "section": "Code spans" + }, + { + "markdown": "``\n", + "html": "

<a href="">`

\n", + "example": 343, + "start_line": 6038, + "end_line": 6042, + "section": "Code spans" + }, + { + "markdown": "
`\n", + "html": "

`

\n", + "example": 344, + "start_line": 6047, + "end_line": 6051, + "section": "Code spans" + }, + { + "markdown": "``\n", + "html": "

<https://foo.bar.baz>`

\n", + "example": 345, + "start_line": 6056, + "end_line": 6060, + "section": "Code spans" + }, + { + "markdown": "`\n", + "html": "

https://foo.bar.`baz`

\n", + "example": 346, + "start_line": 6065, + "end_line": 6069, + "section": "Code spans" + }, + { + "markdown": "```foo``\n", + "html": "

```foo``

\n", + "example": 347, + "start_line": 6075, + "end_line": 6079, + "section": "Code spans" + }, + { + "markdown": "`foo\n", + "html": "

`foo

\n", + "example": 348, + "start_line": 6082, + "end_line": 6086, + "section": "Code spans" + }, + { + "markdown": "`foo``bar``\n", + "html": "

`foobar

\n", + "example": 349, + "start_line": 6091, + "end_line": 6095, + "section": "Code spans" + }, + { + "markdown": "*foo bar*\n", + "html": "

foo bar

\n", + "example": 350, + "start_line": 6308, + "end_line": 6312, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a * foo bar*\n", + "html": "

a * foo bar*

\n", + "example": 351, + "start_line": 6318, + "end_line": 6322, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a*\"foo\"*\n", + "html": "

a*"foo"*

\n", + "example": 352, + "start_line": 6329, + "end_line": 6333, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "* a *\n", + "html": "

* a *

\n", + "example": 353, + "start_line": 6338, + "end_line": 6342, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*$*alpha.\n\n*£*bravo.\n\n*€*charlie.\n", + "html": "

*$*alpha.

\n

*£*bravo.

\n

*€*charlie.

\n", + "example": 354, + "start_line": 6347, + "end_line": 6357, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo*bar*\n", + "html": "

foobar

\n", + "example": 355, + "start_line": 6362, + "end_line": 6366, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5*6*78\n", + "html": "

5678

\n", + "example": 356, + "start_line": 6369, + "end_line": 6373, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo bar_\n", + "html": "

foo bar

\n", + "example": 357, + "start_line": 6378, + "end_line": 6382, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_ foo bar_\n", + "html": "

_ foo bar_

\n", + "example": 358, + "start_line": 6388, + "end_line": 6392, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a_\"foo\"_\n", + "html": "

a_"foo"_

\n", + "example": 359, + "start_line": 6398, + "end_line": 6402, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo_bar_\n", + "html": "

foo_bar_

\n", + "example": 360, + "start_line": 6407, + "end_line": 6411, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5_6_78\n", + "html": "

5_6_78

\n", + "example": 361, + "start_line": 6414, + "end_line": 6418, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "пристаням_стремятся_\n", + "html": "

пристаням_стремятся_

\n", + "example": 362, + "start_line": 6421, + "end_line": 6425, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "aa_\"bb\"_cc\n", + "html": "

aa_"bb"_cc

\n", + "example": 363, + "start_line": 6431, + "end_line": 6435, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo-_(bar)_\n", + "html": "

foo-(bar)

\n", + "example": 364, + "start_line": 6442, + "end_line": 6446, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo*\n", + "html": "

_foo*

\n", + "example": 365, + "start_line": 6454, + "end_line": 6458, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo bar *\n", + "html": "

*foo bar *

\n", + "example": 366, + "start_line": 6464, + "end_line": 6468, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo bar\n*\n", + "html": "

*foo bar\n*

\n", + "example": 367, + "start_line": 6473, + "end_line": 6479, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(*foo)\n", + "html": "

*(*foo)

\n", + "example": 368, + "start_line": 6486, + "end_line": 6490, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(*foo*)*\n", + "html": "

(foo)

\n", + "example": 369, + "start_line": 6496, + "end_line": 6500, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo*bar\n", + "html": "

foobar

\n", + "example": 370, + "start_line": 6505, + "end_line": 6509, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo bar _\n", + "html": "

_foo bar _

\n", + "example": 371, + "start_line": 6518, + "end_line": 6522, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(_foo)\n", + "html": "

_(_foo)

\n", + "example": 372, + "start_line": 6528, + "end_line": 6532, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(_foo_)_\n", + "html": "

(foo)

\n", + "example": 373, + "start_line": 6537, + "end_line": 6541, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo_bar\n", + "html": "

_foo_bar

\n", + "example": 374, + "start_line": 6546, + "end_line": 6550, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_пристаням_стремятся\n", + "html": "

_пристаням_стремятся

\n", + "example": 375, + "start_line": 6553, + "end_line": 6557, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo_bar_baz_\n", + "html": "

foo_bar_baz

\n", + "example": 376, + "start_line": 6560, + "end_line": 6564, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(bar)_.\n", + "html": "

(bar).

\n", + "example": 377, + "start_line": 6571, + "end_line": 6575, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo bar**\n", + "html": "

foo bar

\n", + "example": 378, + "start_line": 6580, + "end_line": 6584, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "** foo bar**\n", + "html": "

** foo bar**

\n", + "example": 379, + "start_line": 6590, + "end_line": 6594, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a**\"foo\"**\n", + "html": "

a**"foo"**

\n", + "example": 380, + "start_line": 6601, + "end_line": 6605, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo**bar**\n", + "html": "

foobar

\n", + "example": 381, + "start_line": 6610, + "end_line": 6614, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo bar__\n", + "html": "

foo bar

\n", + "example": 382, + "start_line": 6619, + "end_line": 6623, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__ foo bar__\n", + "html": "

__ foo bar__

\n", + "example": 383, + "start_line": 6629, + "end_line": 6633, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__\nfoo bar__\n", + "html": "

__\nfoo bar__

\n", + "example": 384, + "start_line": 6637, + "end_line": 6643, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a__\"foo\"__\n", + "html": "

a__"foo"__

\n", + "example": 385, + "start_line": 6649, + "end_line": 6653, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo__bar__\n", + "html": "

foo__bar__

\n", + "example": 386, + "start_line": 6658, + "end_line": 6662, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5__6__78\n", + "html": "

5__6__78

\n", + "example": 387, + "start_line": 6665, + "end_line": 6669, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "пристаням__стремятся__\n", + "html": "

пристаням__стремятся__

\n", + "example": 388, + "start_line": 6672, + "end_line": 6676, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo, __bar__, baz__\n", + "html": "

foo, bar, baz

\n", + "example": 389, + "start_line": 6679, + "end_line": 6683, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo-__(bar)__\n", + "html": "

foo-(bar)

\n", + "example": 390, + "start_line": 6690, + "end_line": 6694, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo bar **\n", + "html": "

**foo bar **

\n", + "example": 391, + "start_line": 6703, + "end_line": 6707, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**(**foo)\n", + "html": "

**(**foo)

\n", + "example": 392, + "start_line": 6716, + "end_line": 6720, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(**foo**)*\n", + "html": "

(foo)

\n", + "example": 393, + "start_line": 6726, + "end_line": 6730, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n", + "html": "

Gomphocarpus (Gomphocarpus physocarpus, syn.\nAsclepias physocarpa)

\n", + "example": 394, + "start_line": 6733, + "end_line": 6739, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo \"*bar*\" foo**\n", + "html": "

foo "bar" foo

\n", + "example": 395, + "start_line": 6742, + "end_line": 6746, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo**bar\n", + "html": "

foobar

\n", + "example": 396, + "start_line": 6751, + "end_line": 6755, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo bar __\n", + "html": "

__foo bar __

\n", + "example": 397, + "start_line": 6763, + "end_line": 6767, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__(__foo)\n", + "html": "

__(__foo)

\n", + "example": 398, + "start_line": 6773, + "end_line": 6777, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(__foo__)_\n", + "html": "

(foo)

\n", + "example": 399, + "start_line": 6783, + "end_line": 6787, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__bar\n", + "html": "

__foo__bar

\n", + "example": 400, + "start_line": 6792, + "end_line": 6796, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__пристаням__стремятся\n", + "html": "

__пристаням__стремятся

\n", + "example": 401, + "start_line": 6799, + "end_line": 6803, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__bar__baz__\n", + "html": "

foo__bar__baz

\n", + "example": 402, + "start_line": 6806, + "end_line": 6810, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__(bar)__.\n", + "html": "

(bar).

\n", + "example": 403, + "start_line": 6817, + "end_line": 6821, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo [bar](/url)*\n", + "html": "

foo bar

\n", + "example": 404, + "start_line": 6829, + "end_line": 6833, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo\nbar*\n", + "html": "

foo\nbar

\n", + "example": 405, + "start_line": 6836, + "end_line": 6842, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo __bar__ baz_\n", + "html": "

foo bar baz

\n", + "example": 406, + "start_line": 6848, + "end_line": 6852, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo _bar_ baz_\n", + "html": "

foo bar baz

\n", + "example": 407, + "start_line": 6855, + "end_line": 6859, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo_ bar_\n", + "html": "

foo bar

\n", + "example": 408, + "start_line": 6862, + "end_line": 6866, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo *bar**\n", + "html": "

foo bar

\n", + "example": 409, + "start_line": 6869, + "end_line": 6873, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar** baz*\n", + "html": "

foo bar baz

\n", + "example": 410, + "start_line": 6876, + "end_line": 6880, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar**baz*\n", + "html": "

foobarbaz

\n", + "example": 411, + "start_line": 6882, + "end_line": 6886, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar*\n", + "html": "

foo**bar

\n", + "example": 412, + "start_line": 6906, + "end_line": 6910, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo** bar*\n", + "html": "

foo bar

\n", + "example": 413, + "start_line": 6919, + "end_line": 6923, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar***\n", + "html": "

foo bar

\n", + "example": 414, + "start_line": 6926, + "end_line": 6930, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar***\n", + "html": "

foobar

\n", + "example": 415, + "start_line": 6933, + "end_line": 6937, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo***bar***baz\n", + "html": "

foobarbaz

\n", + "example": 416, + "start_line": 6944, + "end_line": 6948, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo******bar*********baz\n", + "html": "

foobar***baz

\n", + "example": 417, + "start_line": 6950, + "end_line": 6954, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar *baz* bim** bop*\n", + "html": "

foo bar baz bim bop

\n", + "example": 418, + "start_line": 6959, + "end_line": 6963, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo [*bar*](/url)*\n", + "html": "

foo bar

\n", + "example": 419, + "start_line": 6966, + "end_line": 6970, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "** is not an empty emphasis\n", + "html": "

** is not an empty emphasis

\n", + "example": 420, + "start_line": 6975, + "end_line": 6979, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**** is not an empty strong emphasis\n", + "html": "

**** is not an empty strong emphasis

\n", + "example": 421, + "start_line": 6982, + "end_line": 6986, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo [bar](/url)**\n", + "html": "

foo bar

\n", + "example": 422, + "start_line": 6995, + "end_line": 6999, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo\nbar**\n", + "html": "

foo\nbar

\n", + "example": 423, + "start_line": 7002, + "end_line": 7008, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo _bar_ baz__\n", + "html": "

foo bar baz

\n", + "example": 424, + "start_line": 7014, + "end_line": 7018, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo __bar__ baz__\n", + "html": "

foo bar baz

\n", + "example": 425, + "start_line": 7021, + "end_line": 7025, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo__ bar__\n", + "html": "

foo bar

\n", + "example": 426, + "start_line": 7028, + "end_line": 7032, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo **bar****\n", + "html": "

foo bar

\n", + "example": 427, + "start_line": 7035, + "end_line": 7039, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar* baz**\n", + "html": "

foo bar baz

\n", + "example": 428, + "start_line": 7042, + "end_line": 7046, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo*bar*baz**\n", + "html": "

foobarbaz

\n", + "example": 429, + "start_line": 7049, + "end_line": 7053, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo* bar**\n", + "html": "

foo bar

\n", + "example": 430, + "start_line": 7056, + "end_line": 7060, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar***\n", + "html": "

foo bar

\n", + "example": 431, + "start_line": 7063, + "end_line": 7067, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar **baz**\nbim* bop**\n", + "html": "

foo bar baz\nbim bop

\n", + "example": 432, + "start_line": 7072, + "end_line": 7078, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo [*bar*](/url)**\n", + "html": "

foo bar

\n", + "example": 433, + "start_line": 7081, + "end_line": 7085, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__ is not an empty emphasis\n", + "html": "

__ is not an empty emphasis

\n", + "example": 434, + "start_line": 7090, + "end_line": 7094, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____ is not an empty strong emphasis\n", + "html": "

____ is not an empty strong emphasis

\n", + "example": 435, + "start_line": 7097, + "end_line": 7101, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo ***\n", + "html": "

foo ***

\n", + "example": 436, + "start_line": 7107, + "end_line": 7111, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *\\**\n", + "html": "

foo *

\n", + "example": 437, + "start_line": 7114, + "end_line": 7118, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *_*\n", + "html": "

foo _

\n", + "example": 438, + "start_line": 7121, + "end_line": 7125, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *****\n", + "html": "

foo *****

\n", + "example": 439, + "start_line": 7128, + "end_line": 7132, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo **\\***\n", + "html": "

foo *

\n", + "example": 440, + "start_line": 7135, + "end_line": 7139, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo **_**\n", + "html": "

foo _

\n", + "example": 441, + "start_line": 7142, + "end_line": 7146, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo*\n", + "html": "

*foo

\n", + "example": 442, + "start_line": 7153, + "end_line": 7157, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**\n", + "html": "

foo*

\n", + "example": 443, + "start_line": 7160, + "end_line": 7164, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo**\n", + "html": "

*foo

\n", + "example": 444, + "start_line": 7167, + "end_line": 7171, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "****foo*\n", + "html": "

***foo

\n", + "example": 445, + "start_line": 7174, + "end_line": 7178, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo***\n", + "html": "

foo*

\n", + "example": 446, + "start_line": 7181, + "end_line": 7185, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo****\n", + "html": "

foo***

\n", + "example": 447, + "start_line": 7188, + "end_line": 7192, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo ___\n", + "html": "

foo ___

\n", + "example": 448, + "start_line": 7198, + "end_line": 7202, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _\\__\n", + "html": "

foo _

\n", + "example": 449, + "start_line": 7205, + "end_line": 7209, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _*_\n", + "html": "

foo *

\n", + "example": 450, + "start_line": 7212, + "end_line": 7216, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _____\n", + "html": "

foo _____

\n", + "example": 451, + "start_line": 7219, + "end_line": 7223, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo __\\___\n", + "html": "

foo _

\n", + "example": 452, + "start_line": 7226, + "end_line": 7230, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo __*__\n", + "html": "

foo *

\n", + "example": 453, + "start_line": 7233, + "end_line": 7237, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo_\n", + "html": "

_foo

\n", + "example": 454, + "start_line": 7240, + "end_line": 7244, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo__\n", + "html": "

foo_

\n", + "example": 455, + "start_line": 7251, + "end_line": 7255, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "___foo__\n", + "html": "

_foo

\n", + "example": 456, + "start_line": 7258, + "end_line": 7262, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo_\n", + "html": "

___foo

\n", + "example": 457, + "start_line": 7265, + "end_line": 7269, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo___\n", + "html": "

foo_

\n", + "example": 458, + "start_line": 7272, + "end_line": 7276, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo____\n", + "html": "

foo___

\n", + "example": 459, + "start_line": 7279, + "end_line": 7283, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo**\n", + "html": "

foo

\n", + "example": 460, + "start_line": 7289, + "end_line": 7293, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*_foo_*\n", + "html": "

foo

\n", + "example": 461, + "start_line": 7296, + "end_line": 7300, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__\n", + "html": "

foo

\n", + "example": 462, + "start_line": 7303, + "end_line": 7307, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_*foo*_\n", + "html": "

foo

\n", + "example": 463, + "start_line": 7310, + "end_line": 7314, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "****foo****\n", + "html": "

foo

\n", + "example": 464, + "start_line": 7320, + "end_line": 7324, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo____\n", + "html": "

foo

\n", + "example": 465, + "start_line": 7327, + "end_line": 7331, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "******foo******\n", + "html": "

foo

\n", + "example": 466, + "start_line": 7338, + "end_line": 7342, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo***\n", + "html": "

foo

\n", + "example": 467, + "start_line": 7347, + "end_line": 7351, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_____foo_____\n", + "html": "

foo

\n", + "example": 468, + "start_line": 7354, + "end_line": 7358, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo _bar* baz_\n", + "html": "

foo _bar baz_

\n", + "example": 469, + "start_line": 7363, + "end_line": 7367, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo __bar *baz bim__ bam*\n", + "html": "

foo bar *baz bim bam

\n", + "example": 470, + "start_line": 7370, + "end_line": 7374, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo **bar baz**\n", + "html": "

**foo bar baz

\n", + "example": 471, + "start_line": 7379, + "end_line": 7383, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo *bar baz*\n", + "html": "

*foo bar baz

\n", + "example": 472, + "start_line": 7386, + "end_line": 7390, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*[bar*](/url)\n", + "html": "

*bar*

\n", + "example": 473, + "start_line": 7395, + "end_line": 7399, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo [bar_](/url)\n", + "html": "

_foo bar_

\n", + "example": 474, + "start_line": 7402, + "end_line": 7406, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*\n", + "html": "

*

\n", + "example": 475, + "start_line": 7409, + "end_line": 7413, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**\n", + "html": "

**

\n", + "example": 476, + "start_line": 7416, + "end_line": 7420, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__\n", + "html": "

__

\n", + "example": 477, + "start_line": 7423, + "end_line": 7427, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*a `*`*\n", + "html": "

a *

\n", + "example": 478, + "start_line": 7430, + "end_line": 7434, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_a `_`_\n", + "html": "

a _

\n", + "example": 479, + "start_line": 7437, + "end_line": 7441, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**a\n", + "html": "

**ahttps://foo.bar/?q=**

\n", + "example": 480, + "start_line": 7444, + "end_line": 7448, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__a\n", + "html": "

__ahttps://foo.bar/?q=__

\n", + "example": 481, + "start_line": 7451, + "end_line": 7455, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "[link](/uri \"title\")\n", + "html": "

link

\n", + "example": 482, + "start_line": 7539, + "end_line": 7543, + "section": "Links" + }, + { + "markdown": "[link](/uri)\n", + "html": "

link

\n", + "example": 483, + "start_line": 7549, + "end_line": 7553, + "section": "Links" + }, + { + "markdown": "[](./target.md)\n", + "html": "

\n", + "example": 484, + "start_line": 7555, + "end_line": 7559, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

link

\n", + "example": 485, + "start_line": 7562, + "end_line": 7566, + "section": "Links" + }, + { + "markdown": "[link](<>)\n", + "html": "

link

\n", + "example": 486, + "start_line": 7569, + "end_line": 7573, + "section": "Links" + }, + { + "markdown": "[]()\n", + "html": "

\n", + "example": 487, + "start_line": 7576, + "end_line": 7580, + "section": "Links" + }, + { + "markdown": "[link](/my uri)\n", + "html": "

[link](/my uri)

\n", + "example": 488, + "start_line": 7585, + "end_line": 7589, + "section": "Links" + }, + { + "markdown": "[link](
)\n", + "html": "

link

\n", + "example": 489, + "start_line": 7591, + "end_line": 7595, + "section": "Links" + }, + { + "markdown": "[link](foo\nbar)\n", + "html": "

[link](foo\nbar)

\n", + "example": 490, + "start_line": 7600, + "end_line": 7606, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

[link]()

\n", + "example": 491, + "start_line": 7608, + "end_line": 7614, + "section": "Links" + }, + { + "markdown": "[a]()\n", + "html": "

a

\n", + "example": 492, + "start_line": 7619, + "end_line": 7623, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

[link](<foo>)

\n", + "example": 493, + "start_line": 7627, + "end_line": 7631, + "section": "Links" + }, + { + "markdown": "[a](\n[a](c)\n", + "html": "

[a](<b)c\n[a](<b)c>\n[a](c)

\n", + "example": 494, + "start_line": 7636, + "end_line": 7644, + "section": "Links" + }, + { + "markdown": "[link](\\(foo\\))\n", + "html": "

link

\n", + "example": 495, + "start_line": 7648, + "end_line": 7652, + "section": "Links" + }, + { + "markdown": "[link](foo(and(bar)))\n", + "html": "

link

\n", + "example": 496, + "start_line": 7657, + "end_line": 7661, + "section": "Links" + }, + { + "markdown": "[link](foo(and(bar))\n", + "html": "

[link](foo(and(bar))

\n", + "example": 497, + "start_line": 7666, + "end_line": 7670, + "section": "Links" + }, + { + "markdown": "[link](foo\\(and\\(bar\\))\n", + "html": "

link

\n", + "example": 498, + "start_line": 7673, + "end_line": 7677, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

link

\n", + "example": 499, + "start_line": 7680, + "end_line": 7684, + "section": "Links" + }, + { + "markdown": "[link](foo\\)\\:)\n", + "html": "

link

\n", + "example": 500, + "start_line": 7690, + "end_line": 7694, + "section": "Links" + }, + { + "markdown": "[link](#fragment)\n\n[link](https://example.com#fragment)\n\n[link](https://example.com?foo=3#frag)\n", + "html": "

link

\n

link

\n

link

\n", + "example": 501, + "start_line": 7699, + "end_line": 7709, + "section": "Links" + }, + { + "markdown": "[link](foo\\bar)\n", + "html": "

link

\n", + "example": 502, + "start_line": 7715, + "end_line": 7719, + "section": "Links" + }, + { + "markdown": "[link](foo%20bä)\n", + "html": "

link

\n", + "example": 503, + "start_line": 7731, + "end_line": 7735, + "section": "Links" + }, + { + "markdown": "[link](\"title\")\n", + "html": "

link

\n", + "example": 504, + "start_line": 7742, + "end_line": 7746, + "section": "Links" + }, + { + "markdown": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n", + "html": "

link\nlink\nlink

\n", + "example": 505, + "start_line": 7751, + "end_line": 7759, + "section": "Links" + }, + { + "markdown": "[link](/url \"title \\\""\")\n", + "html": "

link

\n", + "example": 506, + "start_line": 7765, + "end_line": 7769, + "section": "Links" + }, + { + "markdown": "[link](/url \"title\")\n", + "html": "

link

\n", + "example": 507, + "start_line": 7776, + "end_line": 7780, + "section": "Links" + }, + { + "markdown": "[link](/url \"title \"and\" title\")\n", + "html": "

[link](/url "title "and" title")

\n", + "example": 508, + "start_line": 7785, + "end_line": 7789, + "section": "Links" + }, + { + "markdown": "[link](/url 'title \"and\" title')\n", + "html": "

link

\n", + "example": 509, + "start_line": 7794, + "end_line": 7798, + "section": "Links" + }, + { + "markdown": "[link]( /uri\n \"title\" )\n", + "html": "

link

\n", + "example": 510, + "start_line": 7819, + "end_line": 7824, + "section": "Links" + }, + { + "markdown": "[link] (/uri)\n", + "html": "

[link] (/uri)

\n", + "example": 511, + "start_line": 7830, + "end_line": 7834, + "section": "Links" + }, + { + "markdown": "[link [foo [bar]]](/uri)\n", + "html": "

link [foo [bar]]

\n", + "example": 512, + "start_line": 7840, + "end_line": 7844, + "section": "Links" + }, + { + "markdown": "[link] bar](/uri)\n", + "html": "

[link] bar](/uri)

\n", + "example": 513, + "start_line": 7847, + "end_line": 7851, + "section": "Links" + }, + { + "markdown": "[link [bar](/uri)\n", + "html": "

[link bar

\n", + "example": 514, + "start_line": 7854, + "end_line": 7858, + "section": "Links" + }, + { + "markdown": "[link \\[bar](/uri)\n", + "html": "

link [bar

\n", + "example": 515, + "start_line": 7861, + "end_line": 7865, + "section": "Links" + }, + { + "markdown": "[link *foo **bar** `#`*](/uri)\n", + "html": "

link foo bar #

\n", + "example": 516, + "start_line": 7870, + "end_line": 7874, + "section": "Links" + }, + { + "markdown": "[![moon](moon.jpg)](/uri)\n", + "html": "

\"moon\"

\n", + "example": 517, + "start_line": 7877, + "end_line": 7881, + "section": "Links" + }, + { + "markdown": "[foo [bar](/uri)](/uri)\n", + "html": "

[foo bar](/uri)

\n", + "example": 518, + "start_line": 7886, + "end_line": 7890, + "section": "Links" + }, + { + "markdown": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n", + "html": "

[foo [bar baz](/uri)](/uri)

\n", + "example": 519, + "start_line": 7893, + "end_line": 7897, + "section": "Links" + }, + { + "markdown": "![[[foo](uri1)](uri2)](uri3)\n", + "html": "

\"[foo](uri2)\"

\n", + "example": 520, + "start_line": 7900, + "end_line": 7904, + "section": "Links" + }, + { + "markdown": "*[foo*](/uri)\n", + "html": "

*foo*

\n", + "example": 521, + "start_line": 7910, + "end_line": 7914, + "section": "Links" + }, + { + "markdown": "[foo *bar](baz*)\n", + "html": "

foo *bar

\n", + "example": 522, + "start_line": 7917, + "end_line": 7921, + "section": "Links" + }, + { + "markdown": "*foo [bar* baz]\n", + "html": "

foo [bar baz]

\n", + "example": 523, + "start_line": 7927, + "end_line": 7931, + "section": "Links" + }, + { + "markdown": "[foo \n", + "html": "

[foo

\n", + "example": 524, + "start_line": 7937, + "end_line": 7941, + "section": "Links" + }, + { + "markdown": "[foo`](/uri)`\n", + "html": "

[foo](/uri)

\n", + "example": 525, + "start_line": 7944, + "end_line": 7948, + "section": "Links" + }, + { + "markdown": "[foo\n", + "html": "

[foohttps://example.com/?search=](uri)

\n", + "example": 526, + "start_line": 7951, + "end_line": 7955, + "section": "Links" + }, + { + "markdown": "[foo][bar]\n\n[bar]: /url \"title\"\n", + "html": "

foo

\n", + "example": 527, + "start_line": 7989, + "end_line": 7995, + "section": "Links" + }, + { + "markdown": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n", + "html": "

link [foo [bar]]

\n", + "example": 528, + "start_line": 8004, + "end_line": 8010, + "section": "Links" + }, + { + "markdown": "[link \\[bar][ref]\n\n[ref]: /uri\n", + "html": "

link [bar

\n", + "example": 529, + "start_line": 8013, + "end_line": 8019, + "section": "Links" + }, + { + "markdown": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n", + "html": "

link foo bar #

\n", + "example": 530, + "start_line": 8024, + "end_line": 8030, + "section": "Links" + }, + { + "markdown": "[![moon](moon.jpg)][ref]\n\n[ref]: /uri\n", + "html": "

\"moon\"

\n", + "example": 531, + "start_line": 8033, + "end_line": 8039, + "section": "Links" + }, + { + "markdown": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n", + "html": "

[foo bar]ref

\n", + "example": 532, + "start_line": 8044, + "end_line": 8050, + "section": "Links" + }, + { + "markdown": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n", + "html": "

[foo bar baz]ref

\n", + "example": 533, + "start_line": 8053, + "end_line": 8059, + "section": "Links" + }, + { + "markdown": "*[foo*][ref]\n\n[ref]: /uri\n", + "html": "

*foo*

\n", + "example": 534, + "start_line": 8068, + "end_line": 8074, + "section": "Links" + }, + { + "markdown": "[foo *bar][ref]*\n\n[ref]: /uri\n", + "html": "

foo *bar*

\n", + "example": 535, + "start_line": 8077, + "end_line": 8083, + "section": "Links" + }, + { + "markdown": "[foo \n\n[ref]: /uri\n", + "html": "

[foo

\n", + "example": 536, + "start_line": 8089, + "end_line": 8095, + "section": "Links" + }, + { + "markdown": "[foo`][ref]`\n\n[ref]: /uri\n", + "html": "

[foo][ref]

\n", + "example": 537, + "start_line": 8098, + "end_line": 8104, + "section": "Links" + }, + { + "markdown": "[foo\n\n[ref]: /uri\n", + "html": "

[foohttps://example.com/?search=][ref]

\n", + "example": 538, + "start_line": 8107, + "end_line": 8113, + "section": "Links" + }, + { + "markdown": "[foo][BaR]\n\n[bar]: /url \"title\"\n", + "html": "

foo

\n", + "example": 539, + "start_line": 8118, + "end_line": 8124, + "section": "Links" + }, + { + "markdown": "[ẞ]\n\n[SS]: /url\n", + "html": "

ẞ

\n", + "example": 540, + "start_line": 8129, + "end_line": 8135, + "section": "Links" + }, + { + "markdown": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n", + "html": "

Baz

\n", + "example": 541, + "start_line": 8141, + "end_line": 8148, + "section": "Links" + }, + { + "markdown": "[foo] [bar]\n\n[bar]: /url \"title\"\n", + "html": "

[foo] bar

\n", + "example": 542, + "start_line": 8154, + "end_line": 8160, + "section": "Links" + }, + { + "markdown": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n", + "html": "

[foo]\nbar

\n", + "example": 543, + "start_line": 8163, + "end_line": 8171, + "section": "Links" + }, + { + "markdown": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n", + "html": "

bar

\n", + "example": 544, + "start_line": 8204, + "end_line": 8212, + "section": "Links" + }, + { + "markdown": "[bar][foo\\!]\n\n[foo!]: /url\n", + "html": "

[bar][foo!]

\n", + "example": 545, + "start_line": 8219, + "end_line": 8225, + "section": "Links" + }, + { + "markdown": "[foo][ref[]\n\n[ref[]: /uri\n", + "html": "

[foo][ref[]

\n

[ref[]: /uri

\n", + "example": 546, + "start_line": 8231, + "end_line": 8238, + "section": "Links" + }, + { + "markdown": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n", + "html": "

[foo][ref[bar]]

\n

[ref[bar]]: /uri

\n", + "example": 547, + "start_line": 8241, + "end_line": 8248, + "section": "Links" + }, + { + "markdown": "[[[foo]]]\n\n[[[foo]]]: /url\n", + "html": "

[[[foo]]]

\n

[[[foo]]]: /url

\n", + "example": 548, + "start_line": 8251, + "end_line": 8258, + "section": "Links" + }, + { + "markdown": "[foo][ref\\[]\n\n[ref\\[]: /uri\n", + "html": "

foo

\n", + "example": 549, + "start_line": 8261, + "end_line": 8267, + "section": "Links" + }, + { + "markdown": "[bar\\\\]: /uri\n\n[bar\\\\]\n", + "html": "

bar\\

\n", + "example": 550, + "start_line": 8272, + "end_line": 8278, + "section": "Links" + }, + { + "markdown": "[]\n\n[]: /uri\n", + "html": "

[]

\n

[]: /uri

\n", + "example": 551, + "start_line": 8284, + "end_line": 8291, + "section": "Links" + }, + { + "markdown": "[\n ]\n\n[\n ]: /uri\n", + "html": "

[\n]

\n

[\n]: /uri

\n", + "example": 552, + "start_line": 8294, + "end_line": 8305, + "section": "Links" + }, + { + "markdown": "[foo][]\n\n[foo]: /url \"title\"\n", + "html": "

foo

\n", + "example": 553, + "start_line": 8317, + "end_line": 8323, + "section": "Links" + }, + { + "markdown": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

foo bar

\n", + "example": 554, + "start_line": 8326, + "end_line": 8332, + "section": "Links" + }, + { + "markdown": "[Foo][]\n\n[foo]: /url \"title\"\n", + "html": "

Foo

\n", + "example": 555, + "start_line": 8337, + "end_line": 8343, + "section": "Links" + }, + { + "markdown": "[foo] \n[]\n\n[foo]: /url \"title\"\n", + "html": "

foo\n[]

\n", + "example": 556, + "start_line": 8350, + "end_line": 8358, + "section": "Links" + }, + { + "markdown": "[foo]\n\n[foo]: /url \"title\"\n", + "html": "

foo

\n", + "example": 557, + "start_line": 8370, + "end_line": 8376, + "section": "Links" + }, + { + "markdown": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

foo bar

\n", + "example": 558, + "start_line": 8379, + "end_line": 8385, + "section": "Links" + }, + { + "markdown": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

[foo bar]

\n", + "example": 559, + "start_line": 8388, + "end_line": 8394, + "section": "Links" + }, + { + "markdown": "[[bar [foo]\n\n[foo]: /url\n", + "html": "

[[bar foo

\n", + "example": 560, + "start_line": 8397, + "end_line": 8403, + "section": "Links" + }, + { + "markdown": "[Foo]\n\n[foo]: /url \"title\"\n", + "html": "

Foo

\n", + "example": 561, + "start_line": 8408, + "end_line": 8414, + "section": "Links" + }, + { + "markdown": "[foo] bar\n\n[foo]: /url\n", + "html": "

foo bar

\n", + "example": 562, + "start_line": 8419, + "end_line": 8425, + "section": "Links" + }, + { + "markdown": "\\[foo]\n\n[foo]: /url \"title\"\n", + "html": "

[foo]

\n", + "example": 563, + "start_line": 8431, + "end_line": 8437, + "section": "Links" + }, + { + "markdown": "[foo*]: /url\n\n*[foo*]\n", + "html": "

*foo*

\n", + "example": 564, + "start_line": 8443, + "end_line": 8449, + "section": "Links" + }, + { + "markdown": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n", + "html": "

foo

\n", + "example": 565, + "start_line": 8455, + "end_line": 8462, + "section": "Links" + }, + { + "markdown": "[foo][]\n\n[foo]: /url1\n", + "html": "

foo

\n", + "example": 566, + "start_line": 8464, + "end_line": 8470, + "section": "Links" + }, + { + "markdown": "[foo]()\n\n[foo]: /url1\n", + "html": "

foo

\n", + "example": 567, + "start_line": 8474, + "end_line": 8480, + "section": "Links" + }, + { + "markdown": "[foo](not a link)\n\n[foo]: /url1\n", + "html": "

foo(not a link)

\n", + "example": 568, + "start_line": 8482, + "end_line": 8488, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url\n", + "html": "

[foo]bar

\n", + "example": 569, + "start_line": 8493, + "end_line": 8499, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n", + "html": "

foobaz

\n", + "example": 570, + "start_line": 8505, + "end_line": 8512, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n", + "html": "

[foo]bar

\n", + "example": 571, + "start_line": 8518, + "end_line": 8525, + "section": "Links" + }, + { + "markdown": "![foo](/url \"title\")\n", + "html": "

\"foo\"

\n", + "example": 572, + "start_line": 8541, + "end_line": 8545, + "section": "Images" + }, + { + "markdown": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "html": "

\"foo

\n", + "example": 573, + "start_line": 8548, + "end_line": 8554, + "section": "Images" + }, + { + "markdown": "![foo ![bar](/url)](/url2)\n", + "html": "

\"foo

\n", + "example": 574, + "start_line": 8557, + "end_line": 8561, + "section": "Images" + }, + { + "markdown": "![foo [bar](/url)](/url2)\n", + "html": "

\"foo

\n", + "example": 575, + "start_line": 8564, + "end_line": 8568, + "section": "Images" + }, + { + "markdown": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "html": "

\"foo

\n", + "example": 576, + "start_line": 8578, + "end_line": 8584, + "section": "Images" + }, + { + "markdown": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n", + "html": "

\"foo

\n", + "example": 577, + "start_line": 8587, + "end_line": 8593, + "section": "Images" + }, + { + "markdown": "![foo](train.jpg)\n", + "html": "

\"foo\"

\n", + "example": 578, + "start_line": 8596, + "end_line": 8600, + "section": "Images" + }, + { + "markdown": "My ![foo bar](/path/to/train.jpg \"title\" )\n", + "html": "

My \"foo

\n", + "example": 579, + "start_line": 8603, + "end_line": 8607, + "section": "Images" + }, + { + "markdown": "![foo]()\n", + "html": "

\"foo\"

\n", + "example": 580, + "start_line": 8610, + "end_line": 8614, + "section": "Images" + }, + { + "markdown": "![](/url)\n", + "html": "

\"\"

\n", + "example": 581, + "start_line": 8617, + "end_line": 8621, + "section": "Images" + }, + { + "markdown": "![foo][bar]\n\n[bar]: /url\n", + "html": "

\"foo\"

\n", + "example": 582, + "start_line": 8626, + "end_line": 8632, + "section": "Images" + }, + { + "markdown": "![foo][bar]\n\n[BAR]: /url\n", + "html": "

\"foo\"

\n", + "example": 583, + "start_line": 8635, + "end_line": 8641, + "section": "Images" + }, + { + "markdown": "![foo][]\n\n[foo]: /url \"title\"\n", + "html": "

\"foo\"

\n", + "example": 584, + "start_line": 8646, + "end_line": 8652, + "section": "Images" + }, + { + "markdown": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

\"foo

\n", + "example": 585, + "start_line": 8655, + "end_line": 8661, + "section": "Images" + }, + { + "markdown": "![Foo][]\n\n[foo]: /url \"title\"\n", + "html": "

\"Foo\"

\n", + "example": 586, + "start_line": 8666, + "end_line": 8672, + "section": "Images" + }, + { + "markdown": "![foo] \n[]\n\n[foo]: /url \"title\"\n", + "html": "

\"foo\"\n[]

\n", + "example": 587, + "start_line": 8678, + "end_line": 8686, + "section": "Images" + }, + { + "markdown": "![foo]\n\n[foo]: /url \"title\"\n", + "html": "

\"foo\"

\n", + "example": 588, + "start_line": 8691, + "end_line": 8697, + "section": "Images" + }, + { + "markdown": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

\"foo

\n", + "example": 589, + "start_line": 8700, + "end_line": 8706, + "section": "Images" + }, + { + "markdown": "![[foo]]\n\n[[foo]]: /url \"title\"\n", + "html": "

![[foo]]

\n

[[foo]]: /url "title"

\n", + "example": 590, + "start_line": 8711, + "end_line": 8718, + "section": "Images" + }, + { + "markdown": "![Foo]\n\n[foo]: /url \"title\"\n", + "html": "

\"Foo\"

\n", + "example": 591, + "start_line": 8723, + "end_line": 8729, + "section": "Images" + }, + { + "markdown": "!\\[foo]\n\n[foo]: /url \"title\"\n", + "html": "

![foo]

\n", + "example": 592, + "start_line": 8735, + "end_line": 8741, + "section": "Images" + }, + { + "markdown": "\\![foo]\n\n[foo]: /url \"title\"\n", + "html": "

!foo

\n", + "example": 593, + "start_line": 8747, + "end_line": 8753, + "section": "Images" + }, + { + "markdown": "\n", + "html": "

http://foo.bar.baz

\n", + "example": 594, + "start_line": 8780, + "end_line": 8784, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

https://foo.bar.baz/test?q=hello&id=22&boolean

\n", + "example": 595, + "start_line": 8787, + "end_line": 8791, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

irc://foo.bar:2233/baz

\n", + "example": 596, + "start_line": 8794, + "end_line": 8798, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

MAILTO:FOO@BAR.BAZ

\n", + "example": 597, + "start_line": 8803, + "end_line": 8807, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

a+b+c:d

\n", + "example": 598, + "start_line": 8815, + "end_line": 8819, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

made-up-scheme://foo,bar

\n", + "example": 599, + "start_line": 8822, + "end_line": 8826, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

https://../

\n", + "example": 600, + "start_line": 8829, + "end_line": 8833, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

localhost:5001/foo

\n", + "example": 601, + "start_line": 8836, + "end_line": 8840, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

<https://foo.bar/baz bim>

\n", + "example": 602, + "start_line": 8845, + "end_line": 8849, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

https://example.com/\\[\\

\n", + "example": 603, + "start_line": 8854, + "end_line": 8858, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

foo@bar.example.com

\n", + "example": 604, + "start_line": 8876, + "end_line": 8880, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

foo+special@Bar.baz-bar0.com

\n", + "example": 605, + "start_line": 8883, + "end_line": 8887, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

<foo+@bar.example.com>

\n", + "example": 606, + "start_line": 8892, + "end_line": 8896, + "section": "Autolinks" + }, + { + "markdown": "<>\n", + "html": "

<>

\n", + "example": 607, + "start_line": 8901, + "end_line": 8905, + "section": "Autolinks" + }, + { + "markdown": "< https://foo.bar >\n", + "html": "

< https://foo.bar >

\n", + "example": 608, + "start_line": 8908, + "end_line": 8912, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

<m:abc>

\n", + "example": 609, + "start_line": 8915, + "end_line": 8919, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

<foo.bar.baz>

\n", + "example": 610, + "start_line": 8922, + "end_line": 8926, + "section": "Autolinks" + }, + { + "markdown": "https://example.com\n", + "html": "

https://example.com

\n", + "example": 611, + "start_line": 8929, + "end_line": 8933, + "section": "Autolinks" + }, + { + "markdown": "foo@bar.example.com\n", + "html": "

foo@bar.example.com

\n", + "example": 612, + "start_line": 8936, + "end_line": 8940, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

\n", + "example": 613, + "start_line": 9016, + "end_line": 9020, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

\n", + "example": 614, + "start_line": 9025, + "end_line": 9029, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

\n", + "example": 615, + "start_line": 9034, + "end_line": 9040, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

\n", + "example": 616, + "start_line": 9045, + "end_line": 9051, + "section": "Raw HTML" + }, + { + "markdown": "Foo \n", + "html": "

Foo

\n", + "example": 617, + "start_line": 9056, + "end_line": 9060, + "section": "Raw HTML" + }, + { + "markdown": "<33> <__>\n", + "html": "

<33> <__>

\n", + "example": 618, + "start_line": 9065, + "end_line": 9069, + "section": "Raw HTML" + }, + { + "markdown": "
\n", + "html": "

<a h*#ref="hi">

\n", + "example": 619, + "start_line": 9074, + "end_line": 9078, + "section": "Raw HTML" + }, + { + "markdown": "
\n", + "html": "

<a href="hi'> <a href=hi'>

\n", + "example": 620, + "start_line": 9083, + "end_line": 9087, + "section": "Raw HTML" + }, + { + "markdown": "< a><\nfoo>\n\n", + "html": "

< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />

\n", + "example": 621, + "start_line": 9092, + "end_line": 9102, + "section": "Raw HTML" + }, + { + "markdown": "
\n", + "html": "

<a href='bar'title=title>

\n", + "example": 622, + "start_line": 9107, + "end_line": 9111, + "section": "Raw HTML" + }, + { + "markdown": "
\n", + "html": "

\n", + "example": 623, + "start_line": 9116, + "end_line": 9120, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

</a href="foo">

\n", + "example": 624, + "start_line": 9125, + "end_line": 9129, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

foo

\n", + "example": 625, + "start_line": 9134, + "end_line": 9140, + "section": "Raw HTML" + }, + { + "markdown": "foo foo -->\n\nfoo foo -->\n", + "html": "

foo foo -->

\n

foo foo -->

\n", + "example": 626, + "start_line": 9142, + "end_line": 9149, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

foo

\n", + "example": 627, + "start_line": 9154, + "end_line": 9158, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

foo

\n", + "example": 628, + "start_line": 9163, + "end_line": 9167, + "section": "Raw HTML" + }, + { + "markdown": "foo &<]]>\n", + "html": "

foo &<]]>

\n", + "example": 629, + "start_line": 9172, + "end_line": 9176, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

foo

\n", + "example": 630, + "start_line": 9182, + "end_line": 9186, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

foo

\n", + "example": 631, + "start_line": 9191, + "end_line": 9195, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

<a href=""">

\n", + "example": 632, + "start_line": 9198, + "end_line": 9202, + "section": "Raw HTML" + }, + { + "markdown": "foo \nbaz\n", + "html": "

foo
\nbaz

\n", + "example": 633, + "start_line": 9212, + "end_line": 9218, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\nbaz\n", + "html": "

foo
\nbaz

\n", + "example": 634, + "start_line": 9224, + "end_line": 9230, + "section": "Hard line breaks" + }, + { + "markdown": "foo \nbaz\n", + "html": "

foo
\nbaz

\n", + "example": 635, + "start_line": 9235, + "end_line": 9241, + "section": "Hard line breaks" + }, + { + "markdown": "foo \n bar\n", + "html": "

foo
\nbar

\n", + "example": 636, + "start_line": 9246, + "end_line": 9252, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\n bar\n", + "html": "

foo
\nbar

\n", + "example": 637, + "start_line": 9255, + "end_line": 9261, + "section": "Hard line breaks" + }, + { + "markdown": "*foo \nbar*\n", + "html": "

foo
\nbar

\n", + "example": 638, + "start_line": 9267, + "end_line": 9273, + "section": "Hard line breaks" + }, + { + "markdown": "*foo\\\nbar*\n", + "html": "

foo
\nbar

\n", + "example": 639, + "start_line": 9276, + "end_line": 9282, + "section": "Hard line breaks" + }, + { + "markdown": "`code \nspan`\n", + "html": "

code span

\n", + "example": 640, + "start_line": 9287, + "end_line": 9292, + "section": "Hard line breaks" + }, + { + "markdown": "`code\\\nspan`\n", + "html": "

code\\ span

\n", + "example": 641, + "start_line": 9295, + "end_line": 9300, + "section": "Hard line breaks" + }, + { + "markdown": "
\n", + "html": "

\n", + "example": 642, + "start_line": 9305, + "end_line": 9311, + "section": "Hard line breaks" + }, + { + "markdown": "\n", + "html": "

\n", + "example": 643, + "start_line": 9314, + "end_line": 9320, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\n", + "html": "

foo\\

\n", + "example": 644, + "start_line": 9327, + "end_line": 9331, + "section": "Hard line breaks" + }, + { + "markdown": "foo \n", + "html": "

foo

\n", + "example": 645, + "start_line": 9334, + "end_line": 9338, + "section": "Hard line breaks" + }, + { + "markdown": "### foo\\\n", + "html": "

foo\\

\n", + "example": 646, + "start_line": 9341, + "end_line": 9345, + "section": "Hard line breaks" + }, + { + "markdown": "### foo \n", + "html": "

foo

\n", + "example": 647, + "start_line": 9348, + "end_line": 9352, + "section": "Hard line breaks" + }, + { + "markdown": "foo\nbaz\n", + "html": "

foo\nbaz

\n", + "example": 648, + "start_line": 9363, + "end_line": 9369, + "section": "Soft line breaks" + }, + { + "markdown": "foo \n baz\n", + "html": "

foo\nbaz

\n", + "example": 649, + "start_line": 9375, + "end_line": 9381, + "section": "Soft line breaks" + }, + { + "markdown": "hello $.;'there\n", + "html": "

hello $.;'there

\n", + "example": 650, + "start_line": 9395, + "end_line": 9399, + "section": "Textual content" + }, + { + "markdown": "Foo χρῆν\n", + "html": "

Foo χρῆν

\n", + "example": 651, + "start_line": 9402, + "end_line": 9406, + "section": "Textual content" + }, + { + "markdown": "Multiple spaces\n", + "html": "

Multiple spaces

\n", + "example": 652, + "start_line": 9411, + "end_line": 9415, + "section": "Textual content" + } +] \ No newline at end of file diff --git a/tests/test_commonmark_corpus.py b/tests/test_commonmark_corpus.py new file mode 100644 index 0000000..ff9dbc9 --- /dev/null +++ b/tests/test_commonmark_corpus.py @@ -0,0 +1,54 @@ +"""Official corpus and independently specified adapter projections.""" +from pathlib import Path +import importlib.util +import json +import unittest + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location('corpus_validator', ROOT/'scripts/validate_phase0.py') +assert spec and spec.loader +v = importlib.util.module_from_spec(spec) +spec.loader.exec_module(v) +CORPUS = json.loads((ROOT/'tests/fixtures/commonmark-0.31.2-spec.json').read_text(encoding='utf-8')) + + +class CommonMarkCorpusTests(unittest.TestCase): + def test_official_html_semantics(self): + for case in CORPUS: + with self.subTest(example=case['example'], section=case['section']): + actual = v.PARSER.render(case['markdown']) + # An empty quote has no text: this newline is renderer formatting. + actual = actual.replace('
', '
\n
') + self.assertEqual(case['html'], actual) + + def test_adapter_against_explicit_official_example_projections(self): + # Expected headings/prose are hand checked against the supplied HTML, + # with code/HTML/metadata exclusions and Setext policy applied. + expected = { + 19: ((), ''), 23: ((), 'foo'), 24: ((), ''), 35: ((), ''), + 42: ((), '`one\ntwo`'), 49: ((), 'Foo ***'), 51: ((), ''), + 62: (('foo',), '\n'.join(['foo']*6)), 63: ((), '####### foo'), + 65: ((), '## foo'), 69: ((), ''), 70: ((), 'foo # bar'), + 84: ((), ''), 126: ((), ''), 164: ((), ''), 170: ((), 'okay'), + 171: ((), ''), 189: ((), ''), 204: ((), 'foo'), 205: ((), 'Foo'), + 206: ((), 'αγω'), 211: ((), '[foo]'), 218: ((), 'foo'), + 296: ((), 'foo\nbar'), 328: ((), ''), 329: ((), ''), + 330: ((), ''), 332: ((), ''), 344: ((), '`'), + 346: ((), 'https://foo.bar.`baz`'), 348: ((), '`foo'), + 354: ((), '*$*alpha.\n*£*bravo.\n*€*charlie.'), + 356: ((), '5678'), 360: ((), 'foo_bar_'), + 481: ((), '__ahttps://foo.bar/?q=__'), 482: ((), 'link'), + 510: ((), 'link'), 511: ((), '[link] (/uri)'), + 539: ((), 'foo'), 540: ((), 'ẞ'), + } + for case in CORPUS: + if case['example'] in expected: + with self.subTest(example=case['example']): + headings, prose = expected[case['example']] + doc = v.parse_document(case['markdown']) + self.assertEqual(headings, tuple(h.title for h in doc.headings)) + self.assertEqual(prose, '\n'.join(p.text for p in doc.prose)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_markdown_contract.py b/tests/test_markdown_contract.py new file mode 100644 index 0000000..59e2ff9 --- /dev/null +++ b/tests/test_markdown_contract.py @@ -0,0 +1,169 @@ +"""Behavioral policy, recurring review failures, CLI and performance checks.""" +from pathlib import Path +import importlib.util +import shutil +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location('contract_validator', ROOT / 'scripts/validate_phase0.py') +assert spec and spec.loader +v = importlib.util.module_from_spec(spec) +spec.loader.exec_module(v) +PHRASE = 'machine-checkable Phase 0 validator' +ERROR = 'roadmap does not require Phase 0 validator' +HEADING = 'I14 — Contract changes are explicit' + + +def texts(): + return {p: v.read_text(p) for p in v.REQUIRED_FILES} + + +def roadmap_errors(fragment): + contract = texts() + contract['ROADMAP.md'] = contract['ROADMAP.md'].replace(PHRASE, 'removed') + '\n\n' + fragment + return v.validate_texts(contract) + + +class MarkdownContractTests(unittest.TestCase): + def test_current_review_comment_container_exit(self): + fragment = f'[visible][{PHRASE}]\n\n> ')) + self.assertIn(ERROR, roadmap_errors(f'> paragraph ')) + + def test_excluded_content_cannot_satisfy_or_manufacture_phrase(self): + hidden = [f'`{PHRASE}`', f' {PHRASE}', f'~~~\n{PHRASE}\n~~~', + f'![{PHRASE}](/url)', f'', + f'
\n{PHRASE}\n
', f'[visible](/url "{PHRASE}")', + f'[hidden]: /url "{PHRASE}"', + 'machine`code`-checkable Phase 0 validator', + 'machine![alt](/url)-checkable Phase 0 validator', + 'machine-checkable Phase 0 validator', + 'machine-checkable Phase 0 validator'] + for source in hidden: + with self.subTest(source=source): + self.assertIn(ERROR, roadmap_errors(source)) + + def test_text_blocks_never_join(self): + for source in ['machine-checkable Phase 0\n\nvalidator', + '- machine-checkable Phase 0\n- validator', + '## machine-checkable Phase 0\n## validator', + '> machine-checkable Phase 0\n\nvalidator', + 'machine-checkable Phase 0\n\n> validator']: + with self.subTest(source=source): + self.assertIn(ERROR, roadmap_errors(source)) + self.assertEqual([], roadmap_errors('machine-checkable Phase 0\nvalidator')) + + def test_heading_policy_accepts_decoded_atx_and_excludes_setext(self): + self.assertEqual((HEADING,), v.markdown_level2_headings( + '## **I14** — Contract changes are explicit')) + self.assertEqual((), v.markdown_level2_headings(HEADING + '\n---')) + self.assertIn(ERROR, roadmap_errors(PHRASE + '\n---')) + for document, title, error in [('INVARIANTS.md',HEADING,'invariant headings'), + ('HYPOTHESES.md','H1 — Specification Primacy','hypothesis headings')]: + contract = texts() + contract[document] += f'\n- ~~~\n- ## {title}\n' + self.assertTrue(any(error in e for e in v.validate_texts(contract))) + + def test_generated_container_ownership_matrix(self): + # Each marker starts a sibling item; indentation retains the same item. + # These expected values follow the container rule, not parser output. + for quote in ('', '> ', '> > '): + for marker in ('- ', '+ ', '* ', '1. ', '2. ', '-\t', '1.\t'): + for opener in ('~~~', '```', '
', '
', '\n"
+        errors = module.validate_texts(texts)
+        self.assertTrue(
+            any("invariant headings must exactly match" in error for error in errors),
+            errors,
+        )
+
+    def test_multiline_html_comment_cannot_hide_invariant_heading(self) -> None:
+        texts = contract_texts()
+        texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace(
+            "## I14 — Contract changes are explicit",
+            "## Contract changes are explicit",
+            1,
+        )
+        texts["INVARIANTS.md"] += (
+            "\n\n"
+        )
+        errors = module.validate_texts(texts)
+        self.assertTrue(
+            any("invariant headings must exactly match" in error for error in errors),
+            errors,
+        )
+
+    def test_raw_html_pre_block_cannot_hide_invariant_heading(self) -> None:
+        texts = contract_texts()
+        texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace(
+            "## I14 — Contract changes are explicit",
+            "## Contract changes are explicit",
+            1,
+        )
+        texts["INVARIANTS.md"] += (
+            "\n
\n"
+            "## I14 — Contract changes are explicit\n"
+            "
\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_raw_html_div_block_ignores_level2_heading(self) -> None: + texts = contract_texts() + texts["TERMINOLOGY.md"] += ( + "\n
\n" + "## Example subsection\n" + "
\n" + "\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_tab_delimited_pre_block_cannot_hide_invariant_heading(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n\n" + "## I14 — Contract changes are explicit\n" + "
\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_generic_html_tag_does_not_interrupt_paragraph(self) -> None: + texts = contract_texts() + texts["TERMINOLOGY.md"] = texts["TERMINOLOGY.md"].replace( + "\n\n## Machine Verifiability", + "\n\n## Machine Verifiability", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_comment_marker_inside_code_span_is_literal(self) -> None: + texts = contract_texts() + texts["TERMINOLOGY.md"] = ( + "Use the literal marker `\n" + ) + # This is a visible ATX heading in the pinned CommonMark dialect. + self.assertEqual([], module.validate_texts(texts)) + + def test_nested_fence_dedent_reprocesses_top_level_opener(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + fence = chr(96) * 3 + texts["INVARIANTS.md"] += ( + f"\n- {fence}markdown\n" + " sample\n" + f"{fence}\n" + "## I14 — Contract changes are explicit\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_ordered_list_start_two_does_not_interrupt_paragraph(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\nparagraph\n" + "2. ~~~markdown\n" + " ## I14 — Contract changes are explicit\n" + " ~~~\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_tab_expanded_list_fence_uses_visual_columns(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n1.\t~~~markdown\n" + " ## I14 — Contract changes are explicit\n" + " ~~~\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_link_reference_metadata_does_not_satisfy_prose_checks(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[hidden]: / "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + texts = contract_texts() + texts["README.md"] = texts["README.md"].replace( + "The thesis is **not treated as established fact**", + "The thesis remains a research proposition", + 1, + ) + texts["README.md"] += ( + '\n[hidden]: / "The thesis is **not treated as established fact**"\n' + ) + errors = module.validate_texts(texts) + self.assertIn( + "README must explicitly separate thesis from established fact", + errors, + ) + + def test_multiline_reference_destination_is_hidden_metadata(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[hidden]:\n" + ' /url "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_reference_looking_line_inside_paragraph_remains_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '[note]: / "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_filtered_fence_preserves_reference_block_boundary(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + fence = chr(96) * 3 + texts["ROADMAP.md"] += ( + "\nordinary paragraph\n" + f"{fence}\n" + "code\n" + f"{fence}\n" + '[hidden]: /url "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_balanced_parenthesis_reference_destination_is_hidden(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[hidden]: (url) "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_multiline_reference_destination_stops_at_block_start(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[hidden]:\n" + "# machine-checkable Phase 0 validator\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_reference_label_uses_first_unescaped_closing_bracket(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[foo\\]: None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[hidden]:\n" + " /url\n" + '"machine-checkable Phase 0 validator\\\"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_backslash_space_does_not_escape_reference_destination(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[hidden]: /foo\\ bar "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_unescaped_opening_bracket_invalidates_reference_label(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[foo[bar]: /url "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_inline_reference_title_requires_whitespace_after_destination(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[hidden]: "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_multiline_reference_title_is_hidden_metadata(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[hidden]: /url\n" + '"machine-checkable Phase 0 validator\n' + 'continued"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_ordered_list_stops_multiline_reference_destination(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[hidden]:\n" + '1. "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_type7_angle_tag_can_be_multiline_reference_destination(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[hidden]:\n" + "\n" + '"machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_inline_multiline_reference_title_is_hidden(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[hidden]: /url "machine-checkable Phase 0 validator\n' + 'continued"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_first_unescaped_closing_bracket_invalidates_reference_label(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[foo]bar]: /url "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_blank_ordered_list_item_does_not_interrupt_paragraph(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\nparagraph\n" + "1. \n" + "\n" + "## I14 — Contract changes are explicit\n\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_indented_code_does_not_satisfy_required_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n machine-checkable Phase 0 validator\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_generic_closing_tag_with_attributes_is_not_raw_html(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n\n" + "## I14 — Contract changes are explicit\n\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_standalone_equals_marker_starts_paragraph(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n=\n" + "\n" + "## I14 — Contract changes are explicit\n\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_generic_html_tag_allows_gt_inside_quoted_attribute(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + '\n\n' + "## I14 — Contract changes are explicit\n\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_indented_list_continuation_counts_as_rendered_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- item\n" + " machine-checkable Phase 0 validator\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_nbsp_is_list_item_content_not_blank_whitespace(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\nparagraph\n" + "1. \u00a0\n" + "\n" + "## I14 — Contract changes are explicit\n\n" + ) + # This is a visible ATX heading in the pinned CommonMark dialect. + self.assertEqual([], module.validate_texts(texts)) + + def test_nbsp_after_multiline_reference_title_invalidates_definition(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[hidden]: /url "machine-checkable Phase 0 validator\n' + 'continued"\u00a0\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_nested_list_indented_code_does_not_count_as_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- # item\n" + " machine-checkable Phase 0 validator\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_newline_padding_does_not_satisfy_artifact_size(self) -> None: + texts = contract_texts() + texts["METHODOLOGY.md"] = "x" + "\n" * 79 + errors = module.validate_texts(texts) + self.assertIn( + "required artifact is suspiciously small: METHODOLOGY.md", + errors, + ) + + def test_nbsp_preserves_list_continuation_state(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- item\n" + " \u00a0\n" + " machine-checkable Phase 0 validator\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_nested_list_paragraph_continuation_uses_innermost_column(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- - item\n" + " machine-checkable Phase 0 validator\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_list_reference_definition_does_not_open_paragraph(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- [foo]: /foo\n" + '[hidden]: /url "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_multiline_reference_definition_inside_list_is_hidden(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- [hidden]:\n" + ' /url "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_dedented_lazy_continuation_preserves_list_paragraph(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- item\n" + '[hidden]: /url "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_thematic_break_is_not_nested_list_chain(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- - -\n" + " machine-checkable Phase 0 validator\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_nested_ordered_list_cannot_interrupt_open_list_paragraph(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- paragraph\n" + " 2. # heading\n" + " machine-checkable Phase 0 validator\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_raw_html_block_inside_list_hides_heading(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n-
\n" + " ## I14 — Contract changes are explicit\n" + "
\n\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_fenced_code_nested_through_multiple_list_markers_is_hidden(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- - ~~~\n" + " machine-checkable Phase 0 validator\n" + " ~~~\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_level2_heading_inside_list_satisfies_contract(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "- ## I14 — Contract changes are explicit", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_soft_line_break_is_normalized_in_required_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "machine-checkable Phase 0\nvalidator", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_heading_extraction_preserves_paragraph_state(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "paragraph\n2. ## I14 — Contract changes are explicit", + 1, + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_five_space_list_padding_leaves_code_indent(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "- ## I14 — Contract changes are explicit", + 1, + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_list_reference_tab_padding_resolves_definition(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- [hidden]:\n" + "\t /url\n" + ' "machine-checkable Phase 0 validator"\n' + ) + self.assertIn("roadmap does not require Phase 0 validator", + module.validate_texts(texts)) + + def test_list_generic_html_resets_outer_paragraph_state(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\nparagraph\n" + "- \n" + " ## I14 — Contract changes are explicit\n\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_dedent_ends_list_scoped_raw_html(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n-
\n" + "## I14 — Contract changes are explicit\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_tab_marker_padding_keeps_heading_as_code(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "-\t ## I14 — Contract changes are explicit", + 1, + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_blockquote_raw_html_hides_nested_heading(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n>
\n" + "> ## I14 — Contract changes are explicit\n" + ">\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_reference_definition_does_not_open_heading_paragraph(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "[hidden]: /url\n2. ## I14 — Contract changes are explicit", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_nested_tab_padding_keeps_inner_heading_visible(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "- -\t ## I14 — Contract changes are explicit", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_blockquote_fence_hides_heading(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n> ~~~\n" + "> ## I14 — Contract changes are explicit\n" + "> ~~~\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_first_line_list_code_does_not_satisfy_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += "\n- machine-checkable Phase 0 validator\n" + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_whitespace_only_reference_label_is_not_hidden(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[ ]: /url "machine-checkable Phase 0 validator"\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_blockquote_comment_ends_on_dedent(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n> \n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_blockquote_reference_definition_is_hidden_metadata(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n> [hidden]: /url "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_inline_link_title_does_not_satisfy_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n[visible](/url "machine-checkable Phase 0 validator")\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_multiline_reference_definition_resets_heading_state(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "[hidden]:\n/url\n2. ## I14 — Contract changes are explicit", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_raw_html_container_replacement_exposes_heading(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n-
\n" + "> ## I14 — Contract changes are explicit\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_unicode_only_list_paragraph_blocks_nested_ordered_two(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "- \u00a0\n 2. ## I14 — Contract changes are explicit", + 1, + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_lazy_dedent_updates_after_visible_list_continuation(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "- \u00a0\n ordinary paragraph\n2. ## I14 — Contract changes are explicit", + 1, + ) + # This is a visible ATX heading in the pinned CommonMark dialect. + self.assertEqual([], module.validate_texts(texts)) + + def test_quoted_list_dedent_is_measured_inside_quote(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "> - \u00a0\n> 2. ## I14 — Contract changes are explicit", + 1, + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_fresh_blockquote_resets_outer_paragraph_for_ordered_two(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "paragraph\n> 2. ## I14 — Contract changes are explicit", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_repeated_ordered_two_cannot_interrupt_open_list_paragraph(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "- \u00a0\n" + " 2. ## I14 — Contract changes are explicit\n" + " 2. ## I14 — Contract changes are explicit", + 1, + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_nested_quote_ends_list_paragraph_before_reference_definition(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n> - item\n' + '> > [hidden]: /url "machine-checkable Phase 0 validator"\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_lazy_quoted_list_dedent_retains_quote_owner(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "> - item\n> 2. ## I14 — Contract changes are explicit", + 1, + ) + # This is a visible ATX heading in the pinned CommonMark dialect. + self.assertEqual([], module.validate_texts(texts)) + + def test_mixed_quote_list_fence_hides_heading(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += ( + "\n> - ~~~\n" + "> ## I14 — Contract changes are explicit\n" + "> ~~~\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_quoted_soft_break_satisfies_required_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n> machine-checkable Phase 0\n" + "> validator\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_unresolved_reference_syntax_remains_visible_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[visible][machine-checkable Phase 0 validator]\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_quoted_indented_code_does_not_satisfy_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n> machine-checkable Phase 0 validator\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_inline_html_attribute_does_not_satisfy_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\nvisible ' + 'text\n' + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_sibling_list_item_ends_nested_fence(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "- - ~~~\n- ## I14 — Contract changes are explicit", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_reference_definition_after_fence_resolves_inline_reference(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[visible][machine-checkable Phase 0 validator]\n\n" + "```\ncode\n```\n" + "[machine-checkable Phase 0 validator]: /url\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_lazy_blockquote_soft_break_satisfies_required_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n> machine-checkable Phase 0\n" + "validator\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_escaped_inline_html_remains_visible_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + '\n\\\n' + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_list_interrupt_keeps_required_words_in_separate_blocks(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\nmachine-checkable Phase 0\n" + "- validator\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_resolved_reference_with_escaped_bracket_hides_label_metadata(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[visible][machine-checkable Phase 0 validator\\]]\n\n" + "[machine-checkable Phase 0 validator\\]]: /url\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_quoted_indented_code_after_paragraph_does_not_satisfy_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\nordinary paragraph\n" + "> machine-checkable Phase 0 validator\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_unquoted_blank_ends_quote_scoped_fence_for_reference_definition(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n[visible][machine-checkable Phase 0 validator]\n\n" + "> ```\n" + "\n" + "> [machine-checkable Phase 0 validator]: /url\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_lazy_quote_ordered_two_keeps_marker_literal(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n> machine-checkable Phase 0\n" + "2. validator\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_unquoted_blank_ends_quote_scoped_raw_html(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + ">
\n\n"
+            "> ## I14 — Contract changes are explicit",
+            1,
+        )
+        self.assertEqual([], module.validate_texts(texts))
+
+    def test_unquoted_blank_ends_quote_scoped_html_comment(self) -> None:
+        texts = contract_texts()
+        texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace(
+            "## I14 — Contract changes are explicit",
+            "> \n"
+        )
+        errors = module.validate_texts(texts)
+        self.assertIn("roadmap does not require Phase 0 validator", errors)
+
+    def test_reference_after_pre_closer_resolves(self) -> None:
+        required = "machine-checkable Phase 0 validator"
+        texts = contract_texts()
+        texts["ROADMAP.md"] = texts["ROADMAP.md"].replace(
+            required,
+            f"[visible][{required}]",
+            1,
+        )
+        texts["ROADMAP.md"] += (
+            "\n
\nstuff\n
\n" + f"[{required}]: /url\n" + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_same_shape_sibling_duplicate_invariant_is_rejected(self) -> None: + texts = contract_texts() + texts["INVARIANTS.md"] += ( + "\n- ~~~\n" + "- ## I14 — Contract changes are explicit\n" + ) + errors = module.validate_texts(texts) + self.assertTrue( + any("invariant headings must exactly match" in error for error in errors), + errors, + ) + + def test_backslash_before_code_closer_cannot_satisfy_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "`machine-checkable Phase 0 validator\\`", + 1, + ) + errors = module.validate_texts(texts) + self.assertIn("roadmap does not require Phase 0 validator", errors) + + def test_malformed_link_literal_can_satisfy_required_prose(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "[visible](machine-checkable Phase 0 validator)", + 1, + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_equivalent_emphasis_and_entity_prose_is_accepted(self) -> None: + required = "machine-checkable Phase 0 validator" + + entity_texts = contract_texts() + entity_texts["ROADMAP.md"] = entity_texts["ROADMAP.md"].replace( + required, + "machine-checkable Phase 0 validator", + 1, + ) + self.assertEqual([], module.validate_texts(entity_texts)) + + emphasis_texts = contract_texts() + emphasis_texts["ROADMAP.md"] = emphasis_texts["ROADMAP.md"].replace( + required, + "**machine-checkable** Phase 0 validator", + 1, + ) + self.assertEqual([], module.validate_texts(emphasis_texts)) + + readme_texts = contract_texts() + readme_texts["README.md"] = readme_texts["README.md"].replace( + "**not treated as established fact**", + "__not treated as established fact__", + 1, + ) + self.assertEqual([], module.validate_texts(readme_texts)) + + def test_hidden_pre_reference_definition_does_not_resolve(self) -> None: + required = "machine-checkable Phase 0 validator" + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + required, + f"[visible][{required}]", + 1, + ) + texts["ROADMAP.md"] += ( + "\n
\n\n"
+            f"[{required}]: /url\n"
+            "
\n" + ) + self.assertEqual([], module.validate_texts(texts)) + + def test_roadmap_heading_in_fence_does_not_satisfy_contract(self) -> None: + texts = contract_texts() + required = "## Phase 0 — Foundational Research Contract" + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + required, + "## Phase Zero", + 1, + ) + fence = chr(96) * 3 + texts["ROADMAP.md"] += f"\n{fence}markdown\n{required}\n{fence}\n" + errors = module.validate_texts(texts) + self.assertIn( + "roadmap does not define exactly one Phase 0 foundational contract", + errors, + ) + + def test_all_documented_terminology_headings_are_required(self) -> None: + terminology = module.read_text("TERMINOLOGY.md") + self.assertEqual(module.TERMS, module.terminology_headings(terminology)) + + def test_required_files_are_repo_relative(self) -> None: + for relative in module.REQUIRED_FILES: + path = Path(relative) + self.assertFalse(path.is_absolute()) + self.assertNotIn("..", path.parts) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_phase0_markdown_regressions.py b/tests/test_phase0_markdown_regressions.py new file mode 100644 index 0000000..c476b6b --- /dev/null +++ b/tests/test_phase0_markdown_regressions.py @@ -0,0 +1,672 @@ +from pathlib import Path +import importlib.util +import unittest + +ROOT = Path(__file__).resolve().parents[1] +VALIDATOR = ROOT / "scripts" / "validate_phase0.py" + +spec = importlib.util.spec_from_file_location("validate_phase0_regressions", VALIDATOR) +assert spec is not None and spec.loader is not None +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + + +def contract_texts() -> dict[str, str]: + return {relative: module.read_text(relative) for relative in module.REQUIRED_FILES} + + +class MarkdownParserRegressionMatrix(unittest.TestCase): + def test_heading_container_matrix(self) -> None: + heading = "I14 — Contract changes are explicit" + cases = ( + ("top-level", f"## {heading}", True), + ("bullet", f"- ## {heading}", True), + ("blockquote", f"> ## {heading}", True), + ("ordered-one-interrupts", f"paragraph\n1. ## {heading}", True), + ("ordered-two-does-not-interrupt", f"paragraph\n2. ## {heading}", False), + ("four-space-padding", f"- ## {heading}", True), + ("five-space-padding-is-code", f"- ## {heading}", False), + ("tab-plus-two-spaces-is-code", f"-\t ## {heading}", False), + ) + + for name, markdown, expected in cases: + with self.subTest(name=name): + headings = module.markdown_level2_headings(markdown) + self.assertEqual(expected, heading in headings) + + def test_tab_overshoot_is_preserved(self) -> None: + self.assertEqual((), module.markdown_level2_headings( + "-\n\t ## I14 — Contract changes are explicit" + )) + + def test_list_scoped_html_matrix(self) -> None: + cases = ( + ( + "generic-html-after-interrupting-bullet", + "paragraph\n- \n ## I14 — Contract changes are explicit\n\n", + True, + ), + ( + "dedent-ends-list-html", + "-
\n## I14 — Contract changes are explicit", + False, + ), + ( + "indented-heading-stays-in-list-html", + "-
\n ## I14 — Contract changes are explicit\n\n", + True, + ), + ) + + for name, suffix, should_hide in cases: + with self.subTest(name=name): + texts = contract_texts() + texts["INVARIANTS.md"] = texts["INVARIANTS.md"].replace( + "## I14 — Contract changes are explicit", + "## Contract changes are explicit", + 1, + ) + texts["INVARIANTS.md"] += "\n" + suffix + errors = module.validate_texts(texts) + has_invariant_error = any( + "invariant headings must exactly match" in error + for error in errors + ) + self.assertEqual(should_hide, has_invariant_error) + + def test_blockquote_scoped_html_stays_active(self) -> None: + markdown = ( + ">
\n" + "> ## I14 — Contract changes are explicit\n" + ">\n" + ) + self.assertEqual((), module.markdown_level2_headings(markdown)) + + def test_reference_definition_closes_paragraph_state(self) -> None: + markdown = ( + "[hidden]: /url\n" + "2. ## I14 — Contract changes are explicit\n" + ) + self.assertIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_tab_marker_padding_preserves_visual_indent(self) -> None: + self.assertEqual((), module.markdown_level2_headings( + "-\t ## I14 — Contract changes are explicit" + )) + + def test_nested_tab_padding_uses_outer_visual_column(self) -> None: + heading = "I14 — Contract changes are explicit" + markdown = f"- -\t ## {heading}" + self.assertIn(heading, module.markdown_level2_headings(markdown)) + + def test_blockquote_fence_stays_active(self) -> None: + markdown = ( + "> ~~~\n" + "> ## I14 — Contract changes are explicit\n" + "> ~~~\n" + ) + self.assertEqual((), module.markdown_level2_headings(markdown)) + + def test_first_line_list_code_not_rendered_prose(self) -> None: + prose = module.markdown_rendered_prose_text( + "- machine-checkable Phase 0 validator" + ) + self.assertNotIn("machine-checkable Phase 0 validator", prose) + + def test_whitespace_only_reference_label_is_visible(self) -> None: + prose = module.markdown_rendered_prose_text( + '[ ]: /url "machine-checkable Phase 0 validator"' + ) + self.assertIn("machine-checkable Phase 0 validator", prose) + + def test_blockquote_comment_ends_on_container_exit(self) -> None: + markdown = ( + "> \n" + ) + self.assertIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_blockquote_reference_definition_is_hidden(self) -> None: + prose = module.markdown_rendered_prose_text( + '> [hidden]: /url "machine-checkable Phase 0 validator"' + ) + self.assertNotIn("machine-checkable Phase 0 validator", prose) + + def test_inline_link_title_is_not_rendered_prose(self) -> None: + prose = module.markdown_rendered_prose_text( + '[visible](/url "machine-checkable Phase 0 validator")' + ) + self.assertEqual("visible", prose.strip()) + + def test_multiline_reference_definition_resets_state(self) -> None: + markdown = ( + "[hidden]:\n" + "/url\n" + "2. ## I14 — Contract changes are explicit\n" + ) + self.assertIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_raw_html_container_identity_rejects_replacement(self) -> None: + markdown = ( + "-
\n" + "> ## I14 — Contract changes are explicit\n" + ) + self.assertIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_unicode_only_list_paragraph_blocks_nested_ordered_two(self) -> None: + markdown = ( + "- \u00a0\n" + " 2. ## I14 — Contract changes are explicit\n" + ) + self.assertNotIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_lazy_dedent_eligibility_updates_after_visible_continuation(self) -> None: + markdown = ( + "- \u00a0\n" + " ordinary paragraph\n" + "2. ## I14 — Contract changes are explicit\n" + ) + self.assertIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_quoted_list_dedent_uses_container_relative_columns(self) -> None: + markdown = ( + "> - \u00a0\n" + "> 2. ## I14 — Contract changes are explicit\n" + ) + self.assertNotIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_paragraph_container_transition_matrix(self) -> None: + heading = "I14 — Contract changes are explicit" + cases = ( + ( + "fresh-quote-resets-outer-paragraph", + f"paragraph\n> 2. ## {heading}\n", + True, + ), + ( + "same-list-paragraph-rejects-first-two", + f"- \u00a0\n 2. ## {heading}\n", + False, + ), + ( + "same-list-paragraph-rejects-repeated-two", + f"- \u00a0\n 2. ## {heading}\n 2. ## {heading}\n", + False, + ), + ( + "quoted-list-paragraph-rejects-two", + f"> - \u00a0\n> 2. ## {heading}\n", + False, + ), + ( + "nested-quote-is-fresh-container", + f"> - item\n> > 2. ## {heading}\n", + True, + ), + ) + + for name, markdown, expected_visible in cases: + with self.subTest(name=name): + headings = module.markdown_level2_headings(markdown) + self.assertEqual(expected_visible, heading in headings) + + def test_nested_quote_ends_list_paragraph_before_reference(self) -> None: + prose = module.markdown_rendered_prose_text( + '> - item\n> > [hidden]: /url "machine-checkable Phase 0 validator"' + ) + self.assertNotIn("machine-checkable Phase 0 validator", prose) + + def test_lazy_quoted_paragraph_retains_quote_owner_after_dedent(self) -> None: + markdown = ( + "> - item\n" + "> 2. ## I14 — Contract changes are explicit\n" + ) + self.assertIn( + "I14 — Contract changes are explicit", + module.markdown_level2_headings(markdown), + ) + + def test_mixed_quote_list_fence_continues_by_indentation(self) -> None: + markdown = ( + "> - ~~~\n" + "> ## I14 — Contract changes are explicit\n" + "> ~~~\n" + ) + self.assertEqual((), module.markdown_level2_headings(markdown)) + + def test_quoted_soft_break_strips_container_markers(self) -> None: + prose = module.markdown_rendered_prose_text( + "> machine-checkable Phase 0\n" + "> validator" + ) + self.assertIn( + "machine-checkable Phase 0 validator", + prose, + ) + + def test_reference_rendering_resolved_vs_unresolved(self) -> None: + required = "machine-checkable Phase 0 validator" + + unresolved = module.markdown_rendered_prose_text( + f"[visible][{required}]" + ) + self.assertIn(required, unresolved) + + resolved = module.markdown_rendered_prose_text( + f"[visible][{required}]\n\n" + f"[{required}]: /url" + ) + self.assertNotIn(required, resolved) + + def test_quoted_indented_code_is_not_rendered_prose(self) -> None: + prose = module.markdown_rendered_prose_text( + "> machine-checkable Phase 0 validator" + ) + self.assertNotIn( + "machine-checkable Phase 0 validator", + prose, + ) + + def test_inline_html_attributes_are_not_rendered_prose(self) -> None: + prose = module.markdown_rendered_prose_text( + 'visible ' + 'text' + ) + self.assertEqual("visible\ntext", prose.strip()) + + def test_nested_fence_sibling_and_indented_continuation(self) -> None: + heading = "I14 — Contract changes are explicit" + + sibling = ( + "- - ~~~\n" + f"- ## {heading}\n" + ) + self.assertIn( + heading, + module.markdown_level2_headings(sibling), + ) + + continuation = ( + "> - ~~~\n" + f"> ## {heading}\n" + "> ~~~\n" + ) + self.assertNotIn( + heading, + module.markdown_level2_headings(continuation), + ) + + def test_reference_definition_after_fence_resolves_link(self) -> None: + required = "machine-checkable Phase 0 validator" + prose = module.markdown_rendered_prose_text( + f"[visible][{required}]\n\n" + "```\ncode\n```\n" + f"[{required}]: /url" + ) + self.assertNotIn(required, prose) + + def test_blockquote_soft_break_lazy_and_explicit_forms(self) -> None: + required = "machine-checkable Phase 0 validator" + + explicit = module.markdown_rendered_prose_text( + "> machine-checkable Phase 0\n> validator" + ) + lazy = module.markdown_rendered_prose_text( + "> machine-checkable Phase 0\nvalidator" + ) + self.assertIn(required, explicit) + self.assertIn(required, lazy) + + def test_escaped_inline_html_remains_literal(self) -> None: + required = "machine-checkable Phase 0 validator" + escaped = module.markdown_rendered_prose_text( + '\\' + ) + unescaped = module.markdown_rendered_prose_text( + 'text' + ) + self.assertIn(required, escaped) + self.assertNotIn(required, unescaped) + + def test_list_boundary_prevents_soft_break_join(self) -> None: + prose = module.markdown_rendered_prose_text( + "machine-checkable Phase 0\n- validator" + ) + self.assertNotIn( + "machine-checkable Phase 0 validator", + prose, + ) + + def test_full_reference_escaped_bracket_label(self) -> None: + required = "machine-checkable Phase 0 validator" + label = required + "\\]" + prose = module.markdown_rendered_prose_text( + f"[visible][{label}]\n\n" + f"[{label}]: /url" + ) + self.assertNotIn(required, prose) + + def test_quoted_code_after_open_paragraph_is_still_code(self) -> None: + prose = module.markdown_rendered_prose_text( + "ordinary paragraph\n" + "> machine-checkable Phase 0 validator" + ) + self.assertNotIn( + "machine-checkable Phase 0 validator", + prose, + ) + + def test_quote_scoped_fence_blank_line_boundary_matrix(self) -> None: + required = "machine-checkable Phase 0 validator" + + unquoted_blank = module.markdown_rendered_prose_text( + f"[visible][{required}]\n\n" + "> ```\n" + "\n" + f"> [{required}]: /url" + ) + self.assertNotIn(required, unquoted_blank) + + quoted_blank = module.markdown_rendered_prose_text( + f"[visible][{required}]\n\n" + "> ```\n" + ">\n" + f"> [{required}]: /url" + ) + self.assertIn(required, quoted_blank) + + def test_lazy_quote_ordered_marker_matrix(self) -> None: + required = "machine-checkable Phase 0 validator" + + plain = module.markdown_rendered_prose_text( + "> machine-checkable Phase 0\n" + "validator" + ) + ordered_two = module.markdown_rendered_prose_text( + "> machine-checkable Phase 0\n" + "2. validator" + ) + ordered_one = module.markdown_rendered_prose_text( + "> machine-checkable Phase 0\n" + "1. validator" + ) + + self.assertIn(required, plain) + self.assertNotIn(required, ordered_two) + self.assertEqual("machine-checkable Phase 0\nvalidator", ordered_two) + self.assertNotIn(required, ordered_one) + self.assertNotIn("1. validator", ordered_one) + + def test_quote_scoped_html_blank_boundary_matrix(self) -> None: + heading = "I14 — Contract changes are explicit" + + raw_html_unquoted_blank = ( + ">
\n"
+            "\n"
+            f"> ## {heading}\n"
+        )
+        self.assertIn(
+            heading,
+            module.markdown_level2_headings(
+                raw_html_unquoted_blank
+            ),
+        )
+
+        raw_html_quoted_blank = (
+            "> 
\n"
+            ">\n"
+            f"> ## {heading}\n"
+        )
+        self.assertNotIn(
+            heading,
+            module.markdown_level2_headings(
+                raw_html_quoted_blank
+            ),
+        )
+
+        comment_unquoted_blank = (
+            "> "
+        )
+        self.assertNotIn(
+            "machine-checkable Phase 0 validator",
+            prose,
+        )
+
+    def test_reference_definition_after_html_closer_resolves(self) -> None:
+        required = "machine-checkable Phase 0 validator"
+        prose = module.markdown_rendered_prose_text(
+            f"[visible][{required}]\n\n"
+            "
\nstuff\n
\n" + f"[{required}]: /url" + ) + self.assertNotIn(required, prose) + + def test_same_shape_sibling_blocks_do_not_own_each_other(self) -> None: + heading = "I14 — Contract changes are explicit" + for opener in ("~~~", "
", ""), + ): + with self.subTest(opener=opener): + prose = module.markdown_rendered_prose_text( + f"[visible][{required}]\n\n" + f"{opener}\n\n" + f"[{required}]: /url\n" + f"{closer}\n" + ) + self.assertIn(required, prose) + + def test_list_reference_tab_padding_is_hidden(self) -> None: + texts = contract_texts() + texts["ROADMAP.md"] = texts["ROADMAP.md"].replace( + "machine-checkable Phase 0 validator", + "Phase 0 validator", + 1, + ) + texts["ROADMAP.md"] += ( + "\n- [hidden]:\n" + "\t /url\n" + ' "machine-checkable Phase 0 validator"\n' + ) + self.assertIn("roadmap does not require Phase 0 validator", + module.validate_texts(texts)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_review_findings.py b/tests/test_review_findings.py new file mode 100644 index 0000000..93e3b49 --- /dev/null +++ b/tests/test_review_findings.py @@ -0,0 +1,108 @@ +"""Behavioral regressions and controls from the f5b9b2f review.""" + +from pathlib import Path +import importlib.util +import unittest + +REPO = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location( + "reviewed_validator", REPO / "scripts/validate_phase0.py" +) +assert spec and spec.loader +validator = importlib.util.module_from_spec(spec) +spec.loader.exec_module(validator) + +PHRASE = "machine-checkable Phase 0 validator" +HEADING = "I14 — Contract changes are explicit" +ROADMAP_ERROR = "roadmap does not require Phase 0 validator" +CODE_WITH_BACKSLASH = "`" + PHRASE + "\\`" +MALFORMED_LINK = "[visible](" + PHRASE + ")" + + +def texts(): + return {name: validator.read_text(name) for name in validator.REQUIRED_FILES} + + +def roadmap_errors(replacement): + contract = texts() + assert contract["ROADMAP.md"].count(PHRASE) == 1 + contract["ROADMAP.md"] = contract["ROADMAP.md"].replace(PHRASE, replacement) + return validator.validate_texts(contract) + + +def ghost_reference(opener, closer): + return ( + f"[visible][{PHRASE}]\n\n{opener}\n\n" + f"[{PHRASE}]: /url\n{closer}\n" + ) + + +class ReviewFindings(unittest.TestCase): + def test_r1_sibling_fence_heading_is_visible(self): + self.assertIn(HEADING, validator.markdown_level2_headings(f"- ~~~\n- ## {HEADING}\n")) + + def test_r1_sibling_html_heading_is_visible(self): + self.assertIn(HEADING, validator.markdown_level2_headings(f"-
\n- ## {HEADING}\n")) + + def test_r1_sibling_comment_heading_is_visible(self): + self.assertIn(HEADING, validator.markdown_level2_headings(f"- "))) + + def test_r5_roadmap_ghost_reference_is_accepted(self): + contract = texts() + contract["ROADMAP.md"] = contract["ROADMAP.md"].replace(PHRASE, f"[visible][{PHRASE}]") + contract["ROADMAP.md"] += f"\n
\n\n[{PHRASE}]: /url\n
\n" + self.assertEqual([], validator.validate_texts(contract)) + + +class ControlCases(unittest.TestCase): + def test_same_list_item_fence_keeps_heading_hidden(self): + self.assertNotIn(HEADING, validator.markdown_level2_headings(f"- ~~~\n ## {HEADING}\n ~~~\n")) + + def test_valid_link_title_stays_hidden(self): + self.assertNotIn(PHRASE, validator.markdown_rendered_prose_text(f'[visible](/url "{PHRASE}")')) + + def test_ordinary_inline_code_stays_hidden(self): + self.assertNotIn(PHRASE, validator.markdown_rendered_prose_text(f"`{PHRASE}`")) + + def test_real_reference_resolves_and_hides_metadata_label(self): + self.assertNotIn(PHRASE, validator.markdown_rendered_prose_text(f"[visible][{PHRASE}]\n\n[{PHRASE}]: /url\n")) + + +if __name__ == "__main__": + unittest.main()