frontmattering 0.4.2__py3-none-any.whl

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.
Files changed (224) hide show
  1. frontmattering/FRONTMATTER_INDEX_PROVENANCE.json +13 -0
  2. frontmattering/__init__.py +1 -0
  3. frontmattering/__main__.py +1263 -0
  4. frontmattering/ai_instruction_risk.py +51 -0
  5. frontmattering/authoring.py +320 -0
  6. frontmattering/body_tether.py +176 -0
  7. frontmattering/common/__init__.py +3 -0
  8. frontmattering/common/errors.py +1074 -0
  9. frontmattering/core.py +147 -0
  10. frontmattering/corpus_selection.py +436 -0
  11. frontmattering/frontmatter_index.py +340 -0
  12. frontmattering/harness_errors.py +555 -0
  13. frontmattering/inventory.py +83 -0
  14. frontmattering/markdown/MARKDOWN_PARSE_RESULT_schema.json +870 -0
  15. frontmattering/markdown/MARKDOWN_PROVENANCE.json +8 -0
  16. frontmattering/markdown/USER_MANUAL.md +263 -0
  17. frontmattering/markdown/__init__.py +0 -0
  18. frontmattering/markdown/api.py +553 -0
  19. frontmattering/markdown/cli/__init__.py +1 -0
  20. frontmattering/markdown/cli/__main__.py +10 -0
  21. frontmattering/markdown/cli/dump_sections.py +100 -0
  22. frontmattering/markdown/collectors_phase8/__init__.py +15 -0
  23. frontmattering/markdown/collectors_phase8/blockquotes.py +117 -0
  24. frontmattering/markdown/collectors_phase8/codeblocks.py +46 -0
  25. frontmattering/markdown/collectors_phase8/footnotes.py +152 -0
  26. frontmattering/markdown/collectors_phase8/frontmatter.py +80 -0
  27. frontmattering/markdown/collectors_phase8/headings.py +85 -0
  28. frontmattering/markdown/collectors_phase8/html.py +29 -0
  29. frontmattering/markdown/collectors_phase8/html_collector.py +89 -0
  30. frontmattering/markdown/collectors_phase8/images.py +68 -0
  31. frontmattering/markdown/collectors_phase8/inline_code.py +61 -0
  32. frontmattering/markdown/collectors_phase8/links.py +104 -0
  33. frontmattering/markdown/collectors_phase8/lists.py +66 -0
  34. frontmattering/markdown/collectors_phase8/math.py +28 -0
  35. frontmattering/markdown/collectors_phase8/paragraphs.py +41 -0
  36. frontmattering/markdown/collectors_phase8/tables.py +65 -0
  37. frontmattering/markdown/collectors_phase8/tasklists.py +59 -0
  38. frontmattering/markdown/fetchers/__init__.py +8 -0
  39. frontmattering/markdown/fetchers/preview.py +123 -0
  40. frontmattering/markdown/fetchers/url_fetcher.py +123 -0
  41. frontmattering/markdown/security/__init__.py +6 -0
  42. frontmattering/markdown/security/scanner.py +436 -0
  43. frontmattering/markdown/utils/__init__.py +0 -0
  44. frontmattering/markdown/utils/profiling.py +137 -0
  45. frontmattering/markdown/utils/section.py +98 -0
  46. frontmattering/markdown/utils/text_normalization.py +91 -0
  47. frontmattering/markdown/utils/timeout.py +165 -0
  48. frontmattering/markdown/utils/token_warehouse.py +1300 -0
  49. frontmattering/markdown/utils/url_utils.py +179 -0
  50. frontmattering/promotion.py +170 -0
  51. frontmattering/resources/AI_INDEX.json +691 -0
  52. frontmattering/resources/AI_START_HERE.md +378 -0
  53. frontmattering/resources/DAILY_USE_GUIDE.md +87 -0
  54. frontmattering/resources/FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md +3203 -0
  55. frontmattering/resources/KERNEL_ACCEPTANCE_REPORT.md +180 -0
  56. frontmattering/resources/__init__.py +0 -0
  57. frontmattering/resources/examples/no_frontmatter_demo/00_AI_START_HERE.md +43 -0
  58. frontmattering/resources/examples/no_frontmatter_demo/10_NORMATIVE_SPEC.md +28 -0
  59. frontmattering/resources/examples/no_frontmatter_demo/20_IMPLEMENTATION_CONTRACT.md +33 -0
  60. frontmattering/resources/examples/no_frontmatter_demo/30_TEST_SUFFICIENCY.md +34 -0
  61. frontmattering/resources/examples/no_frontmatter_demo/40_EVIDENCE_RECORD.md +33 -0
  62. frontmattering/resources/examples/no_frontmatter_demo/50_RELEASE_DECISION.md +29 -0
  63. frontmattering/resources/examples/power_demo/00_AI_START_HERE.md +142 -0
  64. frontmattering/resources/examples/power_demo/10_NORMATIVE_SPEC.md +117 -0
  65. frontmattering/resources/examples/power_demo/20_IMPLEMENTATION_CONTRACT.md +130 -0
  66. frontmattering/resources/examples/power_demo/30_TEST_SUFFICIENCY.md +132 -0
  67. frontmattering/resources/examples/power_demo/40_EVIDENCE_RECORD.md +128 -0
  68. frontmattering/resources/examples/power_demo/50_RELEASE_DECISION.md +124 -0
  69. frontmattering/resources/fixtures/invalid/adoption_digest_mismatch_rejected/doc.md +80 -0
  70. frontmattering/resources/fixtures/invalid/adoption_record_on_wrong_role_rejected/doc.md +59 -0
  71. frontmattering/resources/fixtures/invalid/adoption_record_shape_rejected/doc.md +58 -0
  72. frontmattering/resources/fixtures/invalid/adoption_without_bootstrap_receipt_rejected/doc.md +78 -0
  73. frontmattering/resources/fixtures/invalid/authored_go_used_as_evidence_rejected/doc.md +57 -0
  74. frontmattering/resources/fixtures/invalid/body_tether_body_digest_mismatch_rejected/doc.md +65 -0
  75. frontmattering/resources/fixtures/invalid/body_tether_fields_mismatch_rejected/doc.md +63 -0
  76. frontmattering/resources/fixtures/invalid/body_tether_malformed_rejected/doc.md +58 -0
  77. frontmattering/resources/fixtures/invalid/body_tether_missing_under_trust_rejected/.frontmattering/project.yaml +11 -0
  78. frontmattering/resources/fixtures/invalid/body_tether_missing_under_trust_rejected/doc.md +57 -0
  79. frontmattering/resources/fixtures/invalid/body_tether_scope_selector_rejected/doc.md +68 -0
  80. frontmattering/resources/fixtures/invalid/body_tether_unknown_canonicalization_rejected/doc.md +63 -0
  81. frontmattering/resources/fixtures/invalid/bootstrap_receipt_shape_rejected/doc.md +80 -0
  82. frontmattering/resources/fixtures/invalid/bootstrap_without_validator_blocks_adoption/doc.md +71 -0
  83. frontmattering/resources/fixtures/invalid/claim_boundary_missing_does_not_verify_rejected/doc.md +55 -0
  84. frontmattering/resources/fixtures/invalid/claimed_state_overreach_rejected/doc.md +57 -0
  85. frontmattering/resources/fixtures/invalid/clause_enforcement_drift_rejected/doc.md +59 -0
  86. frontmattering/resources/fixtures/invalid/container_cardinality_rejected/doc.md +57 -0
  87. frontmattering/resources/fixtures/invalid/deepest_intention_missing_rejected/doc.md +56 -0
  88. frontmattering/resources/fixtures/invalid/downstream_phase_without_upstream_evidence_rejected/doc.md +59 -0
  89. frontmattering/resources/fixtures/invalid/duplicate_document_id_rejected/a.md +57 -0
  90. frontmattering/resources/fixtures/invalid/duplicate_document_id_rejected/b.md +57 -0
  91. frontmattering/resources/fixtures/invalid/duplicate_yaml_key_rejected/doc.md +58 -0
  92. frontmattering/resources/fixtures/invalid/evidence_edge_digest_missing_rejected/doc.md +65 -0
  93. frontmattering/resources/fixtures/invalid/evidence_reference_used_as_truth_rejected/doc.md +66 -0
  94. frontmattering/resources/fixtures/invalid/external_edge_locator_missing_rejected/doc.md +64 -0
  95. frontmattering/resources/fixtures/invalid/external_edge_set_id_missing_rejected/doc.md +64 -0
  96. frontmattering/resources/fixtures/invalid/extracted_catalog_drift_rejected/doc.md +60 -0
  97. frontmattering/resources/fixtures/invalid/fixture_reverse_reachability_drift_rejected/doc.md +59 -0
  98. frontmattering/resources/fixtures/invalid/frontmatter_only_view_incomplete_rejected/doc.md +59 -0
  99. frontmattering/resources/fixtures/invalid/frontmatter_registry_conflict_blocks/doc.md +59 -0
  100. frontmattering/resources/fixtures/invalid/frontmatter_view_body_truth_overclaim_rejected/doc.md +59 -0
  101. frontmattering/resources/fixtures/invalid/generated_state_in_frontmatter_rejected/doc.md +58 -0
  102. frontmattering/resources/fixtures/invalid/green_checks_conflation_rejected/doc.md +76 -0
  103. frontmattering/resources/fixtures/invalid/historical_digest_used_as_current_evidence_rejected/doc.md +66 -0
  104. frontmattering/resources/fixtures/invalid/implementation_start_gate_bypass_rejected/doc.md +59 -0
  105. frontmattering/resources/fixtures/invalid/intention_positive_negative_conflation_rejected/doc.md +76 -0
  106. frontmattering/resources/fixtures/invalid/legacy_string_machine_surface_requires_mapping/doc.md +59 -0
  107. frontmattering/resources/fixtures/invalid/load_order_inversion_flagged/a.md +63 -0
  108. frontmattering/resources/fixtures/invalid/load_order_inversion_flagged/b.md +57 -0
  109. frontmattering/resources/fixtures/invalid/machine_surface_identity_incomplete_rejected/doc.md +62 -0
  110. frontmattering/resources/fixtures/invalid/migration_field_drop_rejected/doc.md +60 -0
  111. frontmattering/resources/fixtures/invalid/migration_silent_coercion_rejected/doc.md +59 -0
  112. frontmattering/resources/fixtures/invalid/missing_frontmatter_rejected/doc.md +1 -0
  113. frontmattering/resources/fixtures/invalid/non_root_exception_or_silent_fallback_rejected/doc.md +59 -0
  114. frontmattering/resources/fixtures/invalid/nonowner_redefines_intention_rejected/doc.md +58 -0
  115. frontmattering/resources/fixtures/invalid/output_format_conflation_rejected/doc.md +76 -0
  116. frontmattering/resources/fixtures/invalid/parser_byte_ranges_missing_rejected/doc.md +59 -0
  117. frontmattering/resources/fixtures/invalid/parser_partial_model_on_error_rejected/doc.md +59 -0
  118. frontmattering/resources/fixtures/invalid/parser_source_locator_missing_rejected/doc.md +59 -0
  119. frontmattering/resources/fixtures/invalid/projected_field_without_origin_rejected/doc.md +59 -0
  120. frontmattering/resources/fixtures/invalid/projection_rebuild_nondeterminism_rejected/doc.md +59 -0
  121. frontmattering/resources/fixtures/invalid/projection_row_without_source_locator_rejected/doc.md +59 -0
  122. frontmattering/resources/fixtures/invalid/relationship_resolution_scope_missing_rejected/doc.md +62 -0
  123. frontmattering/resources/fixtures/invalid/relationship_resolution_scope_unknown_rejected/doc.md +63 -0
  124. frontmattering/resources/fixtures/invalid/relationship_semantics_rejected/doc.md +63 -0
  125. frontmattering/resources/fixtures/invalid/relationship_target_unresolved_rejected/doc.md +63 -0
  126. frontmattering/resources/fixtures/invalid/release_surface_digest_missing_rejected/doc.md +63 -0
  127. frontmattering/resources/fixtures/invalid/required_dependency_cycle_rejected/a.md +63 -0
  128. frontmattering/resources/fixtures/invalid/required_dependency_cycle_rejected/b.md +63 -0
  129. frontmattering/resources/fixtures/invalid/routing_tag_not_in_task_catalog_rejected/doc.md +59 -0
  130. frontmattering/resources/fixtures/invalid/routing_tag_router_not_in_scope_rejected/doc.md +55 -0
  131. frontmattering/resources/fixtures/invalid/scope_on_nonsupersedes_rejected/doc.md +65 -0
  132. frontmattering/resources/fixtures/invalid/stale_bootstrap_receipt_rejected/doc.md +80 -0
  133. frontmattering/resources/fixtures/invalid/stale_validation_result_rejected/doc.md +60 -0
  134. frontmattering/resources/fixtures/invalid/supersedes_without_scope_rejected/doc.md +63 -0
  135. frontmattering/resources/fixtures/invalid/target_section_not_found_rejected/a.md +65 -0
  136. frontmattering/resources/fixtures/invalid/target_section_not_found_rejected/b.md +57 -0
  137. frontmattering/resources/fixtures/invalid/target_sections_on_external_edge_rejected/doc.md +67 -0
  138. frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/router.md +59 -0
  139. frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/selected.md +56 -0
  140. frontmattering/resources/fixtures/invalid/task_scoped_defects_filtering/skipped.md +56 -0
  141. frontmattering/resources/fixtures/invalid/template_claims_normative_authority_rejected/doc.md +57 -0
  142. frontmattering/resources/fixtures/invalid/two_documents_own_same_intention_rejected/a.md +76 -0
  143. frontmattering/resources/fixtures/invalid/two_documents_own_same_intention_rejected/b.md +76 -0
  144. frontmattering/resources/fixtures/invalid/undefined_conditional_token_rejected/doc.md +59 -0
  145. frontmattering/resources/fixtures/invalid/undefined_type_rejected/doc.md +59 -0
  146. frontmattering/resources/fixtures/invalid/unknown_authority_rejected/doc.md +57 -0
  147. frontmattering/resources/fixtures/invalid/unknown_document_role_rejected/doc.md +57 -0
  148. frontmattering/resources/fixtures/invalid/unknown_field_rejected/doc.md +58 -0
  149. frontmattering/resources/fixtures/invalid/unknown_root_block_rejected/doc.md +59 -0
  150. frontmattering/resources/fixtures/invalid/unresolved_reference_on_required_edge_rejected/doc.md +63 -0
  151. frontmattering/resources/fixtures/invalid/unsupported_schema_version_rejected/doc.md +57 -0
  152. frontmattering/resources/fixtures/invalid/untemplated_frontmatter_bypass_rejected/doc.md +59 -0
  153. frontmattering/resources/fixtures/invalid/validator_passes_conflation_rejected/doc.md +76 -0
  154. frontmattering/resources/fixtures/invalid/yaml_1_1_boolean_words_not_coerced/doc.md +57 -0
  155. frontmattering/resources/fixtures/pilot_security/blockquote.md +3 -0
  156. frontmattering/resources/fixtures/pilot_security/code_fence.md +5 -0
  157. frontmattering/resources/fixtures/pilot_security/dangerous_html.md +3 -0
  158. frontmattering/resources/fixtures/pilot_security/dangerous_uri.md +3 -0
  159. frontmattering/resources/fixtures/pilot_security/direct_instruction.md +3 -0
  160. frontmattering/resources/fixtures/pilot_security/generated_resource_limits.yaml +16 -0
  161. frontmattering/resources/fixtures/pilot_security/indirect_instruction.md +4 -0
  162. frontmattering/resources/fixtures/pilot_security/legitimate_security_discussion.md +3 -0
  163. frontmattering/resources/fixtures/pilot_security/link.md +3 -0
  164. frontmattering/resources/fixtures/pilot_security/table.md +5 -0
  165. frontmattering/resources/fixtures/valid/adoption_locator_trigger_word_is_data_valid/doc.md +80 -0
  166. frontmattering/resources/fixtures/valid/body_tether_fresh_valid/doc.md +63 -0
  167. frontmattering/resources/fixtures/valid/bootstrap_validation_receipt_valid/doc.md +66 -0
  168. frontmattering/resources/fixtures/valid/claim_prose_substrings_are_not_verdicts_valid/doc.md +57 -0
  169. frontmattering/resources/fixtures/valid/companion_and_appendix_edges_valid/a.md +69 -0
  170. frontmattering/resources/fixtures/valid/companion_and_appendix_edges_valid/b.md +57 -0
  171. frontmattering/resources/fixtures/valid/document_binding_to_external_intention_valid/doc.md +57 -0
  172. frontmattering/resources/fixtures/valid/document_realizing_external_intention_valid/doc.md +57 -0
  173. frontmattering/resources/fixtures/valid/document_referencing_external_intention_valid/doc.md +57 -0
  174. frontmattering/resources/fixtures/valid/evidence_record_valid/doc.md +57 -0
  175. frontmattering/resources/fixtures/valid/evidence_support_substrings_are_not_proof_valid/doc.md +66 -0
  176. frontmattering/resources/fixtures/valid/external_document_set_edge_valid/doc.md +65 -0
  177. frontmattering/resources/fixtures/valid/frontmatter_adoption_record_valid/doc.md +80 -0
  178. frontmattering/resources/fixtures/valid/frontmatter_projection_mapping_valid/doc.md +57 -0
  179. frontmattering/resources/fixtures/valid/frontmatter_spec_bootstrap_valid/doc.md +76 -0
  180. frontmattering/resources/fixtures/valid/implementation_contract_valid/doc.md +57 -0
  181. frontmattering/resources/fixtures/valid/normative_spec_valid/doc.md +57 -0
  182. frontmattering/resources/fixtures/valid/orientation_router_valid/doc.md +59 -0
  183. frontmattering/resources/fixtures/valid/release_metadata_valid/doc.md +57 -0
  184. frontmattering/resources/fixtures/valid/target_sections_resolved_valid/a.md +65 -0
  185. frontmattering/resources/fixtures/valid/target_sections_resolved_valid/b.md +57 -0
  186. frontmattering/resources/fixtures/valid/template_valid/doc.md +57 -0
  187. frontmattering/resources/fixtures/valid/unresolved_reference_optional_edge_valid/doc.md +63 -0
  188. frontmattering/resources/fixtures/valid/untemplated_document_valid/doc.md +57 -0
  189. frontmattering/resources/fixtures/valid/yaml_1_2_plain_words_valid/doc.md +60 -0
  190. frontmattering/resources/generated/FRONTMATTERING_FRONTMATTER_SCHEMA.schema.json +2422 -0
  191. frontmattering/resources/tools/__init__.py +0 -0
  192. frontmattering/resources/tools/check_frontmatter_sqlite_index.py +40 -0
  193. frontmattering/resources/tools/check_kernel_cleanliness.py +103 -0
  194. frontmattering/resources/tools/check_kernel_exception_taxonomy.py +145 -0
  195. frontmattering/resources/tools/check_markdown_parser_adoption.py +35 -0
  196. frontmattering/resources/tools/check_pilot_readiness.py +545 -0
  197. frontmattering/resources/tools/check_projection_consistency.py +35 -0
  198. frontmattering/resources/tools/check_python_syntax.py +29 -0
  199. frontmattering/resources/tools/check_report_quality.py +478 -0
  200. frontmattering/resources/tools/distribution_receipt.py +54 -0
  201. frontmattering/resources/tools/finding_registry.py +625 -0
  202. frontmattering/resources/tools/frontmattering_cli.py +18 -0
  203. frontmattering/resources/tools/generate_ai_index.py +123 -0
  204. frontmattering/resources/tools/generate_frontmatter_schema.py +70 -0
  205. frontmattering/resources/tools/kernel_reporting.py +1495 -0
  206. frontmattering/resources/tools/make_fixtures.py +507 -0
  207. frontmattering/resources/tools/pack_kernel_distro.py +430 -0
  208. frontmattering/resources/tools/test_verify_kernel.py +177 -0
  209. frontmattering/resources/tools/validate_frontmatters.py +100 -0
  210. frontmattering/resources/tools/verify_kernel.py +197 -0
  211. frontmattering/resources/workflow-docs-manifest.yaml +199 -0
  212. frontmattering/schema_projection.py +288 -0
  213. frontmattering/speccompile.py +81 -0
  214. frontmattering/validator.py +957 -0
  215. frontmattering-0.4.2.dist-info/METADATA +91 -0
  216. frontmattering-0.4.2.dist-info/RECORD +224 -0
  217. frontmattering-0.4.2.dist-info/WHEEL +5 -0
  218. frontmattering-0.4.2.dist-info/entry_points.txt +2 -0
  219. frontmattering-0.4.2.dist-info/licenses/COMMERCIAL_LICENSE_REQUIRED.md +14 -0
  220. frontmattering-0.4.2.dist-info/licenses/LICENSE.md +39 -0
  221. frontmattering-0.4.2.dist-info/licenses/NOTICE +10 -0
  222. frontmattering-0.4.2.dist-info/licenses/THIRD_PARTY_NOTICES.md +12 -0
  223. frontmattering-0.4.2.dist-info/licenses/TRADEMARKS.md +7 -0
  224. frontmattering-0.4.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1263 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import contextlib
5
+ import hashlib
6
+ import importlib.util
7
+ import zipfile
8
+ import io
9
+ import json
10
+ import os
11
+ import re
12
+ import shutil
13
+ import sys
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ sys.dont_write_bytecode = True
18
+
19
+
20
+ PACKAGE_ROOT = Path(__file__).resolve().parent
21
+ _SOURCE_KERNEL_ROOT = PACKAGE_ROOT.parent
22
+ KERNEL_ROOT = _SOURCE_KERNEL_ROOT if (_SOURCE_KERNEL_ROOT / "FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md").exists() else PACKAGE_ROOT / "resources"
23
+ TOOLS_DIR = KERNEL_ROOT / "tools"
24
+ SPEC_PATH = KERNEL_ROOT / "FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md"
25
+
26
+ if str(TOOLS_DIR) not in sys.path:
27
+ sys.path.insert(0, str(TOOLS_DIR))
28
+
29
+ from .speccompile import load_spec # noqa: E402
30
+ from .validator import validate_docset # noqa: E402
31
+ from .schema_projection import render_schema # noqa: E402
32
+ from .core import parse_frontmatter, split_frontmatter # noqa: E402
33
+ from .markdown.api import parse_markdown_text # noqa: E402
34
+ from .ai_instruction_risk import scan_ai_instruction_risk # noqa: E402
35
+ from .frontmatter_index import build_index, ensure_index_fresh, query_index, DEFAULT_INDEX # noqa: E402
36
+ from .authoring import init_project, new_document, PROFILES # noqa: E402
37
+ from .corpus_selection import SelectionResult, resolve_corpus # noqa: E402
38
+ from .inventory import build_inventory # noqa: E402
39
+ from .promotion import promote_document # noqa: E402
40
+ from .body_tether import update_tether # noqa: E402
41
+ from .harness_errors import HarnessError, InternalBugError, ToolingError # noqa: E402
42
+ from finding_registry import declared_finding_ids, finding_contract, guidance_for, retired_finding_info # noqa: E402
43
+ from kernel_reporting import ( # noqa: E402
44
+ render_mesh_report,
45
+ render_brief_report,
46
+ render_report,
47
+ validation_report,
48
+ )
49
+
50
+
51
+ EXIT_GO = 0
52
+ EXIT_FINDING = 1
53
+ EXIT_USAGE = 2
54
+ EXIT_SELF_CHECK = 3
55
+ EXIT_INTERNAL = 4
56
+
57
+
58
+ class StrictArgumentParser(argparse.ArgumentParser):
59
+ """Argument parser with exact option names and no legacy prefix matching."""
60
+
61
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
62
+ kwargs.setdefault("allow_abbrev", False)
63
+ super().__init__(*args, **kwargs)
64
+ BRIEF_DEV_DIR_NAMES = {"archive", "distro_smoke", "fixtures", "mesh", "usecase"}
65
+ ROUTING_TASK_FIELDS = {"routing.read_when_tags[]", "routing.skip_when_tags[]"}
66
+
67
+
68
+ def collect_markdown_paths(inputs: list[str]) -> list[Path]:
69
+ """Resolve Markdown paths through the single Frontmattering v0.3 selection authority."""
70
+ return list(resolve_corpus(inputs).paths)
71
+
72
+
73
+ def build_report(inputs: list[str]) -> dict[str, Any]:
74
+ selection = resolve_corpus(inputs)
75
+ paths = list(selection.paths)
76
+ auth = load_spec(SPEC_PATH)
77
+ result = validate_docset(paths, auth)
78
+ report = validation_report(target_paths=paths, auth=auth, result=result)
79
+ report["selection"] = selection.payload()
80
+ report["project"] = {
81
+ "project_file": str(selection.project_file.resolve()) if selection.project_file else None,
82
+ "project_schema": selection.project_schema,
83
+ "validation_profile": selection.validation_profile,
84
+ }
85
+ return report
86
+
87
+ def brief_paths(paths: list[Path], *, include_dev: bool, inputs: list[str]) -> list[Path]:
88
+ if include_dev:
89
+ return paths
90
+ explicit_dev_roots = {
91
+ Path(raw).resolve()
92
+ for raw in inputs
93
+ if Path(raw).name in BRIEF_DEV_DIR_NAMES
94
+ }
95
+
96
+ def is_explicit_dev_target(path: Path) -> bool:
97
+ resolved = path.resolve()
98
+ return any(resolved == root or resolved.is_relative_to(root) for root in explicit_dev_roots)
99
+
100
+ filtered = [
101
+ path for path in paths
102
+ if is_explicit_dev_target(path) or not any(part in BRIEF_DEV_DIR_NAMES for part in path.parts)
103
+ ]
104
+ if not filtered:
105
+ raise ToolingError(
106
+ "Cold-start brief has no non-dev Markdown targets",
107
+ "brief target filtering",
108
+ "The brief command hides fixture/archive/dev-only directories by default.",
109
+ "Pass --include-dev or point brief at the intended authored document files.",
110
+ {"filtered_directories": sorted(BRIEF_DEV_DIR_NAMES)},
111
+ error_id="FRONTMATTERING-KERNEL-BRIEF-NO-TARGETS",
112
+ )
113
+ return filtered
114
+
115
+
116
+ def build_brief_report(inputs: list[str], *, include_dev: bool) -> dict[str, Any]:
117
+ selection = resolve_corpus(inputs)
118
+ paths = brief_paths(list(selection.paths), include_dev=include_dev, inputs=inputs)
119
+ final_selection = selection.restrict(paths)
120
+ auth = load_spec(SPEC_PATH)
121
+ result = validate_docset(paths, auth)
122
+ report = validation_report(target_paths=paths, auth=auth, result=result)
123
+ report["selection"] = final_selection.payload()
124
+ report["project"] = {
125
+ "project_file": str(selection.project_file.resolve()) if selection.project_file else None,
126
+ "project_schema": selection.project_schema,
127
+ "validation_profile": selection.validation_profile,
128
+ }
129
+ return report
130
+
131
+
132
+ def exit_for_report(report: dict[str, Any]) -> int:
133
+ return EXIT_GO if report.get("terminal_state") == "GO" else EXIT_FINDING
134
+
135
+
136
+ def selected_format(args: argparse.Namespace) -> str:
137
+ return args.format
138
+
139
+
140
+ def command_validate(args: argparse.Namespace) -> int:
141
+ try:
142
+ report = build_report(args.paths)
143
+ except ToolingError as exc:
144
+ print(exc, file=sys.stderr)
145
+ return EXIT_USAGE
146
+ print(render_report(report, selected_format(args)))
147
+ return exit_for_report(report)
148
+
149
+
150
+ def command_mesh(args: argparse.Namespace) -> int:
151
+ try:
152
+ report = build_report(args.paths)
153
+ except ToolingError as exc:
154
+ print(exc, file=sys.stderr)
155
+ return EXIT_USAGE
156
+ print(render_mesh_report(report, selected_format(args)))
157
+ return exit_for_report(report)
158
+
159
+
160
+ def command_brief(args: argparse.Namespace) -> int:
161
+ try:
162
+ report = build_brief_report(args.paths, include_dev=args.include_dev)
163
+ rendered = render_brief_report(report, selected_format(args), task=args.task, section=args.section)
164
+ except ToolingError as exc:
165
+ print(exc, file=sys.stderr)
166
+ return EXIT_USAGE
167
+ print(rendered)
168
+ return EXIT_GO
169
+
170
+
171
+ def command_explain(args: argparse.Namespace) -> int:
172
+ auth = load_spec(SPEC_PATH)
173
+ declared = args.finding_id in declared_finding_ids(auth)
174
+ retired = retired_finding_info(args.finding_id)
175
+ payload = finding_contract(args.finding_id, auth=auth)
176
+ if retired is not None:
177
+ payload.update({
178
+ "finding_status": "retired",
179
+ "retired_in": retired["retired_in"],
180
+ "last_declared_in": retired["last_declared_in"],
181
+ "superseded_by": retired["superseded_by"],
182
+ "retirement_reason": retired["reason"],
183
+ })
184
+ elif not declared:
185
+ payload["finding_status"] = "unknown"
186
+ if args.format == "json":
187
+ print(json.dumps(payload, indent=2, sort_keys=True))
188
+ else:
189
+ print(f"# {args.finding_id}")
190
+ print()
191
+ for key in ("finding_status", "title", "class", "severity", "automation_class", "why"):
192
+ print(f"- {key}: {payload.get(key)}")
193
+ print("- deterministic_repairs:")
194
+ for item in payload.get("deterministic_repairs") or []:
195
+ print(f" - {item}")
196
+ print("- judgment_required:")
197
+ for item in payload.get("judgment_required") or []:
198
+ print(f" - {item}")
199
+ print()
200
+ print("Claim boundary:")
201
+ for item in payload.get("does_not_verify") or []:
202
+ print(f"- does_not_verify: {item}")
203
+ return EXIT_GO if declared else EXIT_FINDING
204
+
205
+ def _tether_status_rows(paths: list[Path]) -> list[dict[str, Any]]:
206
+ auth = load_spec(SPEC_PATH)
207
+ result = validate_docset(paths, auth)
208
+ rows = []
209
+ for path, per_doc in sorted((result.get("per_document") or {}).items()):
210
+ fm = per_doc.get("fm") or {}
211
+ adc = fm.get("ai_document_contract", {}) if isinstance(fm, dict) else {}
212
+ status = per_doc.get("body_tether") or {}
213
+ rows.append({
214
+ "path": path,
215
+ "document_id": adc.get("document_id"),
216
+ "freshness": status.get("freshness") or "UNTETHERED",
217
+ "summary_trust": status.get("summary_trust") or "orientation_only",
218
+ "reasons": status.get("reasons") or [],
219
+ "body_sha256_current": status.get("body_sha256_current"),
220
+ "body_sha256_declared": status.get("body_sha256_declared"),
221
+ "tethered_fields_sha256_current": status.get("tethered_fields_sha256_current"),
222
+ "tethered_fields_sha256_declared": status.get("tethered_fields_sha256_declared"),
223
+ })
224
+ return rows
225
+
226
+
227
+ def command_tether_check(args: argparse.Namespace) -> int:
228
+ try:
229
+ paths = collect_markdown_paths(args.paths)
230
+ except ToolingError as exc:
231
+ print(exc, file=sys.stderr)
232
+ return EXIT_USAGE
233
+ rows = _tether_status_rows(paths)
234
+ payload = {
235
+ "schema_version": "frontmattering_tether_check/v0_1",
236
+ "document_count": len(rows),
237
+ "stale_count": sum(1 for row in rows if row["freshness"] == "STALE"),
238
+ "untethered_count": sum(1 for row in rows if row["freshness"] == "UNTETHERED"),
239
+ "fresh_count": sum(1 for row in rows if row["freshness"] == "FRESH"),
240
+ "rows": rows,
241
+ "claim_boundary": {
242
+ "verifies": ["Body/frontmatter tether freshness under the current validator semantics."],
243
+ "does_not_verify": ["Semantic correctness of summaries or external approval."],
244
+ },
245
+ }
246
+ if args.format == "json":
247
+ print(json.dumps(payload, indent=2, sort_keys=True, default=str))
248
+ else:
249
+ print("# Frontmattering tether check")
250
+ print(f"fresh={payload['fresh_count']} stale={payload['stale_count']} untethered={payload['untethered_count']}")
251
+ for row in rows:
252
+ reasons = ";".join(row.get("reasons") or []) or "none"
253
+ print(f"- {row['freshness']} {row.get('document_id') or '?'} {row['path']} summary_trust={row['summary_trust']} reasons={reasons}")
254
+ return EXIT_FINDING if payload["stale_count"] else EXIT_GO
255
+
256
+
257
+ def command_tether_update(args: argparse.Namespace) -> int:
258
+ try:
259
+ paths = collect_markdown_paths(args.paths)
260
+ except ToolingError as exc:
261
+ print(exc, file=sys.stderr)
262
+ return EXIT_USAGE
263
+ updated = []
264
+ skipped = []
265
+ failed = []
266
+ for path in paths:
267
+ try:
268
+ if update_tether(path, add=args.add, canonical_fields=load_spec(SPEC_PATH).body_tether_canonical_trust_fields):
269
+ updated.append(str(path))
270
+ else:
271
+ skipped.append(str(path))
272
+ except (OSError, ValueError, yaml.YAMLError) as exc:
273
+ failed.append({"path": str(path), "error": str(exc)})
274
+ payload = {
275
+ "schema_version": "frontmattering_tether_update/v0_1",
276
+ "updated_count": len(updated),
277
+ "skipped_count": len(skipped),
278
+ "failed_count": len(failed),
279
+ "updated_paths": updated,
280
+ "skipped_paths": skipped,
281
+ "failures": failed,
282
+ "claim_boundary": {
283
+ "verifies": ["body_tether digest fields were recomputed for reported updated files."],
284
+ "does_not_verify": ["Semantic correctness of summaries or external approval."],
285
+ },
286
+ }
287
+ if args.format == "json":
288
+ print(json.dumps(payload, indent=2, sort_keys=True, default=str))
289
+ else:
290
+ print("# Frontmattering tether update")
291
+ print(f"updated={len(updated)} skipped={len(skipped)} failed={len(failed)}")
292
+ for path in updated:
293
+ print(f"- updated {path}")
294
+ for path in skipped:
295
+ print(f"- skipped {path}")
296
+ for item in failed:
297
+ print(f"- failed {item['path']}: {item['error']}")
298
+ return EXIT_FINDING if failed else EXIT_GO
299
+
300
+ def run_doctor_step(name: str, argv: list[str], *, cwd: Path) -> dict[str, Any]:
301
+ """Execute a shipped Python check in-process with isolated argv and output.
302
+
303
+ Doctor used to launch one subprocess per check. In constrained agent and CI
304
+ environments, repeated child creation could stall after several successful
305
+ checks. Loading each shipped script from its own path preserves that script's
306
+ ROOT semantics while giving doctor one deterministic process boundary.
307
+ """
308
+ if len(argv) < 2:
309
+ return {
310
+ "name": name,
311
+ "argv": argv,
312
+ "exit_code": 2,
313
+ "status": "FAIL",
314
+ "output": "Doctor step is missing a Python script path.",
315
+ "verifies": [],
316
+ "does_not_verify": ["The malformed doctor step was not executed."],
317
+ }
318
+ script = Path(argv[1])
319
+ if not script.is_absolute():
320
+ script = cwd / script
321
+ module_name = "_frontmattering_doctor_" + hashlib.sha256(
322
+ f"{name}:{script}".encode("utf-8")
323
+ ).hexdigest()[:16]
324
+ stdout = io.StringIO()
325
+ stderr = io.StringIO()
326
+ old_argv = list(sys.argv)
327
+ old_cwd = Path.cwd()
328
+ exit_code = 0
329
+ try:
330
+ os.chdir(cwd)
331
+ sys.argv = [str(script), *argv[2:]]
332
+ spec = importlib.util.spec_from_file_location(module_name, script)
333
+ if spec is None or spec.loader is None:
334
+ raise InternalBugError(
335
+ "Cannot load doctor check",
336
+ where="frontmattering.__main__.run_doctor_step",
337
+ context={"name": name, "script": str(script)},
338
+ )
339
+ module = importlib.util.module_from_spec(spec)
340
+ with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
341
+ try:
342
+ spec.loader.exec_module(module)
343
+ main_fn = getattr(module, "main", None)
344
+ if callable(main_fn):
345
+ result = main_fn()
346
+ exit_code = 0 if result is None else int(result)
347
+ except SystemExit as exc:
348
+ value = exc.code
349
+ exit_code = value if isinstance(value, int) else (0 if value is None else 1)
350
+ except (HarnessError, OSError, ValueError, TypeError, AssertionError, ImportError, SyntaxError) as exc:
351
+ exit_code = 1
352
+ print(f"{type(exc).__name__}: {exc}", file=stderr)
353
+ finally:
354
+ sys.argv = old_argv
355
+ os.chdir(old_cwd)
356
+ output = "\n".join(part for part in [stdout.getvalue().strip(), stderr.getvalue().strip()] if part)
357
+ return {
358
+ "name": name,
359
+ "argv": argv,
360
+ "exit_code": exit_code,
361
+ "status": "PASS" if exit_code == 0 else "FAIL",
362
+ "output": output,
363
+ "verifies": [f"The named deterministic check {name} executed with the recorded exit code."],
364
+ "does_not_verify": ["Semantic adequacy, external adoption, release approval, or production readiness."],
365
+ }
366
+
367
+
368
+ def command_doctor(args: argparse.Namespace) -> int:
369
+ python = sys.executable
370
+ root = KERNEL_ROOT.parents[1]
371
+ rel = lambda path: str(path.relative_to(root))
372
+ steps = [
373
+ run_doctor_step(
374
+ "python_syntax",
375
+ [python, rel(KERNEL_ROOT / "tools" / "check_python_syntax.py")],
376
+ cwd=root,
377
+ ),
378
+ run_doctor_step(
379
+ "report_quality",
380
+ [python, rel(KERNEL_ROOT / "tools" / "check_report_quality.py")],
381
+ cwd=root,
382
+ ),
383
+ run_doctor_step(
384
+ "exception_taxonomy",
385
+ [python, rel(KERNEL_ROOT / "tools" / "check_kernel_exception_taxonomy.py")],
386
+ cwd=root,
387
+ ),
388
+ run_doctor_step(
389
+ "ai_index_current",
390
+ [python, rel(KERNEL_ROOT / "tools" / "generate_ai_index.py"), "--check"],
391
+ cwd=root,
392
+ ),
393
+ run_doctor_step(
394
+ "frontmatter_schema_current",
395
+ [python, rel(KERNEL_ROOT / "tools" / "generate_frontmatter_schema.py"), "--check"],
396
+ cwd=root,
397
+ ),
398
+ run_doctor_step(
399
+ "projection_consistency",
400
+ [python, rel(KERNEL_ROOT / "tools" / "check_projection_consistency.py")],
401
+ cwd=root,
402
+ ),
403
+ run_doctor_step(
404
+ "markdown_parser_adoption",
405
+ [python, rel(KERNEL_ROOT / "tools" / "check_markdown_parser_adoption.py")],
406
+ cwd=root,
407
+ ),
408
+ run_doctor_step(
409
+ "frontmatter_sqlite_index",
410
+ [python, rel(KERNEL_ROOT / "tools" / "check_frontmatter_sqlite_index.py")],
411
+ cwd=root,
412
+ ),
413
+ run_doctor_step(
414
+ "kernel_verifier",
415
+ [python, rel(KERNEL_ROOT / "tools" / "verify_kernel.py")],
416
+ cwd=root,
417
+ ),
418
+ run_doctor_step(
419
+ "brief_smoke",
420
+ [
421
+ python,
422
+ rel(KERNEL_ROOT / "tools" / "frontmattering_cli.py"),
423
+ "brief",
424
+ rel(KERNEL_ROOT / "AI_START_HERE.md"),
425
+ rel(KERNEL_ROOT / "FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md"),
426
+ "--task",
427
+ "frontmatter_authoring",
428
+ "--format",
429
+ "json",
430
+ ],
431
+ cwd=root,
432
+ ),
433
+ run_doctor_step(
434
+ "cleanliness",
435
+ [python, rel(KERNEL_ROOT / "tools" / "check_kernel_cleanliness.py")],
436
+ cwd=root,
437
+ ),
438
+ ]
439
+ overall = "PASS" if all(step["exit_code"] == 0 for step in steps) else "FAIL"
440
+ if args.format == "json":
441
+ print(json.dumps({"schema_version": "frontmattering_doctor/v0_3", "doctor_verdict": overall, "steps": steps, "claim_boundary": {"verifies": ["Named local checks executed."], "does_not_verify": ["Semantic adequacy, external adoption, release approval, or production readiness."]}}, indent=2, sort_keys=True))
442
+ else:
443
+ print(f"# Frontmattering kernel doctor\n\n- doctor_verdict: {overall}")
444
+ for step in steps:
445
+ print()
446
+ print(f"## {step['name']}")
447
+ print(f"- status: {step['status']}")
448
+ print(f"- exit_code: {step['exit_code']}")
449
+ if step["output"]:
450
+ print()
451
+ print("```text")
452
+ print(step["output"])
453
+ print("```")
454
+ return EXIT_GO if overall == "PASS" else EXIT_SELF_CHECK
455
+
456
+
457
+ def catalog_payload(auth: Any, field: str | None) -> dict[str, Any]:
458
+ if field is not None:
459
+ entry = auth.fields.get(field)
460
+ if entry is None:
461
+ raise ToolingError(
462
+ "Unknown frontmatter field",
463
+ field,
464
+ "The field is not in the closed field catalog of this specification.",
465
+ "Run `frontmattering catalog` with no --field to list the closed vocabularies.",
466
+ {"field": field},
467
+ error_id="FRONTMATTERING-KERNEL-CATALOG-FIELD-UNKNOWN",
468
+ )
469
+ payload = {
470
+ "field": field,
471
+ "type": entry.get("type"),
472
+ "required_when": entry.get("required_when"),
473
+ "allowed_values": entry.get("allowed_values"),
474
+ }
475
+ if field in ROUTING_TASK_FIELDS:
476
+ built_ins = entry.get("allowed_values") or []
477
+ payload["allowed_values"] = None
478
+ payload["value_domain"] = {
479
+ "built_in_values": built_ins,
480
+ "corpus_declared_values": "Any task_tag value declared in routing.task_catalog[] by an in-scope package_router document.",
481
+ "router_field": "routing.task_catalog[]",
482
+ "scope_note": "Single-file validation of a document with corpus-local tags requires the package_router document in the same validation target.",
483
+ }
484
+ return payload
485
+ return {
486
+ "closed_root_blocks": sorted(auth.closed_root),
487
+ "closed_vocabularies": {
488
+ name: entry.get("allowed_values")
489
+ for name, entry in sorted(auth.fields.items())
490
+ if entry.get("allowed_values")
491
+ },
492
+ "role_compatibility": auth.role_compat,
493
+ "claim_state_compatibility": auth.claim_compat,
494
+ }
495
+
496
+
497
+ def command_catalog(args: argparse.Namespace) -> int:
498
+ auth = load_spec(SPEC_PATH)
499
+ try:
500
+ payload = catalog_payload(auth, args.field)
501
+ except ToolingError as exc:
502
+ print(exc, file=sys.stderr)
503
+ return EXIT_USAGE
504
+
505
+ payload["spec_sha256"] = auth.spec_sha256
506
+ payload["claim_boundary"] = (
507
+ "Catalog projects the closed model of this specification. "
508
+ "It is not validation evidence and does not verify any document."
509
+ )
510
+
511
+ if args.format == "json":
512
+ print(json.dumps(payload, indent=2, sort_keys=True))
513
+ return EXIT_GO
514
+
515
+ print("# Frontmattering closed model catalog")
516
+ print()
517
+ print(f"- spec_sha256: {auth.spec_sha256}")
518
+ print()
519
+ if args.field:
520
+ print(f"## {payload['field']}")
521
+ print()
522
+ print(f"- type: {payload['type']}")
523
+ print(f"- required_when: {payload['required_when']}")
524
+ values = payload["allowed_values"]
525
+ if values:
526
+ print("- allowed_values:")
527
+ for value in values:
528
+ print(f" - {value}")
529
+ else:
530
+ print("- allowed_values: (open - constrained by type, not by enum)")
531
+ if payload.get("value_domain"):
532
+ domain = payload["value_domain"]
533
+ print("- value_domain:")
534
+ print(" - built_in_values:")
535
+ for value in domain["built_in_values"]:
536
+ print(f" - {value}")
537
+ print(f" - corpus_declared_values: {domain['corpus_declared_values']}")
538
+ print(f" - router_field: {domain['router_field']}")
539
+ print(f" - scope_note: {domain['scope_note']}")
540
+ else:
541
+ print("## Closed root blocks")
542
+ print()
543
+ for block in payload["closed_root_blocks"]:
544
+ print(f"- {block}")
545
+ print()
546
+ print("## Closed vocabularies")
547
+ print()
548
+ for name, values in payload["closed_vocabularies"].items():
549
+ print(f"- {name}: {', '.join(str(v) for v in values)}")
550
+ print()
551
+ print("Claim boundary: the catalog projects the closed model only; "
552
+ "it is not validation evidence.")
553
+ return EXIT_GO
554
+
555
+
556
+ def _task_tags_from_paths(inputs: list[str]) -> set[str]:
557
+ tags: set[str] = set()
558
+ for path in collect_markdown_paths(inputs):
559
+ fm, _body, findings = parse_frontmatter(path.read_text(encoding="utf-8"))
560
+ if fm is None or findings:
561
+ continue
562
+ routing = fm.get("routing") if isinstance(fm, dict) else None
563
+ if isinstance(routing, dict):
564
+ for tag in routing.get("task_catalog") or []:
565
+ if isinstance(tag, str) and tag:
566
+ tags.add(tag)
567
+ return tags
568
+
569
+
570
+ def command_schema(args: argparse.Namespace) -> int:
571
+ selection = None
572
+ try:
573
+ if args.paths:
574
+ selection = resolve_corpus(args.paths)
575
+ tags = _task_tags_from_paths([str(path) for path in selection.paths])
576
+ else:
577
+ tags = set()
578
+ except ToolingError as exc:
579
+ print(exc, file=sys.stderr)
580
+ return EXIT_USAGE
581
+ schema_payload = json.loads(render_schema(load_spec(SPEC_PATH), additional_task_tags=tags))
582
+ if selection is not None:
583
+ selection_payload = selection.payload()
584
+ schema_payload["selection"] = selection_payload
585
+ schema_payload["x-frontmattering-selection"] = selection_payload
586
+ rendered = json.dumps(schema_payload, indent=2, sort_keys=True) + "\n"
587
+ if args.vscode:
588
+ schema_path = args.output or ".frontmattering/generated/FRONTMATTERING_FRONTMATTER_SCHEMA.schema.json"
589
+ payload = {"yaml.schemas": {schema_path: ["docs/**/*.md"]}}
590
+ print(json.dumps(payload, indent=2, sort_keys=True))
591
+ return EXIT_GO
592
+ if args.check:
593
+ target = Path(args.check)
594
+ if not target.exists() or target.read_text(encoding="utf-8") != rendered:
595
+ print(ToolingError("Generated JSON Schema is stale", str(target), "Fresh corpus-aware schema emission differs from the written projection.", f"Run: frontmattering schema {' '.join(args.paths)} --output {target}", error_id="FRONTMATTERING-SCHEMA-STALE"), file=sys.stderr)
596
+ return EXIT_FINDING
597
+ print(f"schema_check=PASS path={target}")
598
+ return EXIT_GO
599
+ if args.output:
600
+ target = Path(args.output)
601
+ target.parent.mkdir(parents=True, exist_ok=True)
602
+ target.write_text(rendered, encoding="utf-8")
603
+ print(f"schema_written={target}")
604
+ return EXIT_GO
605
+ print(rendered, end="")
606
+ return EXIT_GO
607
+
608
+
609
+
610
+ def command_init(args: argparse.Namespace) -> int:
611
+ try:
612
+ created = init_project(Path(args.directory), args.set_id, args.title, force=args.force)
613
+ except HarnessError as exc:
614
+ print(exc, file=sys.stderr)
615
+ return EXIT_USAGE
616
+ for path in created:
617
+ print(path)
618
+ root = Path(args.directory)
619
+ print("Next:")
620
+ print(f" frontmattering new {root / 'docs' / '10_GUIDE.md'} --profile guide --title \"First guide\"")
621
+ print(f" frontmattering validate {root / 'docs'}")
622
+ print(f" frontmattering brief {root / 'docs'}")
623
+ return EXIT_GO
624
+
625
+
626
+ def command_new(args: argparse.Namespace) -> int:
627
+ try:
628
+ path = new_document(Path(args.path), args.profile, args.title, summary=args.summary, force=args.force)
629
+ except HarnessError as exc:
630
+ print(f"Frontmattering authoring error: {exc}", file=sys.stderr)
631
+ return EXIT_USAGE
632
+ print(path)
633
+ print("Next: frontmattering validate " + str(path.parent))
634
+ return EXIT_GO
635
+
636
+
637
+ def command_inventory(args: argparse.Namespace) -> int:
638
+ try:
639
+ selection = resolve_corpus(args.paths)
640
+ payload = build_inventory(selection)
641
+ except ToolingError as exc:
642
+ print(exc, file=sys.stderr)
643
+ return EXIT_USAGE
644
+ if selected_format(args) == "json":
645
+ print(json.dumps(payload, indent=2, sort_keys=True))
646
+ else:
647
+ print("# Frontmattering corpus inventory")
648
+ print()
649
+ print(f"- project_schema: {payload['project']['project_schema']}")
650
+ print(f"- selected_path_count: {payload['selection']['selected_path_count']}")
651
+ for key, value in payload["census"].items():
652
+ print(f"- {key}: {value}")
653
+ print()
654
+ print("## Documents")
655
+ print()
656
+ for item in payload["documents"]:
657
+ print(f"- {item['path']}: {item['status']} ({item['reason_code']})")
658
+ print()
659
+ print("Claim boundary: deterministic classification only; no semantic profile recommendation is made.")
660
+ return EXIT_GO
661
+
662
+
663
+ def command_promote(args: argparse.Namespace) -> int:
664
+ if bool(args.output) == bool(args.in_place):
665
+ print(ToolingError("Promotion output mode is ambiguous", "frontmattering promote", "Exactly one of --output or --in-place is required.", "Choose an explicit destination or intentional in-place write.", error_id="FRONTMATTERING-PROMOTE-OUTPUT-MODE-REQUIRED"), file=sys.stderr)
666
+ return EXIT_USAGE
667
+ source = Path(args.input)
668
+ destination = source if args.in_place else Path(args.output)
669
+ try:
670
+ receipt = promote_document(
671
+ source, profile=args.profile, title=args.title, destination=destination, check_only=args.check
672
+ )
673
+ except ToolingError as exc:
674
+ print(exc, file=sys.stderr)
675
+ return EXIT_USAGE
676
+ if args.format == "json":
677
+ print(json.dumps(receipt, indent=2, sort_keys=True))
678
+ else:
679
+ print("# Frontmattering promotion receipt")
680
+ print()
681
+ for key in ("terminal_state", "input", "destination", "profile", "title", "title_source", "input_sha256", "output_sha256", "body_bytes_preserved"):
682
+ print(f"- {key}: {receipt[key]}")
683
+ print()
684
+ print("Claim boundary: frontmatter scaffolding and body-byte preservation only; semantic role appropriateness is not verified.")
685
+ return EXIT_GO
686
+
687
+
688
+ def _json_safe(value: Any) -> Any:
689
+ if isinstance(value, dict):
690
+ return {str(k): _json_safe(v) for k, v in value.items()}
691
+ if isinstance(value, (list, tuple)):
692
+ return [_json_safe(v) for v in value]
693
+ if isinstance(value, (str, int, float, bool)) or value is None:
694
+ return value
695
+ if hasattr(value, "to_dict"):
696
+ return _json_safe(value.to_dict())
697
+ return str(value)
698
+
699
+
700
+ def _parse_one_document(path: Path, args: argparse.Namespace) -> tuple[dict, int]:
701
+ if not path.exists() or not path.is_file() or path.suffix.lower() != ".md":
702
+ raise ToolingError(
703
+ "Markdown input is required", str(path),
704
+ "The parse command accepts an existing .md file or a directory with --batch.",
705
+ "Pass an existing Markdown file, or use --batch with a directory.",
706
+ {"path": str(path)}, error_id="FRONTMATTERING-PARSE-INPUT-INVALID",
707
+ )
708
+ raw = path.read_text(encoding="utf-8", errors="replace")
709
+ canonical_fm, body, fm_findings = parse_frontmatter(raw)
710
+ parsed = parse_markdown_text(
711
+ body,
712
+ config={"allows_html": args.allows_html, "one_pass": True, "surface_all_links": True},
713
+ security_profile=args.security_profile,
714
+ source_path=str(path),
715
+ )
716
+ content_security = parsed.metadata.get("security", {})
717
+ ai_risk = scan_ai_instruction_risk(body, parsed.structure)
718
+ metadata = dict(parsed.metadata)
719
+ metadata.pop("security", None)
720
+ payload = {
721
+ "schema_version": "frontmattering_document_ir/v0_3",
722
+ "distribution_version": "0.4.2",
723
+ "canonical_model_version": "0.1.5",
724
+ "source": {
725
+ "path": str(path),
726
+ "sha256": __import__("hashlib").sha256(raw.encode("utf-8")).hexdigest(),
727
+ "size_bytes": len(raw.encode("utf-8")),
728
+ },
729
+ "frontmatter": {
730
+ "authority": "canonical_frontmattering_strict_parser",
731
+ "value": canonical_fm,
732
+ "findings": [getattr(f, "id", str(f)) for f in fm_findings],
733
+ "generic_parser_projection": None,
734
+ "generic_projection_authoritative": False,
735
+ "body_parser_input": "body_after_strict_frontmattering_frontmatter_boundary",
736
+ },
737
+ "markdown": {
738
+ "implementation": "frontmattering_markdown_parser",
739
+ "source_lineage": "owned_internal_code",
740
+ "structure": parsed.structure,
741
+ "metadata": metadata,
742
+ },
743
+ "security": {
744
+ "content_security": content_security,
745
+ "ai_instruction_risk": ai_risk,
746
+ },
747
+ "claim_boundary": {
748
+ "establishes": ["token-derived Markdown structure", "content-security findings", "literal AI-instruction-risk advisory findings", "strict Frontmattering frontmatter parse state"],
749
+ "does_not_establish": ["body semantic correctness", "factual correctness", "semantic prompt-injection detection", "adoption", "release readiness", "production readiness"],
750
+ },
751
+ }
752
+ return payload, (EXIT_GO if not fm_findings else EXIT_FINDING)
753
+
754
+
755
+ def command_parse(args: argparse.Namespace) -> int:
756
+ path = Path(args.path)
757
+ if args.batch:
758
+ if not path.is_dir():
759
+ raise ToolingError("Batch input must be a directory", str(path), "--batch compiles Markdown files under one directory.", "Pass an existing directory.", {"path": str(path)}, error_id="FRONTMATTERING-PARSE-BATCH-INVALID")
760
+ if not args.output:
761
+ raise ToolingError("Batch output directory is required", "--output", "Batch mode writes one IR per Markdown file plus a manifest.", "Pass --output DIRECTORY.", {}, error_id="FRONTMATTERING-PARSE-BATCH-OUTPUT-REQUIRED")
762
+ selection = resolve_corpus([str(path)])
763
+ out_root = Path(args.output)
764
+ out_root.mkdir(parents=True, exist_ok=True)
765
+ records = []
766
+ terminal = EXIT_GO
767
+ for src in selection.paths:
768
+ payload, code = _parse_one_document(src, args)
769
+ terminal = max(terminal, code)
770
+ rel = src.relative_to(path.resolve()).with_suffix(src.suffix + ".ir.json")
771
+ dest = out_root / rel
772
+ dest.parent.mkdir(parents=True, exist_ok=True)
773
+ dest.write_text(json.dumps(_json_safe(payload), indent=2, sort_keys=True, ensure_ascii=False)+"\n", encoding="utf-8")
774
+ records.append({"source": rel.as_posix()[:-8], "ir": rel.as_posix(), "source_sha256": payload["source"]["sha256"]})
775
+ manifest = {"schema_version":"frontmattering_ir_batch_manifest/v0_3","distribution_version":"0.4.2-pypi-clear","source_root":str(path),"document_count":len(records),"records":records,"selection":selection.payload(),"claim_boundary":"Disposable deterministic projection; source Markdown remains authoritative."}
776
+ (out_root/"manifest.json").write_text(json.dumps(manifest,indent=2,sort_keys=True)+"\n",encoding="utf-8")
777
+ print(f"document_ir_batch_written={out_root} documents={len(records)}")
778
+ return terminal
779
+ payload, code = _parse_one_document(path, args)
780
+ output = json.dumps(_json_safe(payload), indent=2, sort_keys=True, ensure_ascii=False) + "\n"
781
+ if args.output:
782
+ out = Path(args.output)
783
+ out.parent.mkdir(parents=True, exist_ok=True)
784
+ out.write_text(output, encoding="utf-8")
785
+ print(f"document_ir_written={out}")
786
+ else:
787
+ print(output, end="")
788
+ return code
789
+
790
+
791
+ def command_index(args: argparse.Namespace) -> int:
792
+ index_path = Path(getattr(args, "output", None) or getattr(args, "index", None) or DEFAULT_INDEX)
793
+ if args.index_action in {"build", "rebuild", "update"}:
794
+ selection = resolve_corpus([args.corpus])
795
+ result = build_index(
796
+ selection.project_root,
797
+ index_path,
798
+ source_paths=selection.paths,
799
+ selection=selection.replay_payload(),
800
+ )
801
+ print(json.dumps(result, indent=2, sort_keys=True))
802
+ return EXIT_GO
803
+ if args.index_action in {"check", "status"}:
804
+ proof = ensure_index_fresh(index_path)
805
+ print(json.dumps({"schema_version":"frontmattering_frontmatter_index_status/v0_1","terminal_state":"CURRENT","freshness_proof":proof.__dict__}, indent=2, sort_keys=True))
806
+ return EXIT_GO
807
+ if args.index_action == "delete":
808
+ if index_path.exists():
809
+ index_path.unlink()
810
+ print(json.dumps({"terminal_state":"DELETED","index_path":str(index_path)}, indent=2, sort_keys=True))
811
+ return EXIT_GO
812
+ raise ToolingError("Unknown index action", str(args.index_action), "The requested frontmatter-index operation is unsupported.", "Use build, rebuild, update, check, status, or delete.", {}, error_id="FRONTMATTERING-INDEX-ACTION-INVALID")
813
+
814
+
815
+ def command_query(args: argparse.Namespace) -> int:
816
+ result = query_index(Path(args.index or DEFAULT_INDEX), task=args.task, authority=args.authority, role=args.role, target=args.target)
817
+ print(json.dumps(result, indent=2, sort_keys=True, ensure_ascii=False))
818
+ return EXIT_GO
819
+
820
+
821
+ RUNTIME_WRAPPER_TEMPLATE = r"""#!/usr/bin/env python3
822
+ from __future__ import annotations
823
+
824
+ import dataclasses
825
+ import hashlib
826
+ import json
827
+ import sys
828
+ import tempfile
829
+ from pathlib import Path
830
+ from typing import Any
831
+
832
+ MODEL_VERSION = "0.1.5"
833
+ KERNEL_VERSION = "__KERNEL_VERSION__"
834
+ RUNTIME_SCHEMA = "frontmattering_runtime_validate_only/v0_2"
835
+ SPEC_RESOURCE = "resources/FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md"
836
+ VALIDATOR_RESOURCE = "validator.py"
837
+ RUNTIME_BUILD_MANIFEST = __RUNTIME_BUILD_MANIFEST__
838
+
839
+
840
+ def _package_bytes(relative_path: str) -> bytes:
841
+ import frontmattering
842
+ package_dir = Path(frontmattering.__file__).parent
843
+ resource_path = (package_dir / relative_path).as_posix()
844
+ loader = getattr(frontmattering, "__loader__", None) or getattr(frontmattering.__spec__, "loader", None)
845
+ if loader is not None and hasattr(loader, "get_data"):
846
+ return loader.get_data(resource_path)
847
+ return Path(resource_path).read_bytes()
848
+
849
+
850
+ def _sha256(data: bytes) -> str:
851
+ return hashlib.sha256(data).hexdigest()
852
+
853
+
854
+ def _load_authority():
855
+ from frontmattering.speccompile import load_spec
856
+ spec_bytes = _package_bytes(SPEC_RESOURCE)
857
+ # load_spec is intentionally reused unchanged: the runtime supplies the
858
+ # bundled spec bytes through a temporary read-only file instead of parsing a
859
+ # second spec representation.
860
+ with tempfile.NamedTemporaryFile("wb", suffix="_FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md") as handle:
861
+ handle.write(spec_bytes)
862
+ handle.flush()
863
+ return load_spec(Path(handle.name))
864
+
865
+
866
+ def _runtime_identity() -> dict[str, Any]:
867
+ spec_bytes = _package_bytes(SPEC_RESOURCE)
868
+ validator_bytes = _package_bytes(VALIDATOR_RESOURCE)
869
+ return {
870
+ "runtime_schema": RUNTIME_SCHEMA,
871
+ "runtime_surface": "validate_only",
872
+ "runtime_packaging": "zipapp_embedded_frontmattering_validator",
873
+ "canonical_model_version": MODEL_VERSION,
874
+ "kernel_version": KERNEL_VERSION,
875
+ "spec_resource": SPEC_RESOURCE,
876
+ "spec_sha256": _sha256(spec_bytes),
877
+ "validator_resource": VALIDATOR_RESOURCE,
878
+ "validator_sha256": _sha256(validator_bytes),
879
+ **RUNTIME_BUILD_MANIFEST,
880
+ }
881
+
882
+
883
+ def _markdown_paths(targets: list[str]) -> list[Path]:
884
+ paths: list[Path] = []
885
+ for raw in targets or ["."]:
886
+ target = Path(raw)
887
+ if target.is_file() and target.suffix.lower() == ".md":
888
+ paths.append(target)
889
+ elif target.is_dir():
890
+ paths.extend(p for p in target.rglob("*.md") if p.is_file())
891
+ return sorted({p.resolve() for p in paths}, key=lambda item: item.as_posix())
892
+
893
+
894
+ def _finding_payload(finding: Any) -> dict[str, Any]:
895
+ if dataclasses.is_dataclass(finding):
896
+ return dataclasses.asdict(finding)
897
+ payload = {
898
+ "id": getattr(finding, "id", str(finding)),
899
+ "detail": getattr(finding, "detail", ""),
900
+ "source_path": getattr(finding, "source_path", None),
901
+ "document_id": getattr(finding, "document_id", None),
902
+ "field_path": getattr(finding, "field_path", None),
903
+ "line": getattr(finding, "line", None),
904
+ "line_text": getattr(finding, "line_text", None),
905
+ "context": getattr(finding, "context", {}),
906
+ }
907
+ return payload
908
+
909
+
910
+ def validate(targets: list[str]) -> dict[str, Any]:
911
+ from frontmattering.validator import validate_docset
912
+ docs = _markdown_paths(targets)
913
+ auth = _load_authority()
914
+ result = validate_docset(docs, auth)
915
+ findings = [_finding_payload(f) for f in result.get("findings", [])]
916
+ terminal = "GO" if not findings else "VIOLATED"
917
+ return {
918
+ "schema_version": RUNTIME_SCHEMA,
919
+ "runtime_identity": _runtime_identity(),
920
+ "terminal_state": terminal,
921
+ "validation_terminal_state": terminal,
922
+ "finding_count": len(findings),
923
+ "findings": findings,
924
+ "validated_path_count": len(docs),
925
+ "validated_paths": [str(p) for p in docs],
926
+ "claim_boundary": {
927
+ "verifies": [
928
+ "The packed artifact executed the bundled frontmattering.validate_docset code path.",
929
+ "The verdict identity binds the bundled Frontmattering frontmatter spec bytes.",
930
+ ],
931
+ "does_not_verify": [
932
+ "Authoring, mutation, indexing, promotion, initialization, or release approval surfaces.",
933
+ "External adoption, semantic adequacy, or production readiness.",
934
+ ],
935
+ },
936
+ }
937
+
938
+
939
+ def _parse_args(argv: list[str]) -> tuple[str, list[str], str]:
940
+ if len(argv) < 2 or argv[1] != "validate":
941
+ return "usage", [], "json"
942
+ paths: list[str] = []
943
+ fmt = "json"
944
+ i = 2
945
+ while i < len(argv):
946
+ if argv[i] == "--format":
947
+ if i + 1 >= len(argv) or argv[i + 1] not in {"json"}:
948
+ return "bad_format", [], fmt
949
+ fmt = argv[i + 1]
950
+ i += 2
951
+ else:
952
+ paths.append(argv[i])
953
+ i += 1
954
+ return "validate", paths, fmt
955
+
956
+
957
+ def main(argv: list[str] | None = None) -> int:
958
+ argv = list(sys.argv if argv is None else argv)
959
+ action, paths, _fmt = _parse_args(argv)
960
+ if action == "usage":
961
+ print("usage: frontmattering-runtime validate <path> [<path> ...] [--format json]", file=sys.stderr)
962
+ return 2
963
+ if action == "bad_format":
964
+ print("frontmattering-runtime only supports --format json", file=sys.stderr)
965
+ return 2
966
+ report = validate(paths)
967
+ print(json.dumps(report, indent=2, sort_keys=True, default=str))
968
+ return 0 if report["terminal_state"] == "GO" else 1
969
+
970
+
971
+ if __name__ == "__main__":
972
+ raise SystemExit(main(sys.argv))
973
+ """
974
+
975
+
976
+ def _runtime_payload_files() -> list[Path]:
977
+ """Return the minimal validate-only runtime payload.
978
+
979
+ The runtime must carry the actual validation modules and the spec resource,
980
+ but it intentionally omits fixtures, authoring tools, generated reports, and
981
+ mutable/diagnostic surfaces. Keeping the payload narrow prevents a slow or
982
+ misleading "whole distribution" runtime while preserving kernel validation
983
+ semantics.
984
+ """
985
+ relative_files = [
986
+ "__init__.py",
987
+ "harness_errors.py",
988
+ "core.py",
989
+ "speccompile.py",
990
+ "body_tether.py",
991
+ "corpus_selection.py",
992
+ "validator.py",
993
+ "resources/FRONTMATTERING_FRONTMATTER_SPEC_v0_1_5.md",
994
+ ]
995
+ files = [PACKAGE_ROOT / rel for rel in relative_files]
996
+ missing = [str(path.relative_to(PACKAGE_ROOT)) for path in files if not path.is_file()]
997
+ if missing:
998
+ raise ToolingError("Runtime payload assembly failed", "frontmattering runtime pack", f"Required runtime payload files are missing: {missing}", "Restore the validate-only runtime files before packing.")
999
+ return files
1000
+
1001
+
1002
+ def _tree_sha256(files: list[Path]) -> str:
1003
+ h = hashlib.sha256()
1004
+ for path in files:
1005
+ rel = path.relative_to(PACKAGE_ROOT).as_posix().encode("utf-8")
1006
+ data = path.read_bytes()
1007
+ h.update(len(rel).to_bytes(8, "big"))
1008
+ h.update(rel)
1009
+ h.update(len(data).to_bytes(8, "big"))
1010
+ h.update(data)
1011
+ return h.hexdigest()
1012
+
1013
+
1014
+ def _runtime_wrapper() -> str:
1015
+ files = _runtime_payload_files()
1016
+ manifest = {
1017
+ "frontmattering_tree_sha256": _tree_sha256(files),
1018
+ "packed_file_count": len(files),
1019
+ "runtime_builder": "frontmattering.__main__.command_runtime_pack",
1020
+ }
1021
+ return RUNTIME_WRAPPER_TEMPLATE.replace(
1022
+ "__KERNEL_VERSION__", "0.4.2"
1023
+ ).replace(
1024
+ "__RUNTIME_BUILD_MANIFEST__", json.dumps(manifest, sort_keys=True)
1025
+ )
1026
+
1027
+
1028
+ def _write_runtime_zipapp(out: Path, wrapper: str) -> None:
1029
+ if out.exists() and out.is_dir():
1030
+ shutil.rmtree(out)
1031
+ elif out.exists():
1032
+ out.unlink()
1033
+ out.parent.mkdir(parents=True, exist_ok=True)
1034
+ files = _runtime_payload_files()
1035
+ with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as zf:
1036
+ main_info = zipfile.ZipInfo("__main__.py")
1037
+ main_info.date_time = (1980, 1, 1, 0, 0, 0)
1038
+ main_info.external_attr = 0o100755 << 16
1039
+ zf.writestr(main_info, wrapper.encode("utf-8"))
1040
+ for src in files:
1041
+ rel = "frontmattering/" + src.relative_to(PACKAGE_ROOT).as_posix()
1042
+ info = zipfile.ZipInfo(rel)
1043
+ info.date_time = (1980, 1, 1, 0, 0, 0)
1044
+ mode = 0o100755 if os.access(src, os.X_OK) else 0o100644
1045
+ info.external_attr = mode << 16
1046
+ zf.writestr(info, src.read_bytes())
1047
+ try:
1048
+ out.chmod(out.stat().st_mode | 0o111)
1049
+ except OSError as exc:
1050
+ raise ToolingError(
1051
+ "Runtime artifact permission update failed",
1052
+ str(out),
1053
+ "The packed runtime must not silently hide incomplete filesystem finalization.",
1054
+ "Check filesystem permissions or choose a writable output path.",
1055
+ {"error": str(exc)},
1056
+ error_id="FRONTMATTERING-RUNTIME-PACK-CHMOD-FAILED",
1057
+ ) from exc
1058
+
1059
+ def command_runtime_pack(args: argparse.Namespace) -> int:
1060
+ out = Path(args.out)
1061
+ wrapper = _runtime_wrapper()
1062
+ _write_runtime_zipapp(out, wrapper)
1063
+ artifact_bytes = out.read_bytes()
1064
+ files = _runtime_payload_files()
1065
+ payload = {
1066
+ "schema_version": "frontmattering_runtime_pack_report/v0_2",
1067
+ "runtime_artifact": str(out),
1068
+ "canonical_model_version": "0.1.5",
1069
+ "kernel_version": "0.4.2",
1070
+ "artifact_format": "python_zipapp",
1071
+ "surface": "validate_only",
1072
+ "artifact_sha256": hashlib.sha256(artifact_bytes).hexdigest(),
1073
+ "spec_sha256": hashlib.sha256(SPEC_PATH.read_bytes()).hexdigest(),
1074
+ "validator_sha256": hashlib.sha256((PACKAGE_ROOT / "validator.py").read_bytes()).hexdigest(),
1075
+ "frontmattering_tree_sha256": _tree_sha256(files),
1076
+ "packed_file_count": len(files),
1077
+ "claim_boundary": {
1078
+ "verifies": [
1079
+ "The runtime artifact carries frontmattering validation modules and the Frontmattering spec resource.",
1080
+ "The runtime surface is validate-only.",
1081
+ ],
1082
+ "does_not_verify": [
1083
+ "Authoring, mutation, release approval, external adoption, or production readiness.",
1084
+ ],
1085
+ },
1086
+ }
1087
+ if args.format == "json":
1088
+ print(json.dumps(payload, indent=2, sort_keys=True))
1089
+ else:
1090
+ print(
1091
+ "runtime_pack=PASS "
1092
+ f"artifact={payload['runtime_artifact']} "
1093
+ f"format={payload['artifact_format']} "
1094
+ f"canonical_model_version={payload['canonical_model_version']} "
1095
+ f"kernel_version={payload['kernel_version']} "
1096
+ f"sha256={payload['artifact_sha256']} "
1097
+ f"spec_sha256={payload['spec_sha256']} "
1098
+ f"validator_sha256={payload['validator_sha256']}"
1099
+ )
1100
+ return EXIT_GO
1101
+
1102
+ def build_parser() -> argparse.ArgumentParser:
1103
+ parser = StrictArgumentParser(
1104
+ prog="frontmattering",
1105
+ description="Govern and orient load-bearing Markdown with Frontmattering canonical model v0.1.5 frontmatter.",
1106
+ )
1107
+ parser.add_argument("--version", action="version", version="frontmattering distribution v0.4.2; canonical model v0.1.5")
1108
+ sub = parser.add_subparsers(dest="command", required=True, parser_class=StrictArgumentParser)
1109
+
1110
+ init = sub.add_parser("init", help="Initialize a small Frontmattering document set and package router.")
1111
+ init.add_argument("directory")
1112
+ init.add_argument("--set-id", required=True, help="Stable document-set identifier.")
1113
+ init.add_argument("--title", required=True, help="Human-readable document-set title.")
1114
+ init.add_argument("--force", action="store_true", help="Overwrite generated project files.")
1115
+ init.set_defaults(func=command_init)
1116
+
1117
+ new = sub.add_parser("new", help="Create a canonical document from a practical starter profile.")
1118
+ new.add_argument("path", help="Output Markdown path inside an initialized Frontmattering v0.3 project.")
1119
+ new.add_argument("--profile", choices=sorted(PROFILES), default="guide")
1120
+ new.add_argument("--title", required=True)
1121
+ new.add_argument("--summary")
1122
+ new.add_argument("--force", action="store_true")
1123
+ new.set_defaults(func=command_new)
1124
+
1125
+ inventory = sub.add_parser("inventory", help="Classify selected governed and plain Markdown without writing files.")
1126
+ inventory.add_argument("paths", nargs="*", default=["."])
1127
+ inventory.add_argument("--format", choices=["markdown", "json"], default="markdown")
1128
+ inventory.set_defaults(func=command_inventory)
1129
+
1130
+ promote = sub.add_parser("promote", help="Add a shipped starter-profile frontmatter block to existing plain Markdown.")
1131
+ promote.add_argument("input")
1132
+ promote.add_argument("--profile", choices=sorted(PROFILES), required=True)
1133
+ promote.add_argument("--title")
1134
+ promote.add_argument("--output")
1135
+ promote.add_argument("--in-place", action="store_true")
1136
+ promote.add_argument("--check", action="store_true", help="Emit the proposed receipt without writing.")
1137
+ promote.add_argument("--format", choices=["markdown", "json"], default="markdown")
1138
+ promote.set_defaults(func=command_promote)
1139
+
1140
+
1141
+ parse_cmd = sub.add_parser("parse", help="Emit security-aware JSON document IR using the Frontmattering token-based Markdown parser.")
1142
+ parse_cmd.add_argument("path", help="Markdown file to parse.")
1143
+ parse_cmd.add_argument("--output", help="Write JSON IR to this path, or batch output directory with --batch.")
1144
+ parse_cmd.add_argument("--batch", action="store_true", help="Compile every .md file below PATH into a disposable IR directory.")
1145
+ parse_cmd.add_argument("--security-profile", choices=["permissive", "moderate", "strict"], default="moderate")
1146
+ parse_cmd.add_argument("--allows-html", action="store_true", help="Tokenize raw HTML so it is represented and security-scanned.")
1147
+ parse_cmd.set_defaults(func=command_parse)
1148
+
1149
+ index_cmd = sub.add_parser("index", help="Build and verify the disposable SQLite canonical-frontmatter index.")
1150
+ index_sub = index_cmd.add_subparsers(dest="index_action", required=True, parser_class=StrictArgumentParser)
1151
+ for action in ("build", "rebuild", "update"):
1152
+ sp = index_sub.add_parser(action, help=f"{action.capitalize()} the digest-bound frontmatter index.")
1153
+ sp.add_argument("corpus", nargs="?", default=".")
1154
+ sp.add_argument("--output", default=str(DEFAULT_INDEX))
1155
+ sp.set_defaults(func=command_index)
1156
+ for action in ("check", "status", "delete"):
1157
+ sp = index_sub.add_parser(action, help=f"{action.capitalize()} the frontmatter index.")
1158
+ sp.add_argument("--index", default=str(DEFAULT_INDEX))
1159
+ sp.set_defaults(func=command_index)
1160
+
1161
+ query_cmd = sub.add_parser("query", help="Query only freshness-proven canonical frontmatter projections.")
1162
+ query_cmd.add_argument("--index", default=str(DEFAULT_INDEX))
1163
+ query_cmd.add_argument("--task")
1164
+ query_cmd.add_argument("--authority")
1165
+ query_cmd.add_argument("--role")
1166
+ query_cmd.add_argument("--target", help="Return documents whose relationship target matches this document ID.")
1167
+ query_cmd.set_defaults(func=command_query)
1168
+
1169
+ validate = sub.add_parser("validate", help="Validate one Markdown file, many files, or a directory.")
1170
+ validate.add_argument("paths", nargs="*", default=["."])
1171
+ validate.add_argument("--format", choices=["markdown", "json"], default="markdown")
1172
+ validate.set_defaults(func=command_validate)
1173
+
1174
+ mesh = sub.add_parser("mesh", help="Render the frontmatter-only document mesh.")
1175
+ mesh.add_argument("paths", nargs="*", default=["."])
1176
+ mesh.add_argument("--format", choices=["markdown", "json"], default="markdown")
1177
+ mesh.set_defaults(func=command_mesh)
1178
+
1179
+ brief = sub.add_parser("brief", help="Render the cold-start hazard and reading-order brief.")
1180
+ brief.add_argument("paths", nargs="*", default=["."])
1181
+ brief.add_argument("--task", help="Optional read_when_tags task filter.")
1182
+ brief.add_argument(
1183
+ "--section",
1184
+ choices=["summary", "blocks", "traps", "limits", "do-not", "trust", "reading-order", "defects", "claim-boundary"],
1185
+ default="summary",
1186
+ help="Render the bounded summary or expand one hazard section.",
1187
+ )
1188
+ brief.add_argument("--include-dev", action="store_true",
1189
+ help="Include fixtures, archive, mesh, usecase, and distro smoke docs.")
1190
+ brief.add_argument("--format", choices=["markdown", "json"], default="markdown")
1191
+ brief.set_defaults(func=command_brief)
1192
+
1193
+ explain = sub.add_parser("explain", help="Explain one FRONTMATTERING-FM finding identifier.")
1194
+ explain.add_argument("finding_id")
1195
+ explain.add_argument("--format", choices=["markdown", "json"], default="markdown")
1196
+ explain.set_defaults(func=command_explain)
1197
+
1198
+ doctor = sub.add_parser("doctor", help="Run the kernel's targeted self-checks.")
1199
+ doctor.add_argument("--format", choices=["markdown", "json"], default="markdown")
1200
+ doctor.set_defaults(func=command_doctor)
1201
+
1202
+ catalog = sub.add_parser(
1203
+ "catalog",
1204
+ help="Print the closed model: root blocks and controlled vocabularies.",
1205
+ )
1206
+ catalog.add_argument("--field", help="Show one field's type, obligation, and allowed values.")
1207
+ catalog.add_argument("--format", choices=["markdown", "json"], default="markdown")
1208
+ catalog.set_defaults(func=command_catalog)
1209
+
1210
+ schema = sub.add_parser(
1211
+ "schema",
1212
+ help="Emit a corpus-aware JSON Schema projection for editor and CI tooling.",
1213
+ )
1214
+ schema.add_argument("paths", nargs="*", help="Optional corpus paths whose router task_catalog widens the task-tag enum.")
1215
+ schema.add_argument("--output", help="Write the schema projection to this path.")
1216
+ schema.add_argument("--check", metavar="PATH", help="Fail if PATH differs from fresh emission.")
1217
+ schema.add_argument("--vscode", action="store_true", help="Print a VS Code YAML schema settings fragment.")
1218
+ schema.set_defaults(func=command_schema)
1219
+
1220
+ tether = sub.add_parser("tether", help="Check or refresh body/frontmatter freshness tethers.")
1221
+ tether_sub = tether.add_subparsers(dest="tether_action", required=True, parser_class=StrictArgumentParser)
1222
+ tether_check = tether_sub.add_parser("check", help="Report FRESH / STALE / UNTETHERED status for governed Markdown.")
1223
+ tether_check.add_argument("paths", nargs="*", default=["."])
1224
+ tether_check.add_argument("--format", choices=["text", "json"], default="text")
1225
+ tether_check.set_defaults(func=command_tether_check)
1226
+ tether_update = tether_sub.add_parser("update", help="Refresh existing body_tether digests; pass --add to create missing tethers.")
1227
+ tether_update.add_argument("paths", nargs="*", default=["."])
1228
+ tether_update.add_argument("--add", action="store_true", help="Add body_tether blocks to documents that do not already carry one.")
1229
+ tether_update.add_argument("--format", choices=["text", "json"], default="text")
1230
+ tether_update.set_defaults(func=command_tether_update)
1231
+
1232
+ runtime = sub.add_parser("runtime", help="Produce or inspect validate-only Frontmattering runtime artifacts.")
1233
+ runtime_sub = runtime.add_subparsers(dest="runtime_action", required=True, parser_class=StrictArgumentParser)
1234
+ runtime_pack = runtime_sub.add_parser("pack", help="Write a self-contained validate-only runtime artifact.")
1235
+ runtime_pack.add_argument("--out", required=True, help="Output path for the runtime artifact.")
1236
+ runtime_pack.add_argument("--format", choices=["text", "json"], default="text")
1237
+ runtime_pack.set_defaults(func=command_runtime_pack)
1238
+
1239
+ return parser
1240
+
1241
+
1242
+ def main(argv: list[str] | None = None) -> int:
1243
+ parser = build_parser()
1244
+ args = parser.parse_args(argv)
1245
+ try:
1246
+ return args.func(args)
1247
+ except BrokenPipeError:
1248
+ return EXIT_GO
1249
+ except HarnessError as exc:
1250
+ print(exc, file=sys.stderr)
1251
+ return EXIT_INTERNAL
1252
+ except Exception as exc:
1253
+ wrapped = InternalBugError(
1254
+ f"Unexpected kernel exception: {type(exc).__name__}",
1255
+ where="frontmattering.__main__.main",
1256
+ context={"original_error": str(exc), "original_type": type(exc).__name__},
1257
+ )
1258
+ print(wrapped, file=sys.stderr)
1259
+ return EXIT_INTERNAL
1260
+
1261
+
1262
+ if __name__ == "__main__":
1263
+ sys.exit(main())