Skip to content

Add batch 1 assess - #3

Merged
mattbriggs merged 3 commits into
mainfrom
mdb_20260627_parsertest1
Jun 28, 2026
Merged

mattbriggs merged 3 commits into
mainfrom
mdb_20260627_parsertest1

Conversation

@mattbriggs

@mattbriggs mattbriggs commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Improved structured Markdown classification for article and unit types (how-to, reference, concept, introduction), with more reliable fallbacks.
    • Added bounded-time schema validation with configurable timeout, and a validation-disable option for pipeline runs.
    • CLI “pipe” now shows interactive progress when run in a terminal; validation now respects time limits.
    • Enhanced schema-aligned structured output for common blocks (alerts, blockquotes, lists, tables) with more consistent markdown text.
  • Bug Fixes

    • Reduced false “how-to” matches when evidence is weak.
    • Schema validation failures now result in degraded readiness rather than fully ready output.
    • Improved handling of preamble content so valid introductory material is classified more accurately.
  • Tests

    • Expanded classifier and schema-contract coverage with new fixtures.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates Markdown article triage to use weighted metadata and unit evidence, aligns component and unit serialization with schema expectations, changes DITA readiness to degrade on schema validation failure, adds pipeline progress reporting and validation timeout wiring, and expands unit, contract, and fixture coverage.

Changes

Classifier, validation, and readiness changes

Layer / File(s) Summary
Scored article triage and unit heuristics
src/structure_parser/structured_markdown/classifier.py
Introduces weighted metadata evidence and article candidate scoring, expands heading and metadata mappings, filters heading-only preamble sections, and updates pre-H2 and code-block unit classification.
Markdown field and schema serialization
src/structure_parser/structured_markdown/attribute_mapper.py, src/structure_parser/structured_markdown/component_mapper.py, src/structure_parser/validation/model_validator.py, src/structure_parser/validation/schema_validator.py, src/structure_parser/resources/model/articles/units/unitIntroduction.schema.json, model/articles/units/unitIntroduction.schema.json
Populates markdown on mapped components and attributes, serializes units with schema-aligned helpers, broadens the unit introduction schema’s allowed content, and adds bounded schema validation plus schema normalization.
Validation, CLI, and readiness flow
src/structure_parser/application/commands.py, src/structure_parser/cli.py, src/structure_parser/contracts/config.py, src/structure_parser/enrichment/semantic_enricher.py, src/structure_parser/pipeline/orchestrator.py, src/structure_parser/readiness/dita.py
Adds validation timeout plumbing, optional model-validation gating, pipeline progress callbacks, CLI progress rendering, and DITA readiness degradation when schema validation fails.
Unit, contract, and fixture coverage
tests/unit/test_structured_markdown_classifier.py, tests/contract/test_unit_schema_contract.py, tests/contract/test_schema_round_trip.py, tests/fixtures/markdown/*
Adds classifier and readiness tests, contract tests for schema compliance, and Markdown fixtures used by the new assertions.

Design and planning documents

Layer / File(s) Summary
Implementation update notes
design/2026-06-27-parse-assess-implementation-update.md, design/2026-06-28-b1-parse-assess-implementation-update.md
Describes the evidence pipeline, scoring model, acceptance criteria, test plan, and implementation sequence for the parser update.
Roadmap and future tasks
design/2026-06-27-Direction-and-Notes.md, design/2026-06-28-Future-Implementation-Tasks.md, .gitignore
Outlines the long-term parser roadmap, benchmark and testing phases, future DITA export and image-handling tasks, and the new design ignore rule.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hop through scores and schema light,
New triage paths are set aright.
When validation trips the wire,
Readiness softens, not expires.
Markdown sings in clearer form,
And tests keep every boundary warm.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is very vague and doesn’t describe the parser, validation, or schema changes in this PR. Use a specific title naming the main change, e.g. "Improve Markdown parser triage and schema validation".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mdb_20260627_parsertest1

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 markdownlint-cli2 (0.22.1)
design/2026-06-28-b1-parse-assess-implementation-update.md

markdownlint-cli2 wrapper config was not available before execution


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
design/2025-06-27-Direction-and-Notes.md (6)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Filename date appears to be a year off.

The document is dated 2025-06-27 but the PR was created on 2026-06-28 and the implementation update is 2026-06-27. Verify whether this should be 2026-06-27-Direction-and-Notes.md to match the cohort and avoid confusion with stale documents.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/2025-06-27-Direction-and-Notes.md` at line 1, The document filename
appears to use the wrong year, which can make it look stale or misaligned with
the cohort. Rename the Markdown file from the 2025-dated version to the
2026-dated version so it matches the PR timeline and the implementation update,
and make sure any references to this document use the corrected name
consistently.

163-182: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Phase 4 pre-H2 introduction rule is implemented — verify alignment.

The rule described here matches the UnitType.introduction branch in _build_unit (headingless paragraphs without ordered lists). Confirm this section reflects the actual implementation or add a completion marker.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/2025-06-27-Direction-and-Notes.md` around lines 163 - 182, The Phase 4
note should match the actual `_build_unit` behavior for headingless pre-H2
content, but it currently reads like an implementation claim without
confirmation. Verify the `UnitType.introduction` handling in `_build_unit` and
either update the section text to reflect the real rule or add a clear
completion marker showing it is implemented, especially around the headingless
paragraph/list classification path.

185-236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Phase 5 readiness reform partially implemented — clarify scope.

The degraded status for schema validation failure is implemented in DitaReadinessEvaluator, but the broader readiness matrix (partial, blocked, not_attempted) and RAG chunk confidence metadata are not yet built. Mark completed vs. pending items to maintain document accuracy as the system evolves.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/2025-06-27-Direction-and-Notes.md` around lines 185 - 236, The Phase 5
readiness section is mixing implemented and not-yet-built behavior, so update
the wording to clearly separate what `DitaReadinessEvaluator` already supports
(`degraded` for schema-invalid cases) from the still-pending readiness matrix
(`partial`, `blocked`, `not_attempted`) and RAG chunk confidence fields. Keep
the document accurate by explicitly marking the broader status taxonomy and
per-chunk metadata requirements as future work, while preserving the completed
`degraded` scope already reflected in `DitaReadinessEvaluator`.

33-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Phase 1 proposes inspectability features not yet implemented.

The inspect-triage CLI command and detailed JSON triage evidence output (confidence, scores, reasons) are described as deliverables, but the current implementation only exposes _MetadataEvidence and _ArticleCandidateScore as internal dataclasses without the rich debug output format shown. Consider adding a "Completed / Pending" marker to each phase so readers know the current implementation boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/2025-06-27-Direction-and-Notes.md` around lines 33 - 84, The phase
text describes inspect-triage and rich triage evidence as completed deliverables
even though they are not implemented yet. Update the phase wording in this
document to clearly mark the inspectability items as pending, and add an
explicit Completed/Pending status for the phase so readers can distinguish
implemented evidence types like MetadataEvidence and ArticleCandidateScore from
planned CLI/output features such as structure-parser inspect-triage.

351-451: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Milestone sequence doesn't match actual implementation order.

The PR implemented Milestone 2 (conservative scorer), Milestone 3 (unit classifier expansion), and partial Milestone 5 (readiness degradation) before Milestone 1's inspectability layer. Consider reordering or adding cross-references to the actual PR changes so the roadmap doesn't mislead future implementers about dependencies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/2025-06-27-Direction-and-Notes.md` around lines 351 - 451, The
milestone order in this roadmap does not match the actual dependency order in
the implementation. Update the “Practical implementation sequence” section to
reflect the real PR flow: reference the conservative scorer and unit classifier
work before the honesty/inspectability layer only where appropriate, and add
explicit dependency notes between ArticleTriageDecision, UnitEvidence,
inspect-triage, and the readiness/status changes so future implementers are not
misled. Keep the sequence aligned with the existing milestone labels and key
symbols already named in the document.

86-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Phase 2 scoring policy is largely implemented — mark as done or in-progress.

The min_score, min_margin, and topic fallback described here are active in classifier.py (_MIN_MARGIN = 3, ArticleSignature.min_score, _score_article_type). However, dominance_ratio is not implemented. Clarify which sub-items are complete to prevent readers from expecting unbuilt features.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/2025-06-27-Direction-and-Notes.md` around lines 86 - 125, The Phase 2
scoring policy note is overstating what is implemented, since `classifier.py`
already has `_MIN_MARGIN`, `ArticleSignature.min_score`, and
`_score_article_type`, but `dominance_ratio` is still not present. Update the
note to clearly separate completed items from pending ones, marking the
implemented scoring and topic fallback pieces as done/in-progress and calling
out `dominance_ratio` as not yet built. Use the existing symbols
`ArticleSignature.min_score`, `_MIN_MARGIN`, and `_score_article_type` to anchor
the clarification.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/structure_parser/readiness/dita.py`:
- Line 21: The schema-validation prerequisite in DITA readiness is only
recording failures, so successful validation is indistinguishable from “never
run.” Update the logic around the DITA readiness tracking so the branch that
handles schema validation success in the readiness/parser flow (including the
code paths referenced by the DITA prerequisite handling) records a positive
result in TargetReadiness.prerequisites_met, while still keeping failures in
prerequisites_missing.

In `@src/structure_parser/structured_markdown/classifier.py`:
- Around line 178-184: The precedence-ranked metadata key collections used by
_infer_article_type_from_metadata are unordered, so first-match selection can
vary across runs. Replace _AUTHORITATIVE_METADATA_KEYS and _SECONDARY_EXACT_KEYS
in structured_markdown/classifier.py with ordered tuples (keeping the intended
key order) and make sure the lookup logic preserves that order when scanning for
article-type metadata.

---

Nitpick comments:
In `@design/2025-06-27-Direction-and-Notes.md`:
- Line 1: The document filename appears to use the wrong year, which can make it
look stale or misaligned with the cohort. Rename the Markdown file from the
2025-dated version to the 2026-dated version so it matches the PR timeline and
the implementation update, and make sure any references to this document use the
corrected name consistently.
- Around line 163-182: The Phase 4 note should match the actual `_build_unit`
behavior for headingless pre-H2 content, but it currently reads like an
implementation claim without confirmation. Verify the `UnitType.introduction`
handling in `_build_unit` and either update the section text to reflect the real
rule or add a clear completion marker showing it is implemented, especially
around the headingless paragraph/list classification path.
- Around line 185-236: The Phase 5 readiness section is mixing implemented and
not-yet-built behavior, so update the wording to clearly separate what
`DitaReadinessEvaluator` already supports (`degraded` for schema-invalid cases)
from the still-pending readiness matrix (`partial`, `blocked`, `not_attempted`)
and RAG chunk confidence fields. Keep the document accurate by explicitly
marking the broader status taxonomy and per-chunk metadata requirements as
future work, while preserving the completed `degraded` scope already reflected
in `DitaReadinessEvaluator`.
- Around line 33-84: The phase text describes inspect-triage and rich triage
evidence as completed deliverables even though they are not implemented yet.
Update the phase wording in this document to clearly mark the inspectability
items as pending, and add an explicit Completed/Pending status for the phase so
readers can distinguish implemented evidence types like MetadataEvidence and
ArticleCandidateScore from planned CLI/output features such as structure-parser
inspect-triage.
- Around line 351-451: The milestone order in this roadmap does not match the
actual dependency order in the implementation. Update the “Practical
implementation sequence” section to reflect the real PR flow: reference the
conservative scorer and unit classifier work before the honesty/inspectability
layer only where appropriate, and add explicit dependency notes between
ArticleTriageDecision, UnitEvidence, inspect-triage, and the readiness/status
changes so future implementers are not misled. Keep the sequence aligned with
the existing milestone labels and key symbols already named in the document.
- Around line 86-125: The Phase 2 scoring policy note is overstating what is
implemented, since `classifier.py` already has `_MIN_MARGIN`,
`ArticleSignature.min_score`, and `_score_article_type`, but `dominance_ratio`
is still not present. Update the note to clearly separate completed items from
pending ones, marking the implemented scoring and topic fallback pieces as
done/in-progress and calling out `dominance_ratio` as not yet built. Use the
existing symbols `ArticleSignature.min_score`, `_MIN_MARGIN`, and
`_score_article_type` to anchor the clarification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 497a9f75-1781-4c8b-84e4-31ed1ec3edb0

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6e161 and bad91fd.

📒 Files selected for processing (12)
  • design/2025-06-27-Direction-and-Notes.md
  • design/2026-06-27-parse-assess-implementation-update.md
  • design/2026-06-28-assess/assess.md
  • design/2026-06-28-assess/azure-stack-acs-differences.md.json
  • design/2026-06-28-assess/azure-stack-arm-templates.md.json
  • design/2026-06-28-assess/azure-stack-compute-overview.md.json
  • design/2026-06-28-assess/azure-stack-connect-azure-stack.md.json
  • design/2026-06-28-assess/azure-stack-connect-vpn.md.json
  • design/2026-06-28-assess/azure-stack-considerations.md.json
  • src/structure_parser/readiness/dita.py
  • src/structure_parser/structured_markdown/classifier.py
  • tests/unit/test_structured_markdown_classifier.py

- Document must have an H1 title.
- Article type must be classified (not "unknown").
- DITA type mapping must be present.
- Schema validation must pass (failure → degraded, not blocked).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Record successful schema validation when it is present.

The new branch only writes the failure case into prerequisites_missing. When validation succeeds, TargetReadiness.prerequisites_met still looks the same as "validation was never run", even though Line 21 now treats this as a tracked prerequisite.

Proposed fix
-        if doc.validation is not None and not doc.validation.valid:
-            missing.append(
-                "Schema validation failed; DITA output may not conform to article schema"
-            )
+        if doc.validation is not None:
+            if doc.validation.valid:
+                met.append("Schema validation passed")
+            else:
+                missing.append(
+                    "Schema validation failed; DITA output may not conform to article schema"
+                )

Also applies to: 48-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/structure_parser/readiness/dita.py` at line 21, The schema-validation
prerequisite in DITA readiness is only recording failures, so successful
validation is indistinguishable from “never run.” Update the logic around the
DITA readiness tracking so the branch that handles schema validation success in
the readiness/parser flow (including the code paths referenced by the DITA
prerequisite handling) records a positive result in
TargetReadiness.prerequisites_met, while still keeping failures in
prerequisites_missing.

Comment on lines +178 to +184
# Keys whose values are treated as direct author declarations (weight 10).
_AUTHORITATIVE_METADATA_KEYS: frozenset[str] = frozenset({"articleType", "article_type"})

# Additional exact keys whose values are treated as secondary evidence (weight 6).
_SECONDARY_EXACT_KEYS: frozenset[str] = frozenset({
"type", "topic", "topic_type", "content_type", "document_type", "information_type",
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Python frozenset iteration order strings PYTHONHASHSEED determinism

💡 Result:

In Python, the iteration order of a frozenset is not guaranteed to be deterministic [1][2]. Frozensets, like sets, are unordered collections based on hash tables [2], and their internal organization depends on the hash values of their elements [1][3]. Because Python enables hash randomization (also known as "salting") by default for certain types—including strings, bytes, and datetime objects—the hash values for these types change between different invocations of the Python interpreter [1][2]. This randomization is a security feature designed to prevent algorithmic complexity denial-of-service attacks [1][2]. As a result, the iteration order of a frozenset containing such types can vary from one execution of a program to the next [1][3]. You can control this behavior using the PYTHONHASHSEED environment variable: 1. Determinism across runs: By setting PYTHONHASHSEED to a fixed integer value (e.g., export PYTHONHASHSEED=0), you can force the hash seed to be constant across different runs of the same Python process [1][3]. While this makes the iteration order consistent for a given set of elements, the order remains implementation-dependent and should not be relied upon as a stable property [1][3]. 2. Limitations: Setting PYTHONHASHSEED does not guarantee a specific order; it only ensures that the hash-based ordering remains the same if the hash function and set insertion sequence remain identical [3][4]. If your elements include objects that use identity-based hashing (the default for user-defined classes), the order will still vary across runs even with a fixed hash seed because memory addresses change [4]. Note that while frozenset iteration order remains inherently non-deterministic by design [2], recent updates to Python have improved the determinism of frozenset serialization (marshalling) for the purpose of creating reproducible bytecode (pyc files) [5][6]. However, this does not change the runtime iteration behavior of the object itself [6].

Citations:


Use ordered containers for precedence-ranked metadata keys.

_AUTHORITATIVE_METADATA_KEYS and _SECONDARY_EXACT_KEYS are iterated to pick the first matching key, but frozenset iteration over strings is hash-seed dependent. That makes _infer_article_type_from_metadata choose different conflicting metadata values across runs. Switch these to ordered tuples so precedence is deterministic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/structure_parser/structured_markdown/classifier.py` around lines 178 -
184, The precedence-ranked metadata key collections used by
_infer_article_type_from_metadata are unordered, so first-match selection can
vary across runs. Replace _AUTHORITATIVE_METADATA_KEYS and _SECONDARY_EXACT_KEYS
in structured_markdown/classifier.py with ordered tuples (keeping the intended
key order) and make sure the lookup logic preserves that order when scanning for
article-type metadata.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/structure_parser/structured_markdown/component_mapper.py`:
- Around line 73-89: The blockquote/alert mapping in component_mapper is
building markdown only from paragraph text, so nested lists, code blocks, and
other child nodes are lost even though they remain in content. Update the
mapping logic in the alert and blockquote branches of the component creation
code to derive markdown from the already-mapped child components or the original
source slice instead of joining only paragraph content, and remove the synthetic
"> ..." fallback so markdown stays aligned with the actual nested structure.

In `@src/structure_parser/validation/model_validator.py`:
- Around line 130-135: Serialization in `_to_schema_dict()` is dropping empty
required `markdown` values because it uses a truthy check, which removes valid
empty-string fields for components like table cells. Update the
`model_validator` serialization logic to preserve `markdown` when it is `""` by
checking for `None` instead, and keep the existing behavior for missing values.
Use `_to_schema_dict()`, `unit.term`, and the `markdown` field assignment as the
key places to update.

In `@tests/contract/test_unit_schema_contract.py`:
- Around line 176-185: The procedure-dominance assertion in
test_multiple_procedure_units_is_howto is using clean.md, which is also an
authoritative-metadata fixture and can mask regressions in unit-evidence
scoring. Update this test to use a metadata-free fixture in
test_unit_schema_contract.py so ArticleType.howto is asserted based on procedure
units alone, and keep the check tied to parse_file, sc.structured_content, and
UnitType.procedure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73c44328-b8c6-4e6b-ae24-7bd0bf65d9b3

📥 Commits

Reviewing files that changed from the base of the PR and between bad91fd and 9c571ba.

📒 Files selected for processing (14)
  • .gitignore
  • design/2026-06-27-Direction-and-Notes.md
  • design/2026-06-28-Future-Implementation-Tasks.md
  • model/articles/units/unitIntroduction.schema.json
  • src/structure_parser/resources/model/articles/units/unitIntroduction.schema.json
  • src/structure_parser/structured_markdown/attribute_mapper.py
  • src/structure_parser/structured_markdown/classifier.py
  • src/structure_parser/structured_markdown/component_mapper.py
  • src/structure_parser/validation/model_validator.py
  • tests/contract/test_schema_round_trip.py
  • tests/contract/test_unit_schema_contract.py
  • tests/fixtures/markdown/nextstep_only.md
  • tests/fixtures/markdown/procedure_unit.md
  • tests/fixtures/markdown/topic_mixed.md
💤 Files with no reviewable changes (1)
  • design/2026-06-27-Direction-and-Notes.md
✅ Files skipped from review due to trivial changes (3)
  • tests/fixtures/markdown/topic_mixed.md
  • tests/fixtures/markdown/nextstep_only.md
  • src/structure_parser/structured_markdown/attribute_mapper.py

Comment on lines +73 to +89
inner = "\n".join(
f"> {c.content}" for c in node.children if c.node_type == "paragraph" and c.content
)
md = f"> [!{alert_type.upper()}]\n{inner}" if inner else f"> [!{alert_type.upper()}]"
return Component(
component_type=ComponentType.compAlert,
alert_type=alert_type,
markdown=md,
source=span,
content=children_comps,
)
inner = "\n".join(
f"> {c.content}" for c in node.children if c.node_type == "paragraph" and c.content
)
return Component(
component_type=ComponentType.compBlockQuote,
markdown=inner or "> ...",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep markdown faithful to nested quote/list content.

Line 73 and Line 172 only derive markdown from paragraph/inline text, so nested lists or code blocks are preserved in content but disappear from markdown. The "> ..." fallback on Line 89 also invents content for non-paragraph blockquotes. Build markdown from the mapped children (or the raw source slice) so the two representations stay in sync.

Also applies to: 167-175

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/structure_parser/structured_markdown/component_mapper.py` around lines 73
- 89, The blockquote/alert mapping in component_mapper is building markdown only
from paragraph text, so nested lists, code blocks, and other child nodes are
lost even though they remain in content. Update the mapping logic in the alert
and blockquote branches of the component creation code to derive markdown from
the already-mapped child components or the original source slice instead of
joining only paragraph content, and remove the synthetic "> ..." fallback so
markdown stays aligned with the actual nested structure.

Comment on lines +130 to +135
if unit.procedure_representation is not None:
u_dict["procedureRepresentation"] = unit.procedure_representation.value
if unit.term:
u_dict["term"] = unit.term

comps = []
for comp in unit.content:
c_dict: dict = {"componentType": comp.component_type.value}
if comp.markdown:
c_dict["markdown"] = comp.markdown
if comp.text:
c_dict["text"] = comp.text
comps.append(c_dict)
u_dict["content"] = comps
u_dict["content"] = [_component_to_dict(c) for c in unit.content]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve empty required markdown fields during serialization.

Line 173 drops markdown whenever its value is "". That breaks the new mapper behavior for empty-but-valid components such as table cells, where the schema still requires a markdown property. Use a None check here so _to_schema_dict() stops stripping required fields.

Suggested fix
-    if c.markdown:
+    if c.markdown is not None:
         d["markdown"] = c.markdown

Also applies to: 173-175

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/structure_parser/validation/model_validator.py` around lines 130 - 135,
Serialization in `_to_schema_dict()` is dropping empty required `markdown`
values because it uses a truthy check, which removes valid empty-string fields
for components like table cells. Update the `model_validator` serialization
logic to preserve `markdown` when it is `""` by checking for `None` instead, and
keep the existing behavior for missing values. Use `_to_schema_dict()`,
`unit.term`, and the `markdown` field assignment as the key places to update.

Comment on lines +176 to +185
def test_multiple_procedure_units_is_howto(self) -> None:
"""A document with multiple ordered-list procedure units should be howto."""
doc = parse_file(_MD / "clean.md")
sc = doc.structured_content
assert sc is not None
proc_count = sum(1 for u in sc.content if u.unit_type == UnitType.procedure)
assert proc_count >= 1, "clean.md should have at least one procedure unit"
assert sc.article_type == ArticleType.howto, (
f"clean.md with procedure units should be howto, got {sc.article_type.value!r}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use a metadata-free fixture for the procedure-dominance assertion.

Lines 178-185 parse clean.md, but this file is also treated below as an authoritative-metadata howto fixture. That means this test still passes if unit-evidence scoring regresses and metadata alone forces ArticleType.howto, so it does not actually verify conservative howto selection.

Suggested test tightening
     def test_multiple_procedure_units_is_howto(self) -> None:
         """A document with multiple ordered-list procedure units should be howto."""
-        doc = parse_file(_MD / "clean.md")
+        doc = parse_file(_MD / "procedure_dominant.md")  # no authoritative articleType metadata
         sc = doc.structured_content
         assert sc is not None
         proc_count = sum(1 for u in sc.content if u.unit_type == UnitType.procedure)
-        assert proc_count >= 1, "clean.md should have at least one procedure unit"
+        assert proc_count >= 2, "procedure_dominant.md should have multiple procedure units"
         assert sc.article_type == ArticleType.howto, (
-            f"clean.md with procedure units should be howto, got {sc.article_type.value!r}"
+            f"procedure_dominant.md should be howto, got {sc.article_type.value!r}"
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_multiple_procedure_units_is_howto(self) -> None:
"""A document with multiple ordered-list procedure units should be howto."""
doc = parse_file(_MD / "clean.md")
sc = doc.structured_content
assert sc is not None
proc_count = sum(1 for u in sc.content if u.unit_type == UnitType.procedure)
assert proc_count >= 1, "clean.md should have at least one procedure unit"
assert sc.article_type == ArticleType.howto, (
f"clean.md with procedure units should be howto, got {sc.article_type.value!r}"
)
def test_multiple_procedure_units_is_howto(self) -> None:
"""A document with multiple ordered-list procedure units should be howto."""
doc = parse_file(_MD / "procedure_dominant.md") # no authoritative articleType metadata
sc = doc.structured_content
assert sc is not None
proc_count = sum(1 for u in sc.content if u.unit_type == UnitType.procedure)
assert proc_count >= 2, "procedure_dominant.md should have multiple procedure units"
assert sc.article_type == ArticleType.howto, (
f"procedure_dominant.md should be howto, got {sc.article_type.value!r}"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/contract/test_unit_schema_contract.py` around lines 176 - 185, The
procedure-dominance assertion in test_multiple_procedure_units_is_howto is using
clean.md, which is also an authoritative-metadata fixture and can mask
regressions in unit-evidence scoring. Update this test to use a metadata-free
fixture in test_unit_schema_contract.py so ArticleType.howto is asserted based
on procedure units alone, and keep the check tied to parse_file,
sc.structured_content, and UnitType.procedure.

@mattbriggs
mattbriggs merged commit 3663d9c into main Jun 28, 2026
3 of 6 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/structure_parser/cli.py`:
- Around line 48-49: The _pipeline_config helper is forcing
enable_model_validation to False for every pipe run, which prevents downstream
validation state from being populated. Update _pipeline_config to preserve the
validation setting from _config(debug) instead of overriding it, so
semantic_enricher.py can set doc.validation and readiness/dita.py can correctly
report schema-validation readiness. Use the _pipeline_config and _config symbols
to locate and adjust the pipeline configuration flow.

In `@src/structure_parser/contracts/config.py`:
- Around line 26-32: The `schema_validation_timeout_seconds` config field
currently allows non-positive values to slip through and disable the timeout in
`_schema_validation_timer()`. Update the config boundary in `config.py` for
`schema_validation_timeout_seconds` so only `None` is treated as unbounded and
any `0` or negative value is rejected or normalized before validation begins.
Keep the fix localized to the config field or its validation logic so downstream
schema validation never receives non-positive timeout values.

In `@src/structure_parser/validation/schema_validator.py`:
- Around line 5-6: Guard the SIGALRM timer setup in schema_validator so it only
runs on the main thread, since signal.signal() and setitimer() raise ValueError
in worker threads and currently surface as schema-validation failures. Update
the validation flow around the signal-based timeout logic to check for the main
thread before enabling the timer, and skip that timeout path entirely when not
on the main thread.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1665ea93-2df7-4508-ba1d-fea135e25418

📥 Commits

Reviewing files that changed from the base of the PR and between 9c571ba and 5baa279.

📒 Files selected for processing (8)
  • design/2026-06-28-b1-parse-assess-implementation-update.md
  • src/structure_parser/application/commands.py
  • src/structure_parser/cli.py
  • src/structure_parser/contracts/config.py
  • src/structure_parser/enrichment/semantic_enricher.py
  • src/structure_parser/pipeline/orchestrator.py
  • src/structure_parser/validation/model_validator.py
  • src/structure_parser/validation/schema_validator.py

Comment on lines +48 to +49
def _pipeline_config(debug: bool = False) -> ParserConfig:
return _config(debug).model_copy(update={"enable_model_validation": False})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't disable model validation for pipe.

This hard-sets enable_model_validation=False for every pipeline run. Downstream, semantic_enricher.py leaves doc.validation unset when validation is disabled, and readiness/dita.py only degrades on a failed validation result, so the pipe path can no longer reflect schema-validation readiness at all.

Suggested fix
 def _pipeline_config(debug: bool = False) -> ParserConfig:
-    return _config(debug).model_copy(update={"enable_model_validation": False})
+    return _config(debug)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _pipeline_config(debug: bool = False) -> ParserConfig:
return _config(debug).model_copy(update={"enable_model_validation": False})
def _pipeline_config(debug: bool = False) -> ParserConfig:
return _config(debug)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/structure_parser/cli.py` around lines 48 - 49, The _pipeline_config
helper is forcing enable_model_validation to False for every pipe run, which
prevents downstream validation state from being populated. Update
_pipeline_config to preserve the validation setting from _config(debug) instead
of overriding it, so semantic_enricher.py can set doc.validation and
readiness/dita.py can correctly report schema-validation readiness. Use the
_pipeline_config and _config symbols to locate and adjust the pipeline
configuration flow.

Comment on lines +26 to +32
schema_validation_timeout_seconds: int | None = Field(
default=5,
description=(
"Maximum seconds for advisory runtime schema validation. "
"Set to None to allow unbounded validation."
),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reject non-positive timeout values at the config boundary.

schema_validator._schema_validation_timer() treats <= 0 as “no timeout”, so 0 or -1 silently disable the cap even though this field says only None should do that. Please enforce > 0 here or normalize non-positive values before they reach validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/structure_parser/contracts/config.py` around lines 26 - 32, The
`schema_validation_timeout_seconds` config field currently allows non-positive
values to slip through and disable the timeout in `_schema_validation_timer()`.
Update the config boundary in `config.py` for
`schema_validation_timeout_seconds` so only `None` is treated as unbounded and
any `0` or negative value is rejected or normalized before validation begins.
Keep the fix localized to the config field or its validation logic so downstream
schema validation never receives non-positive timeout values.

Comment on lines +5 to +6
import signal
from contextlib import contextmanager

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File outline ==\n'
ast-grep outline src/structure_parser/validation/schema_validator.py --view expanded || true

printf '\n== Relevant file slice ==\n'
sed -n '1,140p' src/structure_parser/validation/schema_validator.py

printf '\n== Search for validate_against_schema usage ==\n'
rg -n "validate_against_schema|_schema_validation_timer|setitimer|SIGALRM|signal\.signal" src tests || true

printf '\n== Threaded signal semantics probe (read-only) ==\n'
python3 - <<'PY'
import signal
import threading

print("has_SIGALRM=", hasattr(signal, "SIGALRM"))
print("main_thread=", threading.current_thread() is threading.main_thread())

def probe():
    try:
        signal.signal(signal.SIGALRM, lambda *_: None)
        print("worker_signal_ok=True")
    except Exception as e:
        print("worker_signal_ok=False", type(e).__name__, str(e))

t = threading.Thread(target=probe)
t.start()
t.join()
PY

Repository: mattbriggs/structured-markdown

Length of output: 7979


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '136,180p' src/structure_parser/validation/schema_validator.py

Repository: mattbriggs/structured-markdown

Length of output: 1903


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Call sites ==\n'
rg -n "validate_against_schema\(" src tests || true

printf '\n== Threading / executor usage near validation paths ==\n'
rg -n "threading|ThreadPool|ProcessPool|executor|asyncio|to_thread|run_in_executor" src tests || true

Repository: mattbriggs/structured-markdown

Length of output: 630


Skip SIGALRM outside the main thread. signal.signal() / setitimer() raise ValueError in worker threads, and this path turns that into a schema-validation failure. Add a main-thread guard before enabling the timer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/structure_parser/validation/schema_validator.py` around lines 5 - 6,
Guard the SIGALRM timer setup in schema_validator so it only runs on the main
thread, since signal.signal() and setitimer() raise ValueError in worker threads
and currently surface as schema-validation failures. Update the validation flow
around the signal-based timeout logic to check for the main thread before
enabling the timer, and skip that timeout path entirely when not on the main
thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant