docpluck 2.4.85__tar.gz → 2.4.90__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/_project/canary.json +6 -1
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/_project/lessons.md +10 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-deploy/SKILL.md +12 -6
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/LEARNINGS.md +24 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/SKILL.md +16 -98
- docpluck-2.4.90/.claude/skills/docpluck-iterate/references/subagent-parallelization.md +96 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/CHANGELOG.md +60 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/LESSONS.md +96 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/PKG-INFO +2 -2
- {docpluck-2.4.85 → docpluck-2.4.90}/TODO.md +41 -2
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/__init__.py +1 -1
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/extract.py +17 -1
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/extract_columns.py +551 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/extract_layout.py +43 -4
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/normalize.py +229 -24
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/render.py +4 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/__init__.py +2 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/camelot_extract.py +12 -1
- docpluck-2.4.90/docs/BENCHMARKS_liteparse_2026-06.md +149 -0
- docpluck-2.4.90/docs/HANDOFF_2026-06-13_sciencearena_grobid_liteparse.md +86 -0
- docpluck-2.4.90/docs/HANDOFF_2026-06-15_docpluck-iterate-resume.md +119 -0
- docpluck-2.4.90/docs/TRIAGE_2026-06-15_head_v2.4.88_assessment.md +76 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/specs/2026-06-08-rc1-region-aware-column-architecture.md +10 -1
- {docpluck-2.4.85 → docpluck-2.4.90}/pyproject.toml +6 -2
- docpluck-2.4.90/tests/test_camelot_temp_cleanup.py +52 -0
- docpluck-2.4.90/tests/test_canary_provenance.py +167 -0
- docpluck-2.4.90/tests/test_dropped_minus_layout_recovery_real_pdf.py +124 -0
- docpluck-2.4.90/tests/test_extract_layout.py +93 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_f0_footnote_strip.py +50 -0
- docpluck-2.4.90/tests/test_rc1_banded_column_real_pdf.py +183 -0
- docpluck-2.4.90/tools/canary_provenance.py +62 -0
- docpluck-2.4.90/tools/fix_python_env.ps1 +34 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tools/render_for_audit.py +35 -0
- docpluck-2.4.85/tests/test_extract_layout.py +0 -47
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-cleanup/SKILL.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/ai-full-doc-verify.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/cycle-report-template.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/local-verification.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/rationalizations.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/real-library-real-pdf.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/release-flow.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/self-improvement.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-iterate/references/three-tier-parity.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-qa/SKILL.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-qa/references/benchmark-mode.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-qa/references/check-11-hard-rules.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-qa/references/check-13-escicheck-production.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-qa/references/check-5-escicheck-library.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-qa/references/check-6-escicheck-local-webapp.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-qa/references/check-7-batch-smoke.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.claude/skills/docpluck-review/SKILL.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.github/workflows/bump-app-pin.yml +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.github/workflows/publish.yml +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.github/workflows/test.yml +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/.gitignore +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/CLAUDE.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/HANDOFF_SECTIONS_APP_INTEGRATION.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/LICENSE +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/REPLY_FROM_DOCPLUCK_v1.4.5.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/REPLY_FROM_DOCPLUCK_v1.5.0.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/REQUEST_08_CHUNKING_ENDPOINT.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/REQUEST_09_REFERENCE_LIST_NORMALIZATION.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/__main__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/batch.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/cli.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/extract_docx.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/extract_html.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/extract_structured.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/figures/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/figures/detect.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/quality.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/annotators/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/annotators/docx.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/annotators/html.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/annotators/pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/annotators/text.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/blocks.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/boundaries.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/core.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/taxonomy.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/sections/types.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/bbox_utils.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/captions.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/cell_cleaning.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/cluster.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/confidence.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/detect.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/flatten.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/render.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/tables/whitespace.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docpluck/version.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/BENCHMARKS.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/DESIGN.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-07_sections_strict_iteration.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-09_session_state_and_followups.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-09_unified_extraction_brainstorm.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-10_table_rendering_iteration.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-10_table_rendering_iteration_2.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-10_table_rendering_iteration_3.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-10_table_rendering_iteration_4.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-10_table_rendering_iteration_5.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-10_table_rendering_iteration_6.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-10_table_rendering_iteration_7.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-11_PROMOTE_SPIKE_TO_LIBRARY.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-11_table_rendering_iteration_8.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-11_visual_review_findings.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-12_phase2_101pdf_corpus.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-12_remaining_ui_and_chrome_verification.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-12_visual_verify_results.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-13_apa_50_expansion.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-13_apa_50_expansion_iter_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-13_apa_50_expansion_iter_2.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-13_iterate_skill_first_use.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-13_iterative_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-13_iterative_library_improvement.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-13_table_extraction_next_iteration.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-14_continue_iterations_v2_4_30_to_15n.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-14_full_corpus_iteration_v2_4_30.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-14_iterate_6_cycles_complete.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-14_iterate_9_cycle_run.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-14_iterate_resume_4_cycles.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-14_iterate_v2_4_31_cycle_15n.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-14_phase_5d_gold_audit_v2_4_29.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-15_autonomous_apa_first_10h.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-15_iterate_apa_run_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-16_ai-gold-instructions.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-16_iterate_apa_run_2.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-16_iterate_apa_run_3.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-16_iterate_run_4_final.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-16_iterate_run_4_fix_and_continue.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-16_iterate_run_5.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-16_iterate_run_6.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-17_iterate_run_7.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-17_iterate_run_8.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-17_iterate_run_9.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-18_iterate_run_9_cont.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-18_iterate_run_9_cont2.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-20_iterate_run_9_cont3.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-22_iterate_run_9_session4_final.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-22_iterate_run_9_session5_close.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-25_haiku-orchestration-pretest.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-05-25_pretest-followups.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-06-08_iterate_splice-wordintegrity-runningheader.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/HANDOFF_2026-06-08_untested_sweep_v2.4.81.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/ITERATION_VERIFICATION_LESSONS.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/LIBRARY_APP_SYNC.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/NORMALIZATION.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/README.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/TRIAGE_2026-05-10_corpus_assessment.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/TRIAGE_2026-05-14_phase_5d_gold_audit.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/TRIAGE_2026-06-08_untested_corpus_sweep.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-22-b1-next-iteration.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-22-b2-remaining-halluc-head.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-22-b3-b7-structural-defects.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-22-residual-after-locally-doable-pass.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-23-bundled-residual-cycle-CLOSED.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-23-residual-after-iterate-spine-cycles-1-3.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-25-canary-audit-architecture-and-cluster-A-B-C-landed.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-25-wrapup-r4-cycle.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-26-run-11-cluster-A-ter-and-C-bis-landed.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-05-26-text-extraction-defects-from-citationguard-audit.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-06-07-text-extraction-defects-from-citationguard-iterate.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-06-07-v2.4.78-landed-canary-iterate.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/handoffs/2026-06-07-v2.4.79-findings-1-2-cleared.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/2026-05-06-section-identification.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/2026-05-06-table-extraction.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/2026-05-07-sections-strict-iteration-progress.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/2026-05-08-unified-extraction-phase-0-splice-spike.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/2026-05-23-haiku-orchestration-pretest.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/sections-deferred-items.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/sections-issues-backlog.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/2026-05-07_spot-01_apa.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/2026-05-07_spot-02_pattern-A-shipped.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/2026-05-08_spot-final_all-styles.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/COMPARISON.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/korbmacher_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/option-a.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/ziano_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_notes_raw.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/notes.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/option-b.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_notes_raw.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/korbmacher_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/notes.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/option-c.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/sample-pdftotext-bbox.html +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/ziano_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/korbmacher_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/notes.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/option-d.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/ziano_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_2022_kruger_bbox.html +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_bbox.html +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/option-e.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/sample-bbox.html +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_2021_joep_bbox.html +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_bbox.html +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_table1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/html-fallback-demo.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.err +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/papers.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/report.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/splice_spike.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/plans/spot-checks/splice-spike/test_splice_spike.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/specs/2026-04-27-request-09-reference-normalization-design.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/specs/2026-05-06-section-identification-design.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/specs/2026-05-06-table-extraction-design.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/specs/2026-05-08-unified-extraction-design.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/specs/2026-05-23-haiku-orchestration-pretest-design.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/docs/superpowers/specs/2026-06-07-ip_feldman-B4-R4-column-interleave-diagnosis.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/README.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/VERIFIER_PROMPT.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/baseline_matrix.json +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/checks.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/corpus.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/corpus_manifest.json +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/extract.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/gold_keys.json +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/harness/inspect.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/lint_rendered_corpus.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/pretest_capture_tokens.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/verify_corpus.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/scripts/verify_corpus_full.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/conftest.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/fixtures/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/fixtures/sections/__init__.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/fixtures/sections/builders.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/fixtures/structured/.gitkeep +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/fixtures/structured/MANIFEST.json +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/fixtures/structured/README.md +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/golden/sections/apa_multi_study_pdf.json +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/golden/sections/apa_single_study_pdf.json +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/golden/sections/html_real_headings.json +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/amj_lattice.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/apa_chan_feldman_lineless.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/apa_chen_jesp_lineless.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/apa_efendic_affect.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/apa_ip_feldman_pspb.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/bmc_lattice.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/ieee_figure_heavy.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/ieee_lattice.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/jama_lattice.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/nat_comms_figure_only.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/nature_minimal_rule.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/snapshots/scirep_minimal_rule.txt +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_a3c_leading_zero_decimal_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_a4_ci_period_to_comma.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_all_caps_section_promote_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_bbox_utils.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_benchmark_docx_html.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_cambridge_footer_strip_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_caption_only_table_heading_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_caption_regex.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_chart_data_trim_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_cid_minus_recovery_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_cli_sections.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_cli_structured.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_confidence.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_corpus_smoke.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_d5_normalization_audit.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_edge_cases.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_elsevier_footer_strip_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_equation_page_header_strip_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_extract_columns.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_extract_docx.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_extract_filter_sugar.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_extract_html.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_extract_pdf_structured.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_extraction.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_f0_table_region_aware.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_fffd_comparison_recovery_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_figure_caption_trim_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_figure_detect.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_fixtures_manifest.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_hallucinated_heading_continuation_guard.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_harness_text_loss_reflow.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_harvard_refs_pagebreak_stitch.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_jama_open_cluster_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_lattice_cluster.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_letterspaced_label_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_ligature_decomposition_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_lt_operator_recovery_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_mathitalic_greek_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_metaesci_followups.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_minus_sign_recovery_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalization.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_a3_r2_body_integer_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_idempotent_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_layout_param.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_metadata_leak_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_report_layout_fields.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_soft_hyphen_dehyphenation.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_normalize_v18_strips.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_numbered_heading_promotion_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_numbered_section_promotion_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_o5_reference_inversion_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_orphan_multilevel_number_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_orphan_section_number_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_p0r_recurring_running_header_strip.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_preserve_math_glyphs_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_pretest_capture_tokens.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_pua_glyph_recovery_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_quality.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_r1_whitespace_cells_wiring_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_r4_column_correction_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_rc1_general_column_correction_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_render.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_render_frontmatter_masthead.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_render_html.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_render_subsection_chain_promotion.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_request_09_reference_normalization.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_residual_2026_05_23_bundled.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_roman_numeral_section_promote_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_section_row_label_no_merge_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_boundaries.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_boundary_truncation.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_core_partition.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_docx_annotator.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_extract_text.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_footnote_section.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_golden.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_html_annotator.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_pdf_annotator.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_public_api.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_real_corpus.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_taxonomy.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_text_annotator.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_types.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_unit_corpus.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_v161_coalesce.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_v161_subheadings.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_v161_taxonomy.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_v161_text_annotator.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_sections_version.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_smoke_fixtures.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_structured_result_type.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_structured_types.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_structured_version.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_table_caption_cell_region_real_pdf.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_table_detect.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_tables_cell_cleaning.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_tables_flatten.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_text_mode.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_v23_1_fixes.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_v23_bug_fixes.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_v23_post_corpus.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_v23_post_corpus_v2.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_v2_backwards_compat.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_v2_top_level_exports.py +0 -0
- {docpluck-2.4.85 → docpluck-2.4.90}/tests/test_whitespace_cluster.py +0 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"stem": "ip_feldman_2025_pspb",
|
|
10
10
|
"key": "10.1177/01461672251327169",
|
|
11
11
|
"doi": "10.1177/01461672251327169",
|
|
12
|
+
"expected_pdf_sha": "8680a4091648fcac0be6a9f80b2ace8faa5690bd96a84a527d6c5839d312cf58",
|
|
12
13
|
"rationale": "Triggered the 2026-05-23 iterate-loop spine. Single paper exercising 4 distinct defect classes: G5d hallucinated subsection headings (Supplemental Materials mid-Method), B3 affiliation/running-header leak into body, B4 mid-text Table 3 caption, running-header 'Ip and Feldman' surfacing as a paragraph. The litmus test for whether the library's structural-defect cluster is closed.",
|
|
13
14
|
"max_gold_age_days": 30
|
|
14
15
|
},
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
"stem": "plos_med_1",
|
|
17
18
|
"key": "10.1371/journal.pmed.1004323",
|
|
18
19
|
"doi": "10.1371/journal.pmed.1004323",
|
|
20
|
+
"expected_pdf_sha": "e6b4cea5767d1ec1eccdd8e448e6f9a6cab57ef740ae1f3fac6c59cddf6dbfd5",
|
|
19
21
|
"rationale": "B1 text_loss canary — Tables 2-5; Table 5 has 13 SAE rows lost. The 1 still-failing Tier-D cell as of run 9 close. Forces the table-completeness story to stay covered every cycle. The architectural decision around modified-Approach-B bbox computation has been outstanding since 2026-05-22 — this canary keeps it visible.",
|
|
20
22
|
"max_gold_age_days": 30
|
|
21
23
|
},
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
"stem": "chandrashekar_2023_mp",
|
|
24
26
|
"key": "10.15626/MP.2022.3108",
|
|
25
27
|
"doi": "10.15626/MP.2022.3108",
|
|
28
|
+
"expected_pdf_sha": "84ff52f5a22089ffd0bf7f37b47040e91bf7fafd7808265fb82866e419284a0e",
|
|
26
29
|
"rationale": "B6 column-interleave canary — pdftotext two-column reading order serialises a multi-column page interleaved. Distinct defect class from ip_feldman / plos_med_1 (extraction-layer, not normalize-layer). Keeps the column-interleave architectural decision (R4 in 2026-05-22-residual handoff) from being silently forgotten.",
|
|
27
30
|
"max_gold_age_days": 30
|
|
28
31
|
}
|
|
@@ -32,12 +35,14 @@
|
|
|
32
35
|
"stem": "chan_feldman_2025_cogemo",
|
|
33
36
|
"key": "10.1080/02699931.2024.2434156",
|
|
34
37
|
"doi": "10.1080/02699931.2024.2434156",
|
|
38
|
+
"expected_pdf_sha": "5661fe9039250dd2885a616253000662e1337b03eb6457ead286be30ffd7cf54",
|
|
35
39
|
"exercises": "B2 hallucinated/demoted headings; B6 column interleave (Measures section)"
|
|
36
40
|
},
|
|
37
41
|
{
|
|
38
42
|
"stem": "ar_apa_j_jesp_2009_12_011",
|
|
39
43
|
"key": "10.1016/j.jesp.2009.12.011",
|
|
40
44
|
"doi": "10.1016/j.jesp.2009.12.011",
|
|
45
|
+
"expected_pdf_sha": "69b2795c04ef1491b5cfc295be28197cc83b9579830f6fdd1ab8f1549314ce31",
|
|
41
46
|
"exercises": "B2 hallucinated headings (Participants/Overview); B7 deleted-minus glyph (b = .022 → should be -0.022)"
|
|
42
47
|
}
|
|
43
48
|
],
|
|
@@ -55,7 +60,7 @@
|
|
|
55
60
|
"gold_view": "reading",
|
|
56
61
|
"locator_via_article_finder": "python ~/.claude/skills/article-finder/cache-check.py <doi>",
|
|
57
62
|
"gold_via_article_finder": "python ~/.claude/skills/article-finder/ai-gold.py get <key> --view reading",
|
|
58
|
-
"render_command": "
|
|
63
|
+
"render_command": "py -3 tools/render_for_audit.py --key {key} --out {out}",
|
|
59
64
|
"allowed_omissions": [
|
|
60
65
|
"Front-matter masthead block between the title (H1) and the Abstract: author name line(s), affiliation line(s), corresponding-author contact line, journal-name banner, volume/issue/page range, 'Article type', publisher/copyright line, 'Article reuse guidelines', journal-home URL. docpluck strips this masthead by design (render.py _strip_frontmatter_masthead_block); consumers needing structured author metadata use CrossRef/DOI lookup. Its absence from the rendered .md is NOT TEXT-LOSS or METADATA-LEAK.",
|
|
61
66
|
"Publication-history date lines such as 'Received <date>; revision accepted <date>' or 'Received/Revised/Accepted' mastheads — stripped as journal furniture (normalize.py _PAGE_FOOTER_LINE_PATTERNS).",
|
|
@@ -575,3 +575,13 @@ Rotation picks `pool[(N mod L) : (N mod L) + rotation_size]` wrapping. Over `cei
|
|
|
575
575
|
**What:** `M_age 59.3` rendered as `39.3` on collabra.77859. The PDF VISUALLY shows `59.3`, but the embedded text codepoint is baked as `3` — **both pdftotext AND pdfplumber extract `39.3`**. This is the `Västfjäll→Vastfall` class, but a DIGIT in a statistic (silent stat corruption — the most dangerous form for meta-science).
|
|
576
576
|
|
|
577
577
|
**How to diagnose:** 3-way check — pdftotext vs pdfplumber vs a visual/AI read of the PDF. When both deterministic extractors agree on the wrong value and only the visual disagrees, the codepoint is baked wrong and NO text-channel logic can fix it (recovery needs OCR / multimodal-glyph-consensus, a new subsystem). User decision 2026-06-08: **document as a known limitation, do not scope an OCR subsystem.** Consumer guidance: downstream stat-checkers (CitationGuard) must cross-verify digits against CrossRef/visual — docpluck cannot guarantee a digit matches the visual glyph when the publisher baked the wrong codepoint.
|
|
578
|
+
|
|
579
|
+
## Dropped-glyph recovery splits into layout-recoverable vs pixel-only — probe per-instance before designing (2026-06-15, v2.4.89 W0h)
|
|
580
|
+
|
|
581
|
+
**What:** A glyph that **pdftotext drops entirely** (emits nothing) is NOT one class but two, and a per-*instance* 3-way diff tells them apart:
|
|
582
|
+
1. **Layout-recoverable** — pdfplumber still sees the glyph (as an unmapped `(cid:N)` in a symbol font); the text channel lost it, the layout channel kept it. → Recover from the layout channel. `ar_apa_j_jesp_2009_12_011`: 3 of 4 negative betas (`-.022 / -.88 / -.428`) carry their U+2212 as `(cid:2)` in font `AdvP4C4E74`. `normalize.recover_dropped_minus_via_layout` (W0h) re-inserts the minus in the `<stat> = <minus><coef>` operator slot.
|
|
583
|
+
2. **Pixel-only** — pdfplumber ALSO drops it (absent from chars/lines/rects/curves AND pdfminer's raw LTChar/LTImage layer); the minus is painted pixels only — same OCR floor as the baked-glyph lesson above. `ar_apa` `β = -.245`: unrecoverable in text+layout. **User decision 2026-06-15: ship the layout-recoverable subset, document the pixel-only residual, do NOT build an OCR tier.**
|
|
584
|
+
|
|
585
|
+
**Trap:** "the layout channel recovers what pdftotext drops" is true only for sub-case 1. Probe the SPECIFIC failing instances (geometry: is there a char/line/rect immediately left of the number?) before assuming a layout fix is complete — feasibility here was 3/4, and a recovery that silently fixes 3 of 4 sign-flips is a product call (false-confidence), surfaced to the user.
|
|
586
|
+
|
|
587
|
+
**Plumbing gotcha (cost a detour):** the section/render path calls `normalize_text` WITHOUT `layout=` (`sections/__init__.py`), so F0 and every layout-gated pass is OFF there by design (text-channel-only contract). A layout-aware fix must thread a **dedicated** param (`dropped_minus_layout`, `render → extract_sections → normalize_text`) — reusing the `layout=` gate would also switch F0 on and risk broad regressions. The detector must cluster chars into lines by **y-overlap**, never `round(top)` (a minus sits ~0.4pt off its digits' baseline and rounds into a different bucket, orphaning it).
|
|
@@ -108,7 +108,7 @@ fi
|
|
|
108
108
|
echo "✅ Library version sync OK"
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
###
|
|
111
|
+
### 5. Verify Vercel Environment Variables
|
|
112
112
|
```bash
|
|
113
113
|
cd frontend && vercel env ls
|
|
114
114
|
```
|
|
@@ -125,7 +125,7 @@ Required variables (all must show as "Encrypted"):
|
|
|
125
125
|
|
|
126
126
|
If any are missing, refer to SETUP_GUIDE.md.
|
|
127
127
|
|
|
128
|
-
###
|
|
128
|
+
### 6. SES Environment Variables Present in Vercel Production (CRITICAL)
|
|
129
129
|
|
|
130
130
|
Same pattern as check 4 (Vercel env vars) but for the SES + notification surface. Missing any of these means the app boots in production with a broken email path (queued notifications, no send; webhook signature checks fail; admin alerts silently dropped).
|
|
131
131
|
|
|
@@ -141,7 +141,7 @@ done
|
|
|
141
141
|
|
|
142
142
|
**Gate:** all listed vars present (Encrypted). Any missing = FAIL the deploy.
|
|
143
143
|
|
|
144
|
-
###
|
|
144
|
+
### 7. SES Identity SUCCESS
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
147
|
aws sesv2 get-email-identity --email-identity mail.docpluck.app --region eu-west-1 \
|
|
@@ -150,7 +150,7 @@ aws sesv2 get-email-identity --email-identity mail.docpluck.app --region eu-west
|
|
|
150
150
|
|
|
151
151
|
**Gate:** must equal `SUCCESS`. Anything else = FAIL the deploy.
|
|
152
152
|
|
|
153
|
-
###
|
|
153
|
+
### 8. DKIM + SPF + DMARC DNS Resolve
|
|
154
154
|
|
|
155
155
|
```bash
|
|
156
156
|
# DKIM token list — read live from SES, do NOT hard-code:
|
|
@@ -322,8 +322,14 @@ curl -s -w '\nHTTP %{http_code}\n' \
|
|
|
322
322
|
|
|
323
323
|
If deployment fails:
|
|
324
324
|
```bash
|
|
325
|
-
# Vercel:
|
|
326
|
-
|
|
325
|
+
# Vercel: roll back to a SPECIFIC known-good deployment (don't rely on bare
|
|
326
|
+
# `vercel rollback`, which targets whatever happens to be the previous deploy
|
|
327
|
+
# and is ambiguous when several deploys landed close together).
|
|
328
|
+
cd frontend
|
|
329
|
+
vercel ls # find the last URL that showed Ready + healthy
|
|
330
|
+
vercel rollback <deployment-url> # e.g. https://pdfextractor-abc123.vercel.app
|
|
331
|
+
# (bare `vercel rollback` = roll back to the immediately previous deployment;
|
|
332
|
+
# only use it when you are certain that is the known-good target.)
|
|
327
333
|
|
|
328
334
|
# Railway: redeploy from last working commit
|
|
329
335
|
railway service extraction-service
|
|
@@ -1174,3 +1174,27 @@ the `SKIP_CANARY=1` justification when the `claude`-less pre-commit hook hard-fa
|
|
|
1174
1174
|
(pdfplumber/camelot/pytest all had to be pip-installed mid-run into C:\Python314).
|
|
1175
1175
|
Stand up a proper env before an iterate run so the harness + camelot table tests
|
|
1176
1176
|
aren't skipped.
|
|
1177
|
+
|
|
1178
|
+
---
|
|
1179
|
+
|
|
1180
|
+
## Run: 2026-06-15 · cycle 1 · B7 layout-channel dropped-minus recovery · v2.4.88 → v2.4.89
|
|
1181
|
+
|
|
1182
|
+
### Outcome
|
|
1183
|
+
- **B7 (the dropped-minus-with-no-CI residual) FIXED** for the layout-visible subset. `normalize.recover_dropped_minus_via_layout` (W0h) recovers `ar_apa` `β = -.022 / -.88 / -.428`; leaves `.48` positive. Shipped v2.4.89. Surgical blast radius (only ar_apa flips across the 5 onboarded canary papers).
|
|
1184
|
+
- User-approved scope decision (surfaced mid-cycle): ship the layout-visible subset + document the OCR-only residual; do NOT build an OCR tier this run.
|
|
1185
|
+
|
|
1186
|
+
### Blind Spots
|
|
1187
|
+
- **"The layout channel can recover what pdftotext drops" has a HARD FLOOR: pixel-only glyphs.** `ar_apa` `β = -.245` is drawn as painted pixels — its minus is absent from pdftotext AND pdfplumber chars/lines/rects/curves AND pdfminer's raw LTChar/LTImage layer. Before designing ANY layout-recovery, probe the SPECIFIC glyph instances (not just "does the layout see a minus somewhere") — feasibility can be 3/4, not 4/4, and a recovery that silently fixes 3 of 4 sign-flips is a product decision (false-confidence risk), not an automatic win. Confirm per-instance with a geometry probe.
|
|
1188
|
+
- **The section/render path calls `normalize_text` WITHOUT `layout=`** (`sections/__init__.py:75`), so F0 — and any layout-gated pass — is OFF in the main render path by design (the section pipeline is deliberately text-channel-only). A layout-aware fix CANNOT just be added behind the existing `layout` gate; it must thread a DEDICATED param (`dropped_minus_layout`) so it doesn't also switch F0 on (which would risk broad regressions). Non-obvious; cost a detour to discover.
|
|
1189
|
+
- **A TRIAGE data-gap claim can be STALE.** `TRIAGE_2026-06-15` said `plos_med_1` has "no reading gold / BLOCKED". `ai-gold.py get` returned a 42KB gold (28d old). Always re-check `ai-gold.py get <key>` before recording BLOCKED-NEEDS-GOLD — the gold may exist. (Also: the gate's I11 then flagged a key-form mismatch warning for plos — the gold may be under a non-canonical key dir; worth a rekey check.)
|
|
1190
|
+
|
|
1191
|
+
### Edge Cases
|
|
1192
|
+
- **Group layout chars into lines by y-OVERLAP, never `round(top)`.** A minus glyph sits ~0.4pt off its digits' baseline; `round(top)` put `.88`'s `(cid:2)` in top-bucket 353 while its digits were in 352, orphaning it (the first impl recovered `.022` but missed `.88`). Cluster by `bottom > top+ε and top < bottom-ε` (y-overlap) like `extract_layout._chars_to_spans` does.
|
|
1193
|
+
- **pdfplumber reports the glyph EM-box, not ink-box**, so bbox HEIGHT is identical for minus/hyphen/period (≈ font ascent-descent) — you cannot shape-discriminate a minus by vertical ink position. Width does differ (minus 6.3 vs hyphen 3.3 vs period 2.0 at size 8). So the safe detector keys on CONTEXT (unmapped `(cid:N)` in the `= <coef>` operator slot + text-lacks-minus), not glyph shape.
|
|
1194
|
+
|
|
1195
|
+
### Verification Gaps
|
|
1196
|
+
- **Camelot non-determinism on Windows changes the rendered byte count run-to-run** (ar_apa 27038B vs 27507B across two identical renders — a table present in one, absent in the other). This makes `rendered_sha` unstable for I10 and can flip a paper's table findings. The body-prose betas are stable, but table-bearing verification needs a deterministic Camelot or a retry/normalize step before sha-pinning.
|
|
1197
|
+
- **The full `pytest -n auto` (xdist) background run died silently on Windows** (0 output, no python process, no completion notification) — the exact "backgrounded long task dies" failure the portfolio rule warns about. Serial `pytest -q` with an explicit `PYTEST_DONE_EXIT_$?` marker is the reliable pattern here; verify liveness (process + output growth), never infer it.
|
|
1198
|
+
|
|
1199
|
+
### Process notes
|
|
1200
|
+
- One AI-verify cycle surfaced THREE pre-existing defect classes beyond the target: RC-1 two-column interleave (ip_feldman/chandrashekar/chan_feldman/ar_apa-table), B1 table-completeness (plos Tables 2/3/4/5 lose rows/cols/bodies), and metadata-leak (plos affiliations/abbrev/running-headers — an RC-2 residual). Per 0e-bis the run's standing verdict stays FAIL; cycle 1 is an incremental ship, not a clean PASS. Surfaced to user as the run punch-list.
|
|
@@ -34,7 +34,7 @@ Reference handoff: `docs/superpowers/handoffs/2026-05-25-canary-audit-architectu
|
|
|
34
34
|
1. Run: `bash ~/.claude/skills/_shared/bin/preflight-filter.sh docpluck-iterate` and print its `🔧 skill-optimize pre-check · ...` heartbeat as your first visible output line.
|
|
35
35
|
2. Initialize `~/.claude/skills/_shared/run-meta/docpluck-iterate.json` per `~/.claude/skills/_shared/preflight.md` step 6 — including the **iterate-skill extension fields** (`project_root`, `current_cycle`, `cycle_status`, `cycle_targets`, `phase_5d_runs`, `corpus_sweeps`, `open_findings`, `iterate_skips`, `run_closeout`). Set `project_root` to the absolute path of the docpluck repo so `iterate-gate.sh` can find the canary file when running outside a git CWD.
|
|
36
36
|
3. Load `~/.claude/skills/_shared/quality-loop/core.md` into working memory. Although `docpluck-iterate` is not a `-qa`/`-review`/`-cleanup`/`-deploy` skill itself, it ORCHESTRATES those skills cycle-by-cycle and the spine rules R1–R5 apply transitively. Treat any FAIL from those orchestrated skills as a phase failure for this skill.
|
|
37
|
-
4. **Load the iterate-loop spine** — read `~/.claude/skills/_shared/iterate-loop/core.md` and hold rules I1–
|
|
37
|
+
4. **Load the iterate-loop spine** — read `~/.claude/skills/_shared/iterate-loop/core.md` and hold rules **I1–I12** in working memory (the spine grew past I7 — I8 gate-was-called, I9 locator-via-article-finder, I10 artifact-existence, I11 gold-sha-matches-cache, I12 lesson-readback; see the table below for the authoritative `--cycle`/`--close` split, mirrored from `core.md`). Confirm `<docpluck-repo>/.claude/skills/_project/canary.json` exists (it MUST; missing canary = immediate gate fail). This spine — not prose in this file — is what makes the cycle/run discipline foolproof. The **per-cycle** rules (I1, I2, I3, I4, I5, I7, I9, I10, I11, I12-present) are checked after every cycle by `iterate-gate.sh --cycle N`; the **run-close** rules (I6, I8, I12-relevance) are checked by `iterate-gate.sh --close`. A non-zero exit BLOCKS the corresponding write (cycle PASS, run closeout). See "Iterate-loop spine integration" below for the exact call sequence.
|
|
38
38
|
|
|
39
39
|
If you skip these steps, the postflight heartbeat will be missing and the run will produce no learning signal — defeating the whole point of a self-improving loop.
|
|
40
40
|
|
|
@@ -46,15 +46,20 @@ This skill — like every `<prefix>-iterate` and `<prefix>-fix` skill across the
|
|
|
46
46
|
|
|
47
47
|
**What the spine enforces (full text in `~/.claude/skills/_shared/iterate-loop/core.md`):**
|
|
48
48
|
|
|
49
|
-
| Rule | Hard check |
|
|
50
|
-
|
|
51
|
-
| **I1** phase-5d-actually-ran | Cycle must record ≥1 `phase_5d_runs` entry |
|
|
52
|
-
| **I2** canary-coverage | Cycle must AI-verify every (target ∪ canary) paper |
|
|
53
|
-
| **I3** verdict-on-truth-not-proxy |
|
|
54
|
-
| **I4** blocked-gold-is-a-cycle-status | BLOCKED-NEEDS-GOLD is a legal cycle status; blocks run-close (not cycle) |
|
|
55
|
-
| **I5** corpus-sweep-not-stale | Sweep must have run within last 3 cycles |
|
|
56
|
-
| **I6** no-open-canary-findings-at-close | Run-close requires zero open canary findings + zero BLOCKED canary papers |
|
|
57
|
-
| **I7** deterministic-metric-is-not-a-verdict | Idempotency / char-ratio / snapshot diff are INPUTS to I3, not substitutes for it |
|
|
49
|
+
| Rule | When | Hard check |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| **I1** phase-5d-actually-ran | `--cycle` | Cycle must record ≥1 `phase_5d_runs` entry |
|
|
52
|
+
| **I2** canary-coverage | `--cycle` | Cycle must AI-verify every (target ∪ canary) paper |
|
|
53
|
+
| **I3** verdict-on-truth-not-proxy | `--cycle` | Fires on ANY `phase_5d_runs` verdict ∈ {FAIL, STALE_GOLD} — does not wait for a claimed PASS |
|
|
54
|
+
| **I4** blocked-gold-is-a-cycle-status | `--cycle` | BLOCKED-NEEDS-GOLD is a legal cycle status (listed as a warning); blocks run-close (not cycle) |
|
|
55
|
+
| **I5** corpus-sweep-not-stale | `--cycle` | Sweep must have run within last 3 cycles |
|
|
56
|
+
| **I6** no-open-canary-findings-at-close | `--close` | Run-close requires zero open canary findings + zero BLOCKED canary papers (MUST-with-override) |
|
|
57
|
+
| **I7** deterministic-metric-is-not-a-verdict | `--cycle` | Idempotency / char-ratio / snapshot diff are INPUTS to I3, not substitutes for it |
|
|
58
|
+
| **I8** gate-was-actually-called | `--close` | Every cycle 1..current must have invoked `iterate-gate.sh --cycle N` (no skipped gate) |
|
|
59
|
+
| **I9** locator-via-article-finder | `--cycle` | All paper/gold location + retrieval goes through article-finder (`locator_via`/`gt_via` ∈ cache-check/corpus-query/ai-gold.get/ai-gold.check/generate-gold) — never direct `find`/`glob`/path reads of `test-pdfs/` or `ai_gold/` |
|
|
60
|
+
| **I10** artifact-existence | `--cycle` | Each `phase_5d_runs` entry's rendered file must exist and its sha match `rendered_sha` |
|
|
61
|
+
| **I11** gold-sha-matches-cache | `--cycle` | Each entry's `gold_sha` must match the sha of the cached gold it was verified against |
|
|
62
|
+
| **I12** lesson-readback | `--cycle` / `--close` | `--cycle`: a this-run `lesson_readback` trail must exist (MUST). `--close`: each surfaced card colliding with `files_touched`/`commands_run` must be in `lessons_applied` (MUST-with-override) |
|
|
58
63
|
|
|
59
64
|
**The two mandatory gate calls (cannot be skipped):**
|
|
60
65
|
|
|
@@ -182,94 +187,7 @@ Before any iteration, establish:
|
|
|
182
187
|
|
|
183
188
|
**Per-cycle self-check (Phase 9 / Verification Checklist):** every cycle must be able to answer "what did I parallelize via subagents this cycle, and what did I do inline that could have been parallel?" If the honest answer is "I did N independent things serially," that is a logged process miss — fix it next cycle.
|
|
184
189
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
### Safety checklist — must pass ALL 4 before parallelizing
|
|
188
|
-
|
|
189
|
-
1. **No shared file state.** Each parallel unit must write to a distinct output path (e.g., `tmp/<paper>_gold.md` for paper A vs `tmp/<paper>_gold.md` for paper B — different paths). Never have two agents writing the same file.
|
|
190
|
-
2. **No shared git state.** Never run two parallel agents that modify git (commits, tags, branches, pushes). Git operations are sequential.
|
|
191
|
-
3. **No sequential dependency.** Agent B does not consume an artifact agent A produces in the same fan-out batch. If A→B, run sequentially.
|
|
192
|
-
4. **Self-contained briefs.** Each subagent prompt is a complete, standalone instruction set — absolute paths, no references to "the prior conversation", no implicit context.
|
|
193
|
-
|
|
194
|
-
If ANY checklist item fails, run sequentially.
|
|
195
|
-
|
|
196
|
-
### Where to parallelize (and where to use `run_in_background: true`)
|
|
197
|
-
|
|
198
|
-
| Phase / step | Parallel? | How | Background? |
|
|
199
|
-
|---|---|---|---|
|
|
200
|
-
| **Phase 2 broad-read** — render 8-10 sample papers from publishers | YES | One `Bash` subprocess per paper OR one `Agent` per paper-cluster | Foreground for ≤4; background for more |
|
|
201
|
-
| **Phase 5d gold-extraction** — DELEGATED to `article-finder generate-gold`; docpluck-iterate does NOT generate golds | N/A | `article-finder` owns extraction + its own parallelization | N/A |
|
|
202
|
-
| **Phase 5d verification** — compare rendered.md ↔ gold for each affected paper | YES | One `Agent` per paper (independent inputs) | **Background** (1-2 min each) |
|
|
203
|
-
| **Phase 5d cross-paper sweep** — corpus-level pattern detection on 5 papers | YES, but only ONE agent for the whole sweep | Single agent reading 5 paper pairs and emitting a corpus-level findings list | Foreground (one call, ~3-4 min) |
|
|
204
|
-
| **Diagnostic artifact capture** — `pdftotext` + `extract_pdf_structured` per paper | YES | One `Bash` per paper | Foreground (each <5s) |
|
|
205
|
-
| **Phase 5b broad pytest** — independent of Phase 5d verification | YES | `Bash` with `run_in_background: true` | Background; check via `Monitor` |
|
|
206
|
-
| **Phase 5c 26-paper baseline** — independent of Phase 5d | YES | `Bash` with `run_in_background: true` | Background; ~10 min |
|
|
207
|
-
| **Phase 6c rendered ↔ tables-tab parity** — across affected papers | YES | One `Bash` per paper | Foreground; each <5s |
|
|
208
|
-
| **Phase 8 Tier-3 prod parity** — across affected papers (POST-deploy) | YES | One `Bash` curl per paper | Foreground; each <10s |
|
|
209
|
-
| **Phase 7 release** — version bump + commit + tag + push + auto-bump merge | **NO** | Sequential git operations | N/A |
|
|
210
|
-
| **Phase 4 library fix** — code edits | **NO** | Orchestrator holds architectural context | N/A |
|
|
211
|
-
| **/docpluck-cleanup, /docpluck-review, /docpluck-deploy** — meta-skill chain | **NO** to running 2 at once | Each is sequential per its own internal logic | Foreground; chain them |
|
|
212
|
-
|
|
213
|
-
### Concrete fan-out patterns to use
|
|
214
|
-
|
|
215
|
-
**Pattern A — obtain golds, then fan-out VERIFICATION for affected papers (typical Phase 5d):**
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
1. Identify N affected papers for this cycle.
|
|
219
|
-
2. For each paper: resolve the canonical key and `ai-gold.py check` the shared cache.
|
|
220
|
-
On a miss, gold generation is DELEGATED to `article-finder generate-gold` — docpluck
|
|
221
|
-
NEVER dispatches its own gold-extraction subagent (see Phase 5d Step 1; 2026-05-16
|
|
222
|
-
directive). Copy each `reading` view to `tmp/<paper>_gold.md`.
|
|
223
|
-
3. While golds are obtained, render the affected papers at the working-tree version
|
|
224
|
-
via a single `Bash` script that renders them in sequence (Camelot is not thread-safe;
|
|
225
|
-
keep render serial).
|
|
226
|
-
4. As each gold is ready, optionally dispatch its verifier Agent immediately (background).
|
|
227
|
-
Or wait for all golds, then dispatch all verifiers in a single multi-tool-call message.
|
|
228
|
-
5. Aggregate verdicts as they return; queue defects per rule 0e.
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
**Pattern B — background long-running tasks during planning:**
|
|
232
|
-
|
|
233
|
-
```
|
|
234
|
-
1. Kick off the 26-paper baseline (`Bash` with run_in_background=true).
|
|
235
|
-
2. Kick off the broad pytest (`Bash` with run_in_background=true).
|
|
236
|
-
3. While both run, do Phase 3 TRIAGE re-read + Phase 4 code edit planning.
|
|
237
|
-
4. By the time you need 5b/5c results, they're already done.
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
**Pattern C — corpus sweep with a single agent:**
|
|
241
|
-
|
|
242
|
-
```
|
|
243
|
-
For the every-3rd-cycle corpus sweep, do NOT fan out 5 separate agents.
|
|
244
|
-
Use ONE agent given paths to 5 paper pairs and ask for a corpus-level
|
|
245
|
-
findings list. This produces a coherent ranking; 5 independent agents
|
|
246
|
-
would each produce a local list and the orchestrator would have to
|
|
247
|
-
merge them by hand.
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Anti-patterns to avoid
|
|
251
|
-
|
|
252
|
-
| Anti-pattern | Why it's wrong |
|
|
253
|
-
|---|---|
|
|
254
|
-
| "Dispatch 10 agents to each fix a different defect" | Multiple agents editing the same library code → merge conflicts, lost work. Orchestrator does fixes. |
|
|
255
|
-
| "Dispatch parallel agents to bump version" | Two agents racing on `pyproject.toml` / `__init__.py` / git → broken commits. |
|
|
256
|
-
| "Dispatch parallel agents to render the same paper" | Same `tmp/<paper>_v<version>.md` written twice → race condition. |
|
|
257
|
-
| "Skip the Pattern-A wait and do verify before gold exists" | Verifier needs gold as input; sequential dependency. |
|
|
258
|
-
| "Dispatch a subagent to read the PDF and produce a gold" | docpluck-iterate does NOT generate ground truth — generation is delegated to `article-finder generate-gold` (one producer, one protocol; 2026-05-16 directive). A local extraction subagent re-forks the gold. |
|
|
259
|
-
| "Use multiple agents for a single small task" | Subagent dispatch has fixed overhead (~30s). For tasks <60s, do it inline. |
|
|
260
|
-
| "Subagents share my conversation context" | They DON'T. Each subagent prompt must be self-contained — give absolute paths, restate the goal, restate the discipline. |
|
|
261
|
-
|
|
262
|
-
### When in doubt
|
|
263
|
-
|
|
264
|
-
Default to **sequential** when:
|
|
265
|
-
- You're not sure if outputs collide.
|
|
266
|
-
- The task takes <1 minute (overhead > benefit).
|
|
267
|
-
- The task modifies global state (git, env, settings).
|
|
268
|
-
|
|
269
|
-
Default to **parallel** when:
|
|
270
|
-
- 3+ independent items with the same pattern (papers, sections, checks).
|
|
271
|
-
- Each item takes ≥2 minutes.
|
|
272
|
-
- Each item has a distinct output path.
|
|
190
|
+
**Operational detail — load on demand before fanning out:** [references/subagent-parallelization.md](references/subagent-parallelization.md) holds the 4-item safety checklist (all must pass or run sequentially), the per-phase where-to-parallelize + `run_in_background` table, the concrete fan-out patterns (A: golds-then-verify, B: background-during-planning, C: single-agent corpus sweep), the anti-patterns table, and the when-in-doubt sequential/parallel defaults. Read it whenever you are about to dispatch parallel work.
|
|
273
191
|
|
|
274
192
|
---
|
|
275
193
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Subagent parallelization — operational detail
|
|
2
|
+
|
|
3
|
+
> Extracted from `SKILL.md` (the "Subagent parallelization" MANDATE section) on 2026-06-15 to keep the SKILL under the size guideline. The MANDATE itself, the per-cycle self-check, and the provenance stay inline in SKILL.md; this file holds the safety checklist, the where-to-parallelize table, the concrete fan-out patterns, the anti-patterns, and the when-in-doubt defaults. Load on demand whenever you are about to fan out work.
|
|
4
|
+
|
|
5
|
+
> **Added 2026-05-14 by user directive, RE-STATED 2026-05-15** ("use subagents to optimize the whole process whenever possible"). The re-statement means the directive slipped — treat this as a HARD MANDATE, not advice.
|
|
6
|
+
|
|
7
|
+
**MANDATE (restated):** Before doing ANY batch of 2+ independent units of work inline, STOP and ask: *"could N parallel background subagents do this instead?"* If yes and the safety checklist passes, you MUST fan out. Doing serially in the orchestrator's own context what subagents could do in parallel is a process defect — it is slow and burns the orchestrator's context window. This applies to: per-paper renders, gold extractions, AI-gold verifications, broad-read reader-passes, diagnostic captures, cross-paper sweeps. The orchestrator keeps ONLY: code edits, git/release operations, version bumps, and <60s one-offs.
|
|
8
|
+
|
|
9
|
+
Iteration is dominated by I/O + AI work that is naturally parallel across papers. The orchestrator (the `docpluck-iterate` skill) MUST aggressively fan out to `Agent` subagents whenever there are 2+ independent units of work, when the work passes the safety checklist below.
|
|
10
|
+
|
|
11
|
+
## Safety checklist — must pass ALL 4 before parallelizing
|
|
12
|
+
|
|
13
|
+
1. **No shared file state.** Each parallel unit must write to a distinct output path (e.g., `tmp/<paper>_gold.md` for paper A vs `tmp/<paper>_gold.md` for paper B — different paths). Never have two agents writing the same file.
|
|
14
|
+
2. **No shared git state.** Never run two parallel agents that modify git (commits, tags, branches, pushes). Git operations are sequential.
|
|
15
|
+
3. **No sequential dependency.** Agent B does not consume an artifact agent A produces in the same fan-out batch. If A→B, run sequentially.
|
|
16
|
+
4. **Self-contained briefs.** Each subagent prompt is a complete, standalone instruction set — absolute paths, no references to "the prior conversation", no implicit context.
|
|
17
|
+
|
|
18
|
+
If ANY checklist item fails, run sequentially.
|
|
19
|
+
|
|
20
|
+
## Where to parallelize (and where to use `run_in_background: true`)
|
|
21
|
+
|
|
22
|
+
| Phase / step | Parallel? | How | Background? |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| **Phase 2 broad-read** — render 8-10 sample papers from publishers | YES | One `Bash` subprocess per paper OR one `Agent` per paper-cluster | Foreground for ≤4; background for more |
|
|
25
|
+
| **Phase 5d gold-extraction** — DELEGATED to `article-finder generate-gold`; docpluck-iterate does NOT generate golds | N/A | `article-finder` owns extraction + its own parallelization | N/A |
|
|
26
|
+
| **Phase 5d verification** — compare rendered.md ↔ gold for each affected paper | YES | One `Agent` per paper (independent inputs) | **Background** (1-2 min each) |
|
|
27
|
+
| **Phase 5d cross-paper sweep** — corpus-level pattern detection on 5 papers | YES, but only ONE agent for the whole sweep | Single agent reading 5 paper pairs and emitting a corpus-level findings list | Foreground (one call, ~3-4 min) |
|
|
28
|
+
| **Diagnostic artifact capture** — `pdftotext` + `extract_pdf_structured` per paper | YES | One `Bash` per paper | Foreground (each <5s) |
|
|
29
|
+
| **Phase 5b broad pytest** — independent of Phase 5d verification | YES | `Bash` with `run_in_background: true` | Background; check via `Monitor` |
|
|
30
|
+
| **Phase 5c 26-paper baseline** — independent of Phase 5d | YES | `Bash` with `run_in_background: true` | Background; ~10 min |
|
|
31
|
+
| **Phase 6c rendered ↔ tables-tab parity** — across affected papers | YES | One `Bash` per paper | Foreground; each <5s |
|
|
32
|
+
| **Phase 8 Tier-3 prod parity** — across affected papers (POST-deploy) | YES | One `Bash` curl per paper | Foreground; each <10s |
|
|
33
|
+
| **Phase 7 release** — version bump + commit + tag + push + auto-bump merge | **NO** | Sequential git operations | N/A |
|
|
34
|
+
| **Phase 4 library fix** — code edits | **NO** | Orchestrator holds architectural context | N/A |
|
|
35
|
+
| **/docpluck-cleanup, /docpluck-review, /docpluck-deploy** — meta-skill chain | **NO** to running 2 at once | Each is sequential per its own internal logic | Foreground; chain them |
|
|
36
|
+
|
|
37
|
+
## Concrete fan-out patterns to use
|
|
38
|
+
|
|
39
|
+
**Pattern A — obtain golds, then fan-out VERIFICATION for affected papers (typical Phase 5d):**
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
1. Identify N affected papers for this cycle.
|
|
43
|
+
2. For each paper: resolve the canonical key and `ai-gold.py check` the shared cache.
|
|
44
|
+
On a miss, gold generation is DELEGATED to `article-finder generate-gold` — docpluck
|
|
45
|
+
NEVER dispatches its own gold-extraction subagent (see Phase 5d Step 1; 2026-05-16
|
|
46
|
+
directive). Copy each `reading` view to `tmp/<paper>_gold.md`.
|
|
47
|
+
3. While golds are obtained, render the affected papers at the working-tree version
|
|
48
|
+
via a single `Bash` script that renders them in sequence (Camelot is not thread-safe;
|
|
49
|
+
keep render serial).
|
|
50
|
+
4. As each gold is ready, optionally dispatch its verifier Agent immediately (background).
|
|
51
|
+
Or wait for all golds, then dispatch all verifiers in a single multi-tool-call message.
|
|
52
|
+
5. Aggregate verdicts as they return; queue defects per rule 0e.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Pattern B — background long-running tasks during planning:**
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
1. Kick off the 26-paper baseline (`Bash` with run_in_background=true).
|
|
59
|
+
2. Kick off the broad pytest (`Bash` with run_in_background=true).
|
|
60
|
+
3. While both run, do Phase 3 TRIAGE re-read + Phase 4 code edit planning.
|
|
61
|
+
4. By the time you need 5b/5c results, they're already done.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Pattern C — corpus sweep with a single agent:**
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
For the every-3rd-cycle corpus sweep, do NOT fan out 5 separate agents.
|
|
68
|
+
Use ONE agent given paths to 5 paper pairs and ask for a corpus-level
|
|
69
|
+
findings list. This produces a coherent ranking; 5 independent agents
|
|
70
|
+
would each produce a local list and the orchestrator would have to
|
|
71
|
+
merge them by hand.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Anti-patterns to avoid
|
|
75
|
+
|
|
76
|
+
| Anti-pattern | Why it's wrong |
|
|
77
|
+
|---|---|
|
|
78
|
+
| "Dispatch 10 agents to each fix a different defect" | Multiple agents editing the same library code → merge conflicts, lost work. Orchestrator does fixes. |
|
|
79
|
+
| "Dispatch parallel agents to bump version" | Two agents racing on `pyproject.toml` / `__init__.py` / git → broken commits. |
|
|
80
|
+
| "Dispatch parallel agents to render the same paper" | Same `tmp/<paper>_v<version>.md` written twice → race condition. |
|
|
81
|
+
| "Skip the Pattern-A wait and do verify before gold exists" | Verifier needs gold as input; sequential dependency. |
|
|
82
|
+
| "Dispatch a subagent to read the PDF and produce a gold" | docpluck-iterate does NOT generate ground truth — generation is delegated to `article-finder generate-gold` (one producer, one protocol; 2026-05-16 directive). A local extraction subagent re-forks the gold. |
|
|
83
|
+
| "Use multiple agents for a single small task" | Subagent dispatch has fixed overhead (~30s). For tasks <60s, do it inline. |
|
|
84
|
+
| "Subagents share my conversation context" | They DON'T. Each subagent prompt must be self-contained — give absolute paths, restate the goal, restate the discipline. |
|
|
85
|
+
|
|
86
|
+
## When in doubt
|
|
87
|
+
|
|
88
|
+
Default to **sequential** when:
|
|
89
|
+
- You're not sure if outputs collide.
|
|
90
|
+
- The task takes <1 minute (overhead > benefit).
|
|
91
|
+
- The task modifies global state (git, env, settings).
|
|
92
|
+
|
|
93
|
+
Default to **parallel** when:
|
|
94
|
+
- 3+ independent items with the same pattern (papers, sections, checks).
|
|
95
|
+
- Each item takes ≥2 minutes.
|
|
96
|
+
- Each item has a distinct output path.
|
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.90] — 2026-06-15
|
|
4
|
+
|
|
5
|
+
**RC-1 Step 2 — per-band region-aware two-column re-extraction (ship-dark behind `DOCPLUCK_COLUMN_CORRECT_BANDED`, default OFF).** No `NORMALIZATION_VERSION` change — the default path is byte-identical; the flag only adds reading-order corrections upstream of normalize.
|
|
6
|
+
|
|
7
|
+
The dominant defect on two-column APA papers: pdftotext serializes a page's columns interleaved, so Method/Results/Discussion order scrambles and paragraphs split with their continuations displaced. The Step-1 whole-page corrector (`extract_page_text_columns`, v2.4.82) **cannot reach** these pages — its bilateral y-row gate and full-height gutter strip both reject any page that carries a full-width band (a table row, banner, or wide title) crossing the column centre, which is exactly the failing pages (TRIAGE 2026-06-15: re-rendering with `DOCPLUCK_COLUMN_CORRECT_GENERAL=1` produced byte-near-identical output).
|
|
8
|
+
|
|
9
|
+
**Step 2** (`extract_page_text_banded`, `docpluck/extract_columns.py`) segments a flagged page into horizontal y-BANDS: a band whose central gutter strip is glyph-free across its rows is two-column prose → re-extracted left-then-right; a band with full-width content (table/banner/title) is kept as-is. Bands are reassembled top-to-bottom at glyph-free cut lines (vertically-overlapping bands are merged to full-width so a tall title glyph is never bisected). Applied **only as a fallback** inside `splice_column_corrected_pages` when the whole-page path returns "", and under the **same unconditional word-preservation guard** — a band crop that drops/fabricates a word is rejected and the page kept as-is, so the flag can only ADD a pure reorder, never ship corruption.
|
|
10
|
+
|
|
11
|
+
**Validation (2026-06-15).** Corpus word-preservation scan across 4 two-column papers (71 flagged pages): every accepted page is a pure reorder; the 6 residual band-cut clips are all guard-rejected (no corruption). Production smoke (flag ON) corrects ~44 pages across 5 papers, all word-safe; flag OFF is byte-identical. **AI-verify vs article-finder reading golds** (Sonnet subagents) on the two heaviest papers (`chan_feldman_2025_cogemo`, `chandrashekar_2023_mp`): both **ON_BETTER** — Procedure/Manipulations ordering and the Power-analysis paragraph split fixed (chan); `## Results` heading restored and column-token orphans suppressed (chandrashekar); **zero text-loss, zero hallucination, zero regression** vs flag OFF. New regression `tests/test_rc1_banded_column_real_pdf.py` (synthetic-layout geometry units + real-PDF word-preservation + ship-dark-default). Touched-path suite 302 passed; 26-paper baseline 26/26 (flag OFF).
|
|
12
|
+
|
|
13
|
+
**Known residual (documented, not regressions):** ~6/71 flagged pages still clip a single word at a band cut and are guard-rejected (stay interleaved — no worse than today); hard title+sidebar pages (e.g. PSPB p1) degrade to full-width (no de-interleave). These are the remaining Step-2 refinement targets before the default can be flipped.
|
|
14
|
+
|
|
15
|
+
## [2.4.89] — 2026-06-15
|
|
16
|
+
|
|
17
|
+
**W0h — recover dropped-minus statistical coefficients from the LAYOUT channel (the no-CI residual W0g cannot reach).** `NORMALIZATION_VERSION` 1.9.34 → 1.9.35.
|
|
18
|
+
|
|
19
|
+
Surfaced by `/docpluck-iterate` Phase-5d (B7 canary `ar_apa_j_jesp_2009_12_011`). On tight-kerned PDFs that draw the U+2212 minus in a dedicated symbol font, pdftotext **drops the glyph entirely**, so a body-prose coefficient `β = −.022, t(87) = .17, ns` reaches the text channel as `b = .022` — a **silent sign flip that inverts the statistical conclusion**. The existing W0g recovery needs a confidence interval to prove the sign; these betas report only a t-value, so W0g cannot reach them.
|
|
20
|
+
|
|
21
|
+
**Root cause + recovery.** The dropped minus survives in the **layout channel** (pdfplumber) as an unmapped `(cid:N)` glyph in a symbol font (here `AdvP4C4E74`), immediately touching the coefficient. `normalize.recover_dropped_minus_via_layout` (W0h) detects this in the `<stat> = <minus><coef>` operator slot — an unmapped glyph whose left neighbour is `=` and whose right neighbour is a coefficient — and re-inserts the minus into the pdftotext text. Keyed on the **structural signature** (not paper/font identity): the `=`-anchor excludes `5.2 ± 0.3` (glyph between two numbers) so a dropped `±`/`≈`/dagger can never be mistaken for a sign; it flips only coefficients the layout proves negative, only as many times as found. Threaded through a dedicated `dropped_minus_layout` param (`render → extract_sections → normalize_text`) so the section pipeline's text-channel-only contract is preserved (F0 stays off). On `ar_apa` it recovers `β = −.022 / −.88 / −.428` and leaves the genuinely-positive `.48` untouched. Blast radius: of the 5 onboarded canary papers only `ar_apa` flips (exactly its 3 layout-visible negatives); the other 4 are byte-identical no-ops.
|
|
22
|
+
|
|
23
|
+
**Documented limitation (NOT fixable in text+layout).** `ar_apa` `β = −.245` is drawn as **painted pixels** — its minus is absent from pdftotext AND pdfplumber chars/lines/rects/curves AND pdfminer's raw layer (proven). Only OCR could recover it; W0h deliberately leaves it rather than guessing. See `TODO.md` R5 Path 1.
|
|
24
|
+
|
|
25
|
+
New regression `tests/test_dropped_minus_layout_recovery_real_pdf.py` (4 synthetic-layout unit tests pinning the `=`-slot guard + the no-flip-after-a-number guard, and 1 real-PDF render test). Minus/normalize/render/sections suite: 148 passed.
|
|
26
|
+
|
|
27
|
+
## [2.4.88] — 2026-06-13
|
|
28
|
+
|
|
29
|
+
**Camelot temp-file cleanup is best-effort — stop silently dropping every table on Windows.** (No `NORMALIZATION_VERSION` change — table channel only; output on POSIX/prod is unchanged.)
|
|
30
|
+
|
|
31
|
+
Surfaced by `/docpluck-qa` (corpus render verifier, tag H) while validating v2.4.86/87: `efendic_2022_affect` rendered with **0** of its **4** in-body HTML tables. Reproduced on HEAD too (pre-existing, not from the layout fixes).
|
|
32
|
+
|
|
33
|
+
**Root cause (Windows-only).** `tables/camelot_extract.py::extract_tables_camelot` writes the PDF to a `NamedTemporaryFile(delete=False)`, runs Camelot, then unlinks the temp file in a `finally` block. Under **camelot-py 2.0.0** on Windows, Camelot still holds the temp-file handle open at that point, so `Path(tmp_path).unlink()` raises `PermissionError [WinError 32]`. That exception propagated out of `extract_tables_camelot` and was swallowed by `extract_structured`'s broad `except Exception` (→ `method="…camelot_failed"`, `tables=[]`) — so **every** paper lost **all** Camelot tables on Windows, even though extraction itself succeeded. POSIX permits unlinking an open file, so prod/Linux/Railway never hit this; it was invisible outside Windows dev. (The drift to the camelot-py 2.0.0 major came via the unbounded `camelot-py[cv]>=0.11.0` pin.)
|
|
34
|
+
|
|
35
|
+
**Fix.** The `finally` cleanup now swallows `OSError` (best-effort temp removal; the OS temp dir reclaims the file). Camelot table extraction is restored on Windows: `efendic_2022_affect` 0 → 5 tables (3 with HTML), corpus verifier PASS. New regression `tests/test_camelot_temp_cleanup.py` monkeypatches `Path.unlink` to raise and asserts `extract_tables_camelot` returns a list instead of propagating. Table/structured suite: 90 passed, 1 skipped.
|
|
36
|
+
|
|
37
|
+
## [2.4.87] — 2026-06-13
|
|
38
|
+
|
|
39
|
+
**F0 sources the body from the text channel — strip header/footer/footnote lines from pdftotext, never rebuild the body from spans.** `NORMALIZATION_VERSION` 1.9.33 → 1.9.34.
|
|
40
|
+
|
|
41
|
+
Follow-up #1 from `docs/HANDOFF_2026-06-13_sciencearena_grobid_liteparse.md` — the deeper, L-001-preferred fix for the residual the v2.4.86 word-gluing patch left behind.
|
|
42
|
+
|
|
43
|
+
**Root cause (architectural).** When `layout=` is supplied, the F0 step (`_f0_strip_running_and_footnotes`) was **rebuilding the entire body from `TextSpan.text`** and discarding the pdftotext `raw_text` the caller passed in (used only to locate footnote offsets). That rebuild-from-spans is what made *both* the v2.4.86 word-gluing (pdfplumber's char stream drops the inter-word space glyph) and the residual two-column interleaving (`_chars_to_spans` groups chars by y-coordinate only, so left+right columns at the same y merge into one span — e.g. `how www.cambridge.org/cns we can pay for it`) possible. It violates the documented text-channel/layout-channel split (CLAUDE.md, LESSONS L-001/L-007): the body must come from `extract_pdf` (pdftotext, which already infers spaces from the x-gap *and* serialises columns in reading order), and the layout channel should be used only to *identify* which lines are running headers/footers/footnotes.
|
|
44
|
+
|
|
45
|
+
**Fix.** `_f0_strip_running_and_footnotes` now keeps the exact same span-based classification (repeating-header detection, body-y-band header/footnote position rules, font-size footnote test, table-region guard) but uses it only to build **strip-key sets**. It then walks the pdftotext `raw_text` line by line (splitting on both `\n` and the bare `\f` page separator, keeping separators so footnote byte-offsets stay exact), drops header/footer lines, moves footnote lines to the `\n\f\f\n` appendix, and keeps the rest **in pdftotext order with pdftotext spacing**. Keyed on the structural signature (content-key membership), so it generalises to any PDF; strip-keys that are pure-numeric or shorter than 4 chars are excluded (page numbers etc. are handled downstream by P0/P0r/R2 and are absent from the JATS body), removing the only realistic false-strip vector. The body is now provably a *line-subsequence* of the text channel — F0 only deletes, never reorders or merges.
|
|
46
|
+
|
|
47
|
+
**Validation (ScienceArena pdf-text-fidelity-v1 held-out PMC corpus, 30 papers, token-F1 vs JATS gold).** Mean token-F1 **0.745 → 0.776** (above raw pdftotext 0.750; on par with normalized-pdftotext 0.767, and above the per-paper cases where F0's header/footnote stripping adds lift). Primary metric (`0.5·levenshtein + 0.5·token_f1`, the leaderboard rank key) **0.559 → 0.666** — the large jump comes from levenshtein (~0.30 → ~0.60) now that column reading-order is correct. Zero catastrophic-zero papers (unchanged). `_join_chars_with_spaces` (v2.4.86) is retained — it is now load-bearing for span-text key matching and for the sections/tables layout consumers.
|
|
48
|
+
|
|
49
|
+
New regression tests in `tests/test_normalize_f0_footnote_strip.py`: the F0 body is a line-subsequence of the text channel (a span-rebuild regression reorders/merges and fails), and every kept body line is an exact substring of `raw_text` (spacing comes from the text channel, never a glued span). LESSONS L-007 updated: the architectural follow-up is now done.
|
|
50
|
+
|
|
51
|
+
## [2.4.86] — 2026-06-13
|
|
52
|
+
|
|
53
|
+
**F0 layout body channel — reinsert inter-word spaces from the x-gap (stop gluing words on tight-kerned PDFs).** `NORMALIZATION_VERSION` 1.9.32 → 1.9.33.
|
|
54
|
+
|
|
55
|
+
Surfaced by `docs/HANDOFF_2026-06-13_sciencearena_grobid_liteparse.md` (ScienceArena pdf-text-fidelity-v1 held-out PMC corpus): on ~16 of 30 real biomedical PDFs docpluck's `normalize_text(..., layout=...)` body scored token-F1 **≈ 0.00** against the JATS gold while raw pdftotext scored 0.7–0.9 — *with a normal character count*. Same characters, zero token overlap: the words were glued (`CNSSpectrums`, `Thebehavioralhealthcarecontinuuminthe`, `UnitedStates`).
|
|
56
|
+
|
|
57
|
+
**Root cause.** When `layout=` is supplied, the F0 step (`_f0_strip_running_and_footnotes`) rebuilds the body from `TextSpan.text`. Span text was built in `extract_layout._chars_to_spans` by `"".join(c["text"] for c in line)` — a naive character concatenation with **no x-gap handling** (the function's own docstring claimed x-gap splitting that was never implemented). pdfplumber's `chars` stream omits the inter-word space glyph on tight-kerned PDFs (Cambridge journals, many two-column layouts) — pdftotext infers those spaces from the horizontal gap, but the raw char stream does not carry them. So on those PDFs the entire layout body collapsed to space-ratio ~0.005 (vs ~0.13 for the same text via pdftotext), and any consumer using the layout body channel (the recommended body-fidelity path since v2.4.83) silently emitted unspaced text. This is the long-standing `feedback_pdfplumber_extract_words_unreliable` failure mode — "always carry a char-level absolute-x-gap fallback" — which had never been applied to span text.
|
|
58
|
+
|
|
59
|
+
**Fix.** New `extract_layout._join_chars_with_spaces` reinserts a space between consecutive glyphs when the horizontal gap exceeds a font-relative threshold (`gap > 0.20·font_size`), keyed on the structural signature (x-gap) rather than any paper identity, so it generalizes to every tight-kerned PDF. `0.20·size` reproduces pdftotext/JATS spacing to within ~0.2% space-density on the Cambridge/PMC corpus (PMC13064744 token-F1 0.00 → 0.86; space-ratio 0.0053 → 0.1282; full 30-paper held-out PMC token-F1 mean ~0.34 → 0.747 with zero catastrophic-zero papers, was 16/30). The guard never doubles an existing space and never splits within a tightly-kerned word. One known residual remains (documented, not addressed here): `_chars_to_spans` still does not split a line at a column gutter, so a line spanning two columns is merged — a smaller reading-order issue handled by the text channel, tracked for a follow-up.
|
|
60
|
+
|
|
61
|
+
New regression tests in `tests/test_extract_layout.py`: x-gap space insertion on a word gap, no-split within a tight word, no double-space across an explicit space glyph, and a real-render assertion that normal loosely-kerned text keeps its word spaces.
|
|
62
|
+
|
|
3
63
|
## [2.4.85] — 2026-06-12
|
|
4
64
|
|
|
5
65
|
**Harvard name-year reference splitting (D1) + page-break reference stitch & category-label running-header strip (D2).** `NORMALIZATION_VERSION` 1.9.31 → 1.9.32.
|
|
@@ -156,6 +156,102 @@ Demo showing the difference: `docs/superpowers/plans/spot-checks/splice-spike/ht
|
|
|
156
156
|
|
|
157
157
|
---
|
|
158
158
|
|
|
159
|
+
## L-007 — Layout span text MUST reinsert inter-word spaces from the x-gap (never `"".join(chars)`)
|
|
160
|
+
|
|
161
|
+
### The recurring mistake
|
|
162
|
+
When a downstream step rebuilds text from the **layout channel** (`extract_pdf_layout`
|
|
163
|
+
→ `TextSpan.text`), it is tempting to construct a line's text by concatenating
|
|
164
|
+
pdfplumber's per-character `chars`: `"".join(c["text"] for c in line)`. This is wrong.
|
|
165
|
+
pdfplumber's char stream **does not carry the inter-word space glyph** on tight-kerned
|
|
166
|
+
PDFs (Cambridge journals, many two-column layouts) — pdftotext *infers* those spaces
|
|
167
|
+
from the horizontal gap, but the raw chars do not. So the naive join glues whole lines
|
|
168
|
+
into one token (`CNSSpectrums`, `Thebehavioralhealthcarecontinuuminthe`).
|
|
169
|
+
|
|
170
|
+
### What it broke (2026-06-13, v2.4.86)
|
|
171
|
+
`extract_layout._chars_to_spans` built span text with the naive join. Since v2.4.83 the
|
|
172
|
+
F0 step (`normalize_text(..., layout=...)`) rebuilds the **body** from spans, so on
|
|
173
|
+
~16 of 30 real biomedical PDFs the body collapsed to space-ratio ~0.005 (vs ~0.13 via
|
|
174
|
+
pdftotext) — token-F1 ≈ 0.00 against the JATS gold *with a normal character count*.
|
|
175
|
+
The defect was invisible to char-ratio/word-delta metrics (the chars are all there;
|
|
176
|
+
only the spaces are gone) and was surfaced by ScienceArena's `pdf-text-fidelity-v1`
|
|
177
|
+
held-out PMC set, where raw pdftotext beat docpluck. The function's own docstring even
|
|
178
|
+
*claimed* x-gap handling that had never been implemented.
|
|
179
|
+
|
|
180
|
+
### The rule
|
|
181
|
+
- Any reconstruction of text from layout chars MUST reinsert a space when the
|
|
182
|
+
horizontal gap between consecutive glyphs exceeds a **font-relative** threshold
|
|
183
|
+
(`gap > 0.20·font_size` reproduces pdftotext/JATS spacing to ~0.2% space-density).
|
|
184
|
+
Use `extract_layout._join_chars_with_spaces`; never `"".join(chars)`.
|
|
185
|
+
- This is the in-repo instance of memory `feedback_pdfplumber_extract_words_unreliable`
|
|
186
|
+
("always carry a char-level absolute-x-gap fallback"). It applies to span text, and
|
|
187
|
+
to any future layout-channel text reconstruction (sections annotators, tables).
|
|
188
|
+
- **A space-density collapse is the canary.** When a layout-derived body has space-ratio
|
|
189
|
+
far below the pdftotext text for the same PDF (e.g. < 0.05 vs ~0.13), suspect glued
|
|
190
|
+
word boundaries before anything else — it is not "dropped text."
|
|
191
|
+
- Architecturally, the body is sourced from `extract_pdf` (pdftotext, which already has
|
|
192
|
+
correct spaces AND correct column reading-order) and the layout channel is used only
|
|
193
|
+
to *identify* lines to strip (running headers / footnotes), per L-001's
|
|
194
|
+
text-channel/layout-channel split. **Done in v2.4.87** (`NORMALIZATION_VERSION`
|
|
195
|
+
1.9.34): `_f0_strip_running_and_footnotes` no longer rebuilds the body from spans — it
|
|
196
|
+
builds strip-key sets from the span classification and deletes the matching lines from
|
|
197
|
+
the pdftotext `raw_text`, keeping the rest in pdftotext order/spacing. This closed the
|
|
198
|
+
residual two-column interleaving (`how www.cambridge.org/cns we can pay for it`) that
|
|
199
|
+
the v2.4.86 spacing patch left behind, and lifted the held-out PMC token-F1 mean
|
|
200
|
+
0.745 → 0.776 (primary 0.559 → 0.666). The F0 body is now provably a line-subsequence
|
|
201
|
+
of the text channel (guarded by
|
|
202
|
+
`tests/test_normalize_f0_footnote_strip.py::test_f0_body_is_a_line_subsequence_of_the_text_channel`).
|
|
203
|
+
Rebuilding the whole body from spans is the smell that made the gluing bug possible; do
|
|
204
|
+
not reintroduce it.
|
|
205
|
+
|
|
206
|
+
Cite: `docpluck/normalize.py` (`_f0_strip_running_and_footnotes`),
|
|
207
|
+
`docpluck/extract_layout.py` (`_join_chars_with_spaces`),
|
|
208
|
+
`tests/test_normalize_f0_footnote_strip.py`, `tests/test_extract_layout.py`,
|
|
209
|
+
CHANGELOG 2026-06-13 (v2.4.86 spacing, v2.4.87 body-source).
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## L-008 — Temp-file cleanup must be best-effort; a broad `except` around extraction will swallow a cleanup error into total silent failure
|
|
214
|
+
|
|
215
|
+
### The recurring mistake
|
|
216
|
+
A function writes input to a `NamedTemporaryFile(delete=False)`, runs an external
|
|
217
|
+
library, and unlinks the temp file in a `finally` block. The caller wraps the whole
|
|
218
|
+
call in `except Exception: return []`. If the unlink raises, the exception escapes the
|
|
219
|
+
`finally`, the caller's broad `except` swallows it, and the **successful** extraction
|
|
220
|
+
result is discarded — a total, silent, output-zeroing failure that looks like "the
|
|
221
|
+
tool found nothing."
|
|
222
|
+
|
|
223
|
+
### What it broke (2026-06-13, v2.4.88)
|
|
224
|
+
`tables/camelot_extract.py::extract_tables_camelot` unlinked its temp PDF in a
|
|
225
|
+
`finally`. Under **camelot-py 2.0.0 on Windows**, Camelot still held the file handle
|
|
226
|
+
open, so `Path(tmp_path).unlink()` raised `PermissionError [WinError 32]`. The
|
|
227
|
+
exception propagated into `extract_structured`'s `except Exception` →
|
|
228
|
+
`camelot_failed`, `tables=[]` — so **every** paper lost **all** tables on Windows even
|
|
229
|
+
though Camelot had extracted them fine. POSIX allows unlinking an open file, so
|
|
230
|
+
prod/Linux/Railway never saw it; it was invisible outside Windows dev and only caught
|
|
231
|
+
by the corpus render verifier (tag H, 4 tables → 0).
|
|
232
|
+
|
|
233
|
+
### The rules
|
|
234
|
+
1. **Temp-file cleanup is always best-effort.** Wrap `unlink`/`rmtree` of a temp path
|
|
235
|
+
in `try/except OSError: pass` (or use a tempdir context that tolerates it). A
|
|
236
|
+
failure to delete scratch is never worth failing — or silently zeroing — the real
|
|
237
|
+
result. The OS temp dir reclaims it.
|
|
238
|
+
2. **A platform-specific cleanup failure is invisible on the platform you test prod on.**
|
|
239
|
+
POSIX `unlink`-while-open succeeds; Windows refuses. If extraction works in CI/Linux
|
|
240
|
+
but returns empty locally on Windows (or vice-versa), suspect a `finally`-block
|
|
241
|
+
cleanup raising under a held file handle before suspecting the extractor.
|
|
242
|
+
3. **A broad `except Exception` around a subprocess/library call hides this class.**
|
|
243
|
+
When such a wrapper exists, the inner function must not raise on cleanup — otherwise
|
|
244
|
+
"tool failed, 0 results" silently conflates real failure with a cosmetic cleanup error.
|
|
245
|
+
4. **Pin breaking-major dependencies.** The drift to camelot-py 2.0.0 came through the
|
|
246
|
+
unbounded `camelot-py[cv]>=0.11.0` pin. Settled-on deps should carry a tested upper
|
|
247
|
+
bound (see memory `feedback_no_silent_optional_deps`); a major bump is opt-in + re-verified.
|
|
248
|
+
|
|
249
|
+
Cite: `docpluck/tables/camelot_extract.py` (`extract_tables_camelot` `finally`),
|
|
250
|
+
`docpluck/extract_structured.py` (the broad `except`), `tests/test_camelot_temp_cleanup.py`,
|
|
251
|
+
CHANGELOG 2026-06-13 (v2.4.88).
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
159
255
|
## When to add a new lesson here
|
|
160
256
|
|
|
161
257
|
Add a lesson when:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docpluck
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.90
|
|
4
4
|
Summary: PDF, DOCX, and HTML text extraction and normalization for academic papers
|
|
5
5
|
Project-URL: Homepage, https://docpluck.app
|
|
6
6
|
Project-URL: Documentation, https://docpluck.app/api-docs
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
23
23
|
Classifier: Topic :: Text Processing :: General
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
|
-
Requires-Dist: camelot-py[cv]
|
|
25
|
+
Requires-Dist: camelot-py[cv]<3.0,>=0.11.0
|
|
26
26
|
Requires-Dist: pdfplumber>=0.11.0
|
|
27
27
|
Provides-Extra: all
|
|
28
28
|
Requires-Dist: beautifulsoup4>=4.12.0; extra == 'all'
|