|
53 | 53 | ) |
54 | 54 | ROADMAP_DOCUMENTATION_TRUTH = re.compile(r'documentation-current-truth', re.IGNORECASE) |
55 | 55 | ROADMAP_DOC_VS_HEAD = re.compile(r'doc-vs-head', re.IGNORECASE) |
| 56 | +CURRENT_DIRECTION_FORBIDDEN_README = ('aspirational adoption',) |
| 57 | +CURRENT_DIRECTION_FORBIDDEN_INDEX_CURRENT = ( |
| 58 | + 'What can a daily user trust today?', |
| 59 | + 'Current daily-driver behavior', |
| 60 | + 'Ticket execution order (daily-driver)', |
| 61 | +) |
| 62 | +CURRENT_DIRECTION_FORBIDDEN_ROADMAP = ( |
| 63 | + 'Packaging and adoption', |
| 64 | + 'general-user trust onboarding', |
| 65 | + 'external-facing release channels', |
| 66 | +) |
56 | 67 | ROADMAP_TABLE_SEPARATOR = re.compile(r'^\|[-:\s|]+\|$') |
57 | 68 | ROADMAP_EXIT_COLUMNS = ('Exit Evidence', 'Exit Criteria', 'Risk') |
58 | 69 | ROADMAP_REQUIRED_ROW_FIELDS = ('Owner', 'Status', 'Confidence', 'Next Gate') |
@@ -664,6 +675,90 @@ def validate_roadmap_status(roadmap_text: str) -> list[str]: |
664 | 675 | return errors |
665 | 676 |
|
666 | 677 |
|
| 678 | +def _markdown_section(text: str, heading: str) -> str: |
| 679 | + lines = text.splitlines() |
| 680 | + start_index: int | None = None |
| 681 | + heading_level = len(heading) - len(heading.lstrip('#')) |
| 682 | + for index, line in enumerate(lines): |
| 683 | + if line.strip() == heading: |
| 684 | + start_index = index |
| 685 | + break |
| 686 | + if start_index is None: |
| 687 | + return '' |
| 688 | + |
| 689 | + end_index = len(lines) |
| 690 | + for index in range(start_index + 1, len(lines)): |
| 691 | + line = lines[index] |
| 692 | + if not line.startswith('#'): |
| 693 | + continue |
| 694 | + level = len(line) - len(line.lstrip('#')) |
| 695 | + if level <= heading_level: |
| 696 | + end_index = index |
| 697 | + break |
| 698 | + return '\n'.join(lines[start_index:end_index]) |
| 699 | + |
| 700 | + |
| 701 | +def validate_current_truth_framing( |
| 702 | + *, |
| 703 | + readme_text: str, |
| 704 | + docs_index_text: str, |
| 705 | + roadmap_text: str, |
| 706 | +) -> list[str]: |
| 707 | + """Keep current-truth front doors aligned to owner-operated harness direction.""" |
| 708 | + errors: list[str] = [] |
| 709 | + readme_header = '\n'.join(readme_text.splitlines()[:8]) |
| 710 | + index_current = _markdown_section(docs_index_text, '## Current Truth') |
| 711 | + index_start = _markdown_section(docs_index_text, '## Start Here') |
| 712 | + roadmap_horizons = _markdown_section(roadmap_text, '## Roadmap Horizons') |
| 713 | + roadmap_header = roadmap_text.split('## Purpose', 1)[0] |
| 714 | + |
| 715 | + if 'owner-operator harness-first current direction' not in readme_header: |
| 716 | + errors.append( |
| 717 | + 'README.md direction record must stay owner-operator harness-first.' |
| 718 | + ) |
| 719 | + if 'owner-operator' not in index_start: |
| 720 | + errors.append( |
| 721 | + 'docs/INDEX.md Start Here must frame current use as owner-operated.' |
| 722 | + ) |
| 723 | + if 'owner-operated' not in index_current: |
| 724 | + errors.append( |
| 725 | + 'docs/INDEX.md Current Truth must frame current use as owner-operated.' |
| 726 | + ) |
| 727 | + if 'owner-operator is the current validated persona' not in roadmap_header: |
| 728 | + errors.append( |
| 729 | + 'docs/roadmap-status.md must name owner-operator as the current ' |
| 730 | + 'validated persona.' |
| 731 | + ) |
| 732 | + if 'not current goals' not in roadmap_header: |
| 733 | + errors.append( |
| 734 | + 'docs/roadmap-status.md must state external adoption/hosted expansion ' |
| 735 | + 'are not current goals.' |
| 736 | + ) |
| 737 | + |
| 738 | + for phrase in CURRENT_DIRECTION_FORBIDDEN_README: |
| 739 | + if phrase in readme_header: |
| 740 | + errors.append( |
| 741 | + f'README.md direction record contains outdated phrase {phrase!r}.' |
| 742 | + ) |
| 743 | + for phrase in CURRENT_DIRECTION_FORBIDDEN_INDEX_CURRENT: |
| 744 | + if phrase in index_start or phrase in index_current: |
| 745 | + errors.append( |
| 746 | + f'docs/INDEX.md current-truth sections contain outdated phrase ' |
| 747 | + f'{phrase!r}.' |
| 748 | + ) |
| 749 | + for phrase in CURRENT_DIRECTION_FORBIDDEN_ROADMAP: |
| 750 | + if phrase in roadmap_horizons: |
| 751 | + errors.append( |
| 752 | + f'docs/roadmap-status.md Roadmap Horizons contain outdated phrase ' |
| 753 | + f'{phrase!r}.' |
| 754 | + ) |
| 755 | + |
| 756 | + return errors |
| 757 | + |
| 758 | + |
| 759 | +validate_current_direction_claims = validate_current_truth_framing |
| 760 | + |
| 761 | + |
667 | 762 | def _normalize_roadmap_status(value: str) -> str: |
668 | 763 | return re.sub(r'[*_`]', '', value).strip().lower() |
669 | 764 |
|
@@ -1185,6 +1280,7 @@ def validate_docs_consistency( |
1185 | 1280 | catalog_path: Path | None = None, |
1186 | 1281 | use_cases_path: Path | None = None, |
1187 | 1282 | roadmap_status_path: Path | None = None, |
| 1283 | + docs_index_path: Path | None = None, |
1188 | 1284 | pyproject_path: Path | None = None, |
1189 | 1285 | coverage_omit_ledger_path: Path | None = None, |
1190 | 1286 | dependency_audit_policy_path: Path | None = None, |
@@ -1214,6 +1310,7 @@ def validate_docs_consistency( |
1214 | 1310 | roadmap_status_doc_path = roadmap_status_path or ( |
1215 | 1311 | _REPO_ROOT / 'docs' / 'roadmap-status.md' |
1216 | 1312 | ) |
| 1313 | + docs_index_doc_path = docs_index_path or (_REPO_ROOT / 'docs' / 'INDEX.md') |
1217 | 1314 | pyproject_doc_path = pyproject_path or (_REPO_ROOT / 'pyproject.toml') |
1218 | 1315 | coverage_omit_ledger_doc_path = coverage_omit_ledger_path or ( |
1219 | 1316 | _REPO_ROOT / 'docs' / 'governance' / 'coverage-omit-ledger.md' |
@@ -1242,6 +1339,11 @@ def validate_docs_consistency( |
1242 | 1339 | if roadmap_status_doc_path.is_file() |
1243 | 1340 | else '' |
1244 | 1341 | ) |
| 1342 | + docs_index_text = ( |
| 1343 | + docs_index_doc_path.read_text(encoding='utf-8') |
| 1344 | + if docs_index_doc_path.is_file() |
| 1345 | + else '' |
| 1346 | + ) |
1245 | 1347 |
|
1246 | 1348 | if check_providers: |
1247 | 1349 | if architecture_path.is_file() and usage_doc_path.is_file(): |
@@ -1415,6 +1517,13 @@ def validate_docs_consistency( |
1415 | 1517 | errors.extend( |
1416 | 1518 | validate_index_status_vocabulary(index_path.read_text(encoding='utf-8')) |
1417 | 1519 | ) |
| 1520 | + errors.extend( |
| 1521 | + validate_current_truth_framing( |
| 1522 | + readme_text=readme_text, |
| 1523 | + docs_index_text=docs_index_text, |
| 1524 | + roadmap_text=roadmap_status_text, |
| 1525 | + ) |
| 1526 | + ) |
1418 | 1527 |
|
1419 | 1528 | errors.extend( |
1420 | 1529 | validate_durable_docs_language( |
|
0 commit comments