utterplan 0.6.6__tar.gz → 0.6.7__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.
Files changed (249) hide show
  1. {utterplan-0.6.6 → utterplan-0.6.7}/PKG-INFO +1 -1
  2. {utterplan-0.6.6 → utterplan-0.6.7}/docs/bundles.md +8 -1
  3. {utterplan-0.6.6 → utterplan-0.6.7}/docs/changelog.md +8 -0
  4. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_bundle_replan.py +20 -0
  5. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_bundle_services.py +46 -2
  6. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_cli_bundle_compile_book.py +33 -0
  7. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_cli_bundle_document.py +35 -0
  8. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_ssmd_bundle.py +52 -1
  9. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_ssmd_bundle_document.py +1 -1
  10. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/_book_replan.py +11 -3
  11. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/_book_service.py +9 -2
  12. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/_document_service.py +5 -14
  13. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/_version.py +3 -3
  14. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/bundle.py +4 -0
  15. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/bundle_replan.py +1 -0
  16. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/cli.py +9 -3
  17. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/codecs/v4_toml.py +0 -14
  18. utterplan-0.6.7/utterplan/explain.py +295 -0
  19. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/ssmd_bundle.py +24 -20
  20. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan.egg-info/PKG-INFO +1 -1
  21. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan.egg-info/scm_file_list.json +2 -0
  22. utterplan-0.6.7/utterplan.egg-info/scm_version.json +8 -0
  23. utterplan-0.6.6/utterplan/explain.py +0 -643
  24. utterplan-0.6.6/utterplan.egg-info/scm_version.json +0 -8
  25. {utterplan-0.6.6 → utterplan-0.6.7}/.codecrate.toml +0 -0
  26. {utterplan-0.6.6 → utterplan-0.6.7}/.github/workflows/codecov.yml +0 -0
  27. {utterplan-0.6.6 → utterplan-0.6.7}/.github/workflows/pre-commit.yml +0 -0
  28. {utterplan-0.6.6 → utterplan-0.6.7}/.github/workflows/python-publish.yml +0 -0
  29. {utterplan-0.6.6 → utterplan-0.6.7}/.github/workflows/tests.yml +0 -0
  30. {utterplan-0.6.6 → utterplan-0.6.7}/.gitignore +0 -0
  31. {utterplan-0.6.6 → utterplan-0.6.7}/.pre-commit-config.yaml +0 -0
  32. {utterplan-0.6.6 → utterplan-0.6.7}/AGENTS.md +0 -0
  33. {utterplan-0.6.6 → utterplan-0.6.7}/LICENSE +0 -0
  34. {utterplan-0.6.6 → utterplan-0.6.7}/MANIFEST.in +0 -0
  35. {utterplan-0.6.6 → utterplan-0.6.7}/NOTICE +0 -0
  36. {utterplan-0.6.6 → utterplan-0.6.7}/README.md +0 -0
  37. {utterplan-0.6.6 → utterplan-0.6.7}/docs/Makefile +0 -0
  38. {utterplan-0.6.6 → utterplan-0.6.7}/docs/architecture.md +0 -0
  39. {utterplan-0.6.6 → utterplan-0.6.7}/docs/cli.md +0 -0
  40. {utterplan-0.6.6 → utterplan-0.6.7}/docs/conf.py +0 -0
  41. {utterplan-0.6.6 → utterplan-0.6.7}/docs/consumer-guide.md +0 -0
  42. {utterplan-0.6.6 → utterplan-0.6.7}/docs/coordinate-spaces.md +0 -0
  43. {utterplan-0.6.6 → utterplan-0.6.7}/docs/debugging.md +0 -0
  44. {utterplan-0.6.6 → utterplan-0.6.7}/docs/format.md +0 -0
  45. {utterplan-0.6.6 → utterplan-0.6.7}/docs/getting-started.md +0 -0
  46. {utterplan-0.6.6 → utterplan-0.6.7}/docs/index.md +0 -0
  47. {utterplan-0.6.6 → utterplan-0.6.7}/docs/make.bat +0 -0
  48. {utterplan-0.6.6 → utterplan-0.6.7}/docs/phase-a-completion.md +0 -0
  49. {utterplan-0.6.6 → utterplan-0.6.7}/docs/pykokoro-integration.md +0 -0
  50. {utterplan-0.6.6 → utterplan-0.6.7}/docs/python-api.md +0 -0
  51. {utterplan-0.6.6 → utterplan-0.6.7}/docs/requirements.txt +0 -0
  52. {utterplan-0.6.6 → utterplan-0.6.7}/examples/basic.py +0 -0
  53. {utterplan-0.6.6 → utterplan-0.6.7}/examples/chapter.ssmd +0 -0
  54. {utterplan-0.6.6 → utterplan-0.6.7}/examples/compile_file.py +0 -0
  55. {utterplan-0.6.6 → utterplan-0.6.7}/examples/inspect_plan.py +0 -0
  56. {utterplan-0.6.6 → utterplan-0.6.7}/examples/multilingual.py +0 -0
  57. {utterplan-0.6.6 → utterplan-0.6.7}/examples/ssmd_example.py +0 -0
  58. {utterplan-0.6.6 → utterplan-0.6.7}/pyproject.toml +0 -0
  59. {utterplan-0.6.6 → utterplan-0.6.7}/setup.cfg +0 -0
  60. {utterplan-0.6.6 → utterplan-0.6.7}/spec/schemas/v1.schema.json +0 -0
  61. {utterplan-0.6.6 → utterplan-0.6.7}/spec/schemas/v2.schema.json +0 -0
  62. {utterplan-0.6.6 → utterplan-0.6.7}/spec/schemas/v3.schema.json +0 -0
  63. {utterplan-0.6.6 → utterplan-0.6.7}/spec/schemas/v4.schema.json +0 -0
  64. {utterplan-0.6.6 → utterplan-0.6.7}/spec/utterplan.schema.json +0 -0
  65. {utterplan-0.6.6 → utterplan-0.6.7}/tests/__init__.py +0 -0
  66. {utterplan-0.6.6 → utterplan-0.6.7}/tests/compiler_helpers.py +0 -0
  67. {utterplan-0.6.6 → utterplan-0.6.7}/tests/fixtures/canonical_consumer_contract.expected.toml +0 -0
  68. {utterplan-0.6.6 → utterplan-0.6.7}/tests/fixtures/canonical_consumer_contract.ssmd +0 -0
  69. {utterplan-0.6.6 → utterplan-0.6.7}/tests/fixtures/document-bundle-v1/story.ssmd-bundle/document.ssmd.md +0 -0
  70. {utterplan-0.6.6 → utterplan-0.6.7}/tests/fixtures/document-bundle-v1/story.ssmd-bundle/manifest.json +0 -0
  71. {utterplan-0.6.6 → utterplan-0.6.7}/tests/fixtures/ssmd_09_comprehensive.ssmd +0 -0
  72. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/basic_en.utterplan.toml +0 -0
  73. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/directives.utterplan.toml +0 -0
  74. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/markers.utterplan.toml +0 -0
  75. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/multilingual.utterplan.toml +0 -0
  76. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/parenthetical.utterplan.toml +0 -0
  77. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/spokenform_offsets.utterplan.toml +0 -0
  78. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/ssmd_09_comprehensive.utterplan.toml +0 -0
  79. {utterplan-0.6.6 → utterplan-0.6.7}/tests/golden/ssmd_breaks.utterplan.toml +0 -0
  80. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/__init__.py +0 -0
  81. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/cases.py +0 -0
  82. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/basic_en.utterplan.json +0 -0
  83. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/directives.utterplan.json +0 -0
  84. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/markers.utterplan.json +0 -0
  85. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/multilingual.utterplan.json +0 -0
  86. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/parenthetical.utterplan.json +0 -0
  87. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/spokenform_offsets.utterplan.json +0 -0
  88. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/ssmd_09_comprehensive.utterplan.json +0 -0
  89. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/fixtures/v4/ssmd_breaks.utterplan.json +0 -0
  90. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/goldens/multilingual_preparation.json +0 -0
  91. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/goldens/spokenform_numbers.json +0 -0
  92. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/goldens/ssmd_break.json +0 -0
  93. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/goldens/ssmd_language_detection.json +0 -0
  94. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/goldens/ssmd_marker.json +0 -0
  95. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/goldens/ssmd_pronunciation.json +0 -0
  96. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/test_legacy_json_import.py +0 -0
  97. {utterplan-0.6.6 → utterplan-0.6.7}/tests/migration/test_semantic_migration_regressions.py +0 -0
  98. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/README.md +0 -0
  99. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v1/basic.json +0 -0
  100. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v1/directives.json +0 -0
  101. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v1/markers.json +0 -0
  102. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v1/multilingual.json +0 -0
  103. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v1/parenthetical.json +0 -0
  104. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v1/spokenform_offsets.json +0 -0
  105. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v1/ssmd_breaks.json +0 -0
  106. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v2/directives.json +0 -0
  107. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v3/multilingual.json +0 -0
  108. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v3/ssmd_09_comprehensive.json +0 -0
  109. {utterplan-0.6.6 → utterplan-0.6.7}/tests/schema_history/v5/canonical_consumer_contract.toml +0 -0
  110. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_atomic_io.py +0 -0
  111. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_batch.py +0 -0
  112. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_boundaries.py +0 -0
  113. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_bundle_book_replan.py +0 -0
  114. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_bundle_book_services.py +0 -0
  115. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_bundle_document_compile.py +0 -0
  116. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_bundle_document_render.py +0 -0
  117. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_cli.py +0 -0
  118. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_cli_batch.py +0 -0
  119. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_cli_bundle_render_book.py +0 -0
  120. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_cli_bundle_render_document.py +0 -0
  121. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_cli_bundle_replan_book.py +0 -0
  122. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_compiler.py +0 -0
  123. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_consumer_contract.py +0 -0
  124. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_consumer_readiness.py +0 -0
  125. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_coordinates.py +0 -0
  126. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_dependency_architecture.py +0 -0
  127. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_directives.py +0 -0
  128. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_explain.py +0 -0
  129. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_fake_renderer.py +0 -0
  130. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_flow_compilation.py +0 -0
  131. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_format.py +0 -0
  132. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_golden.py +0 -0
  133. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_identity_units.py +0 -0
  134. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_import_boundaries.py +0 -0
  135. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_language.py +0 -0
  136. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_linguistics.py +0 -0
  137. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_migration_framework.py +0 -0
  138. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_migration_v3_to_v4.py +0 -0
  139. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_migration_v4_to_v5.py +0 -0
  140. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_pauses.py +0 -0
  141. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_planner.py +0 -0
  142. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_preparation.py +0 -0
  143. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_preview.py +0 -0
  144. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_progress.py +0 -0
  145. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_public_identity.py +0 -0
  146. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_renderability.py +0 -0
  147. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_renderability_repair.py +0 -0
  148. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_renderplan_batch.py +0 -0
  149. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_renderplan_dependency_contract.py +0 -0
  150. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_renderplan_export.py +0 -0
  151. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_renderplan_replan.py +0 -0
  152. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_renderplan_v7_contract.py +0 -0
  153. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_replacement_provenance.py +0 -0
  154. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_role_coverage.py +0 -0
  155. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_schema_history.py +0 -0
  156. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_schema_registry.py +0 -0
  157. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_schema_strict.py +0 -0
  158. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_schema_v6_source.py +0 -0
  159. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_schema_v7.py +0 -0
  160. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_segment_topology.py +0 -0
  161. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_segmentation_adapter.py +0 -0
  162. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_segmentation_recovery.py +0 -0
  163. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_semantic_boundaries.py +0 -0
  164. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_sentence_topology.py +0 -0
  165. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_source_input.py +0 -0
  166. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_ssmd_09_parser.py +0 -0
  167. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_ssmd_bundle_book.py +0 -0
  168. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_ssmd_substitution_preparation.py +0 -0
  169. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_token_topology.py +0 -0
  170. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_toml_format.py +0 -0
  171. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_ttsready_preparation_adapter.py +0 -0
  172. {utterplan-0.6.6 → utterplan-0.6.7}/tests/test_version.py +0 -0
  173. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/__init__.py +0 -0
  174. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/__main__.py +0 -0
  175. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/atomic_io.py +0 -0
  176. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/attempt_serialization.py +0 -0
  177. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/attempts.py +0 -0
  178. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/batch.py +0 -0
  179. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/boundaries.py +0 -0
  180. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/codecs/__init__.py +0 -0
  181. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/codecs/v5_toml.py +0 -0
  182. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/codecs/v6_toml.py +0 -0
  183. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/codecs/v7_toml.py +0 -0
  184. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/compiler.py +0 -0
  185. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/config.py +0 -0
  186. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/diagnostics.py +0 -0
  187. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/directives.py +0 -0
  188. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/exceptions.py +0 -0
  189. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/flow_projection.py +0 -0
  190. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/format.py +0 -0
  191. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/hashing.py +0 -0
  192. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/language.py +0 -0
  193. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/linguistics.py +0 -0
  194. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migration.py +0 -0
  195. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/__init__.py +0 -0
  196. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/registry.py +0 -0
  197. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/v1_to_v2.py +0 -0
  198. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/v2_to_v3.py +0 -0
  199. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/v3_to_v4.py +0 -0
  200. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/v4_to_v5.py +0 -0
  201. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/v5_to_v6.py +0 -0
  202. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/migrations/v6_to_v7.py +0 -0
  203. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/model.py +0 -0
  204. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/parsers.py +0 -0
  205. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/pauses.py +0 -0
  206. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/planner.py +0 -0
  207. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/preparation.py +0 -0
  208. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/preview.py +0 -0
  209. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/progress.py +0 -0
  210. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/py.typed +0 -0
  211. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/renderability.py +0 -0
  212. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/renderplan_batch.py +0 -0
  213. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/renderplan_export.py +0 -0
  214. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/renderplan_grouping.py +0 -0
  215. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/renderplan_projection.py +0 -0
  216. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/renderplan_replan.py +0 -0
  217. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/renderplan_workspace.py +0 -0
  218. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/role_coverage.py +0 -0
  219. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/schema_registry.py +0 -0
  220. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/schemas/v1.schema.json +0 -0
  221. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/schemas/v2.schema.json +0 -0
  222. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/schemas/v3.schema.json +0 -0
  223. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/schemas/v4.schema.json +0 -0
  224. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/segmentation.py +0 -0
  225. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/serialization.py +0 -0
  226. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/source_input.py +0 -0
  227. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/spacy_models.py +0 -0
  228. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/spans.py +0 -0
  229. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/__init__.py +0 -0
  230. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/document/__init__.py +0 -0
  231. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/document/plain.py +0 -0
  232. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/document/ssmd.py +0 -0
  233. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/preparation/__init__.py +0 -0
  234. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/preparation/identity.py +0 -0
  235. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/preparation/ssmd.py +0 -0
  236. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/preparation/ttsready.py +0 -0
  237. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/stages/protocols.py +0 -0
  238. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/token_topology.py +0 -0
  239. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/toml_codec.py +0 -0
  240. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/trace_codec.py +0 -0
  241. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/units.py +0 -0
  242. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/utterplan.schema.json +0 -0
  243. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/versioning.py +0 -0
  244. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan/workspace_paths.py +0 -0
  245. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan.egg-info/SOURCES.txt +0 -0
  246. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan.egg-info/dependency_links.txt +0 -0
  247. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan.egg-info/entry_points.txt +0 -0
  248. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan.egg-info/requires.txt +0 -0
  249. {utterplan-0.6.6 → utterplan-0.6.7}/utterplan.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: utterplan
3
- Version: 0.6.6
3
+ Version: 0.6.7
4
4
  Summary: Engine-independent text-to-speech planning compiler and interchange format
5
5
  Author-email: Holger Nahrstaedt <nahrstaedt@gmail.com>
6
6
  License: Apache License
@@ -26,6 +26,13 @@ utterplan bundle compile novel.ssmd-bundle --chapters 5-17
26
26
  utterplan bundle compile story.ssmd-bundle --text-preparation identity
27
27
  ```
28
28
 
29
+ When already inside a bundle root, `.` is also accepted because the supplied path is resolved before the directory name is validated:
30
+
31
+ ```bash
32
+ cd story.ssmd-bundle
33
+ utterplan bundle compile --lang en .
34
+ ```
35
+
29
36
  Compilation defaults to all items. For book bundles, `--chapters` selects manifest `source_number` values: one number, an inclusive range, or comma-separated combinations such as `1,3-5,9`. Selected items retain manifest order. A document bundle accepts only `--chapters all`.
30
37
 
31
38
  By default, one schema-v7 `.utterplan.toml` plan per selected item and `compile-report.toml` are written under `<bundle>/utterplan/`. `--output-dir` and `--report` override those destinations. Existing plans are protected unless `--force` is supplied; `--fail-fast` records later selected items as skipped after a failure. `--language` is a fallback for SSMD language metadata. Bundle sources are SSMD 0.9, so the command does not accept an input-format override. The report is operational metadata and does not affect plan identity.
@@ -58,7 +65,7 @@ The full variant directory name, including its profile fingerprint, is required.
58
65
 
59
66
  ## Safe and read-only behavior
60
67
 
61
- The bundle path must be a directory named `<name>.ssmd-bundle` containing a regular `manifest.json`. Manifest item paths must be normalized relative POSIX paths, remain inside the resolved root, and resolve to regular files. Symlink escapes and unsafe output collisions are rejected. Manifest size and document size are bounded. Source digests are not used to replace current edited content; the manifest and source files are never refreshed or rewritten.
68
+ The supplied workspace path is resolved first. The resolved directory must be named `<name>.ssmd-bundle` and contain a regular, non-symlink `manifest.json`. Therefore `.` and equivalent aliases are valid when they resolve to the bundle root; paths to `chapters/` or an item file remain invalid. Manifest item paths must be normalized relative POSIX paths, remain inside the resolved root, and resolve to regular files. Symlink escapes and unsafe output collisions are rejected. Manifest size and document size are bounded. Source digests are not used to replace current edited content; the manifest and source files are never refreshed or rewritten.
62
69
 
63
70
  ## Python API
64
71
 
@@ -4,6 +4,14 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [v0.6.7] - 2026-10-22
8
+
9
+ ### Fixed
10
+
11
+ - Fixed bundle path detection when a path resolves to an SSMD bundle root
12
+
13
+ <!-- generated by releaseledger -->
14
+
7
15
  ## [v0.6.6] - 2026-10-21
8
16
 
9
17
  ### Added
@@ -10,8 +10,10 @@ from renderplan import codec as renderplan_codec
10
10
 
11
11
  from tests.test_bundle_book_replan import _active_plan, _save_preflight, _setup
12
12
  from tests.test_bundle_services import _planner_config, _profile
13
+ from utterplan._book_replan import BookReplanError, replan_book
13
14
  from utterplan.bundle import compile_bundle, render_bundle
14
15
  from utterplan.bundle_replan import BundleReplanError, replan_bundle
16
+ from utterplan.ssmd_bundle import load_ssmd_bundle
15
17
 
16
18
  DOCUMENT_FIXTURE = Path(__file__).parent / "fixtures/document-bundle-v1/story.ssmd-bundle"
17
19
 
@@ -62,13 +64,24 @@ def _document_preflight(root: Path, variant: str, active_path: Path) -> Path:
62
64
 
63
65
  def test_replan_bundle_keeps_book_set_selection_and_immutable_child_behavior(
64
66
  tmp_path: Path,
67
+ monkeypatch: pytest.MonkeyPatch,
65
68
  ) -> None:
66
69
  root, variant, variant_dir, _ = _setup(tmp_path, numbers=(5, 6))
67
70
  entry, parent, _ = _active_plan(variant_dir)
71
+ loaded_indexes = []
72
+
73
+ def tracked_load(path):
74
+ index = load_ssmd_bundle(path)
75
+ loaded_indexes.append(index)
76
+ return index
77
+
78
+ monkeypatch.setattr("utterplan.bundle_replan.load_ssmd_bundle", tracked_load)
79
+ monkeypatch.setattr("utterplan._book_replan.load_ssmd_bundle", tracked_load)
68
80
  preflight_path = _save_preflight(root / "voicerender", variant, entry.id, parent)
69
81
  starts = []
70
82
 
71
83
  result = replan_bundle(root, variant=variant, chapters="5", on_start=starts.append)
84
+ assert len(loaded_indexes) == 1
72
85
 
73
86
  assert result.kind == "book"
74
87
  assert result.selected_item_ids == ("chapter-0005",)
@@ -133,3 +146,10 @@ def test_document_replan_rejects_non_all_selector(tmp_path: Path) -> None:
133
146
  root = _document_bundle(tmp_path / "story.ssmd-bundle")
134
147
  with pytest.raises(BundleReplanError, match="document bundles only support --chapters all"):
135
148
  replan_bundle(root, variant="any", chapters="1")
149
+
150
+
151
+ def test_book_replan_rejects_document_bundle_direct_call(tmp_path: Path) -> None:
152
+ root = _document_bundle(tmp_path / "story.ssmd-bundle")
153
+
154
+ with pytest.raises(BookReplanError, match="bundle manifest format is not a book"):
155
+ replan_book(root, variant="not-a-book")
@@ -8,8 +8,10 @@ import pytest
8
8
  from renderplan import PausePolicy, PlanningPolicy, RenderProfile, RoleProfile
9
9
  from renderplan import codec as renderplan_codec
10
10
 
11
+ from utterplan._book_service import BookServiceError, compile_book, render_book
11
12
  from utterplan.bundle import BundleServiceError, compile_bundle, render_bundle
12
13
  from utterplan.config import LinguisticsConfig, PlannerConfig
14
+ from utterplan.ssmd_bundle import load_ssmd_bundle
13
15
 
14
16
  DOCUMENT_FIXTURE = Path(__file__).parent / "fixtures/document-bundle-v1/story.ssmd-bundle"
15
17
 
@@ -76,7 +78,7 @@ def _profile(path: Path) -> RenderProfile:
76
78
 
77
79
  @pytest.mark.parametrize("kind", ["book", "document"])
78
80
  def test_compile_bundle_dispatches_both_kinds_and_returns_uniform_result(
79
- tmp_path: Path, kind: str
81
+ tmp_path: Path, kind: str, monkeypatch: pytest.MonkeyPatch
80
82
  ) -> None:
81
83
  root = (
82
84
  _book(tmp_path / "novel.ssmd-bundle")
@@ -84,6 +86,19 @@ def test_compile_bundle_dispatches_both_kinds_and_returns_uniform_result(
84
86
  else _document(tmp_path / "story.ssmd-bundle")
85
87
  )
86
88
  starts = []
89
+ loaded_indexes = []
90
+
91
+ def tracked_load(path):
92
+ index = load_ssmd_bundle(path)
93
+ loaded_indexes.append(index)
94
+ return index
95
+
96
+ for module in (
97
+ "utterplan.bundle",
98
+ "utterplan._book_service",
99
+ "utterplan._document_service",
100
+ ):
101
+ monkeypatch.setattr(f"{module}.load_ssmd_bundle", tracked_load)
87
102
 
88
103
  result = compile_bundle(root, planner_config=_planner_config(), on_start=starts.append)
89
104
 
@@ -100,6 +115,8 @@ def test_compile_bundle_dispatches_both_kinds_and_returns_uniform_result(
100
115
  assert starts[0].selected_item_ids == result.selected_item_ids
101
116
  assert result.report_path.is_file()
102
117
 
118
+ assert len(loaded_indexes) == 1
119
+
103
120
 
104
121
  def test_document_bundle_only_accepts_all_chapters_for_compile_and_render(tmp_path: Path) -> None:
105
122
  root = _document(tmp_path / "story.ssmd-bundle")
@@ -110,18 +127,45 @@ def test_document_bundle_only_accepts_all_chapters_for_compile_and_render(tmp_pa
110
127
  render_bundle(root, profile=tmp_path / "missing.renderprofile.toml", chapters="2")
111
128
 
112
129
 
113
- def test_render_bundle_dispatches_book_and_document_with_uniform_outputs(tmp_path: Path) -> None:
130
+ def test_book_services_reject_document_bundle_direct_calls(tmp_path: Path) -> None:
131
+ root = _document(tmp_path / "story.ssmd-bundle")
132
+
133
+ with pytest.raises(BookServiceError, match="bundle manifest format is not a book"):
134
+ compile_book(root)
135
+ with pytest.raises(BookServiceError, match="bundle manifest format is not a book"):
136
+ render_book(root, profile=tmp_path / "missing.renderprofile.toml")
137
+
138
+
139
+ def test_render_bundle_dispatches_book_and_document_with_uniform_outputs(
140
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
141
+ ) -> None:
142
+ loaded_indexes = []
143
+
144
+ def tracked_load(path):
145
+ index = load_ssmd_bundle(path)
146
+ loaded_indexes.append(index)
147
+ return index
148
+
149
+ for module in (
150
+ "utterplan.bundle",
151
+ "utterplan._book_service",
152
+ "utterplan._document_service",
153
+ ):
154
+ monkeypatch.setattr(f"{module}.load_ssmd_bundle", tracked_load)
155
+
114
156
  for kind, root in (
115
157
  ("book", _book(tmp_path / "novel.ssmd-bundle")),
116
158
  ("document", _document(tmp_path / "story.ssmd-bundle")),
117
159
  ):
118
160
  compile_bundle(root, planner_config=_planner_config())
119
161
  profile_path = root / f"{kind}.renderprofile.toml"
162
+ loaded_indexes.clear()
120
163
  _profile(profile_path)
121
164
  starts = []
122
165
 
123
166
  result = render_bundle(root, profile=profile_path, on_start=starts.append)
124
167
 
168
+ assert len(loaded_indexes) == 1
125
169
  assert result.kind == kind
126
170
  assert result.workspace == root
127
171
  assert result.exit_code == 0
@@ -166,6 +166,39 @@ def test_compile_book_root_range_defaults_to_utterplan_and_writes_v6_plans(
166
166
  assert (root / "manifest.json").read_bytes() == manifest_before
167
167
 
168
168
 
169
+ def test_bundle_compile_accepts_dot_from_current_book_bundle(
170
+ tmp_path: Path,
171
+ monkeypatch: pytest.MonkeyPatch,
172
+ capsys: pytest.CaptureFixture[str],
173
+ ) -> None:
174
+ root = tmp_path / "book.ssmd-bundle"
175
+ _write_book(root, (5,), contents={5: _ssmd("Chapter 5.")})
176
+ manifest = root / "manifest.json"
177
+ manifest_before = manifest.read_bytes()
178
+ monkeypatch.chdir(root)
179
+
180
+ result = main(
181
+ [
182
+ "bundle",
183
+ "compile",
184
+ "--lang",
185
+ "en",
186
+ ".",
187
+ "--text-preparation",
188
+ "identity",
189
+ "--spacy",
190
+ "off",
191
+ ]
192
+ )
193
+
194
+ capsys.readouterr()
195
+ assert result == 0
196
+ assert (root / "utterplan/chapter-0005.utterplan.toml").is_file()
197
+ assert (root / "utterplan/compile-report.toml").is_file()
198
+ assert _plan_text(root / "utterplan/chapter-0005.utterplan.toml") == "Chapter 5."
199
+ assert manifest.read_bytes() == manifest_before
200
+
201
+
169
202
  def test_compile_book_report_records_ssmd_mode_and_repaired_cut_details(
170
203
  tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
171
204
  ) -> None:
@@ -84,6 +84,41 @@ def test_compile_document_uses_manifest_kind_with_canonical_root(
84
84
  assert (root / "utterplan/document.utterplan.toml").is_file()
85
85
 
86
86
 
87
+ def test_bundle_compile_accepts_dot_from_current_document_bundle(
88
+ tmp_path: Path,
89
+ monkeypatch: pytest.MonkeyPatch,
90
+ capsys: pytest.CaptureFixture[str],
91
+ ) -> None:
92
+ root = _workspace(tmp_path)
93
+ manifest = root / "manifest.json"
94
+ source = root / "document.ssmd.md"
95
+ manifest_before = manifest.read_bytes()
96
+ source_before = source.read_bytes()
97
+ monkeypatch.chdir(root)
98
+
99
+ result = main(
100
+ [
101
+ "bundle",
102
+ "compile",
103
+ "--lang",
104
+ "en",
105
+ ".",
106
+ "--text-preparation",
107
+ "identity",
108
+ "--spacy",
109
+ "off",
110
+ ]
111
+ )
112
+
113
+ capsys.readouterr()
114
+ assert result == 0
115
+ assert (root / "utterplan/document.utterplan.toml").is_file()
116
+ assert (root / "utterplan/compile-report.toml").is_file()
117
+ assert "Original content." in _plan_text(root / "utterplan/document.utterplan.toml")
118
+ assert manifest.read_bytes() == manifest_before
119
+ assert source.read_bytes() == source_before
120
+
121
+
87
122
  def test_compile_document_uses_dirty_current_content_without_refreshing_manifest(
88
123
  tmp_path: Path, capsys: pytest.CaptureFixture[str]
89
124
  ) -> None:
@@ -111,6 +111,25 @@ def test_rejects_non_root_workspace_inputs(tmp_path: Path) -> None:
111
111
  resolve_ssmd_bundle_root(tmp_path / "legacy-workspace")
112
112
 
113
113
 
114
+ @pytest.mark.parametrize(
115
+ ("working_directory", "path"),
116
+ [("root", "."), ("root", "./"), ("child", "..")],
117
+ )
118
+ def test_resolves_bundle_root_path_aliases(
119
+ tmp_path: Path,
120
+ monkeypatch: pytest.MonkeyPatch,
121
+ working_directory: str,
122
+ path: str,
123
+ ) -> None:
124
+ root = tmp_path / "story.ssmd-bundle"
125
+ _write_manifest(root, _document_manifest())
126
+ (root / "child").mkdir()
127
+ monkeypatch.chdir(root if working_directory == "root" else root / "child")
128
+
129
+ assert resolve_ssmd_bundle_root(path) == root.resolve()
130
+ assert load_ssmd_bundle(path).root == root.resolve()
131
+
132
+
114
133
  def test_rejects_bundle_root_without_manifest_or_regular_root_directory(tmp_path: Path) -> None:
115
134
  root = tmp_path / "empty.ssmd-bundle"
116
135
  root.mkdir()
@@ -207,5 +226,37 @@ def test_rejects_manifest_symlink_escape(tmp_path: Path) -> None:
207
226
  except (OSError, NotImplementedError) as exc:
208
227
  pytest.skip(f"symlinks unavailable: {exc}")
209
228
 
210
- with pytest.raises(SsmdBundleError, match="cannot read bundle manifest"):
229
+ with pytest.raises(SsmdBundleError, match="regular file, not a symlink"):
211
230
  load_ssmd_bundle(root)
231
+
232
+
233
+ def test_rejects_manifest_symlink_to_file_inside_bundle(tmp_path: Path) -> None:
234
+ root = tmp_path / "internal-manifest-link.ssmd-bundle"
235
+ root.mkdir()
236
+ target = root / "manifest-copy.json"
237
+ target.write_text(json.dumps(_book_manifest()), encoding="utf-8")
238
+ try:
239
+ (root / "manifest.json").symlink_to(target)
240
+ except (OSError, NotImplementedError) as exc:
241
+ pytest.skip(f"symlinks unavailable: {exc}")
242
+
243
+ with pytest.raises(SsmdBundleError, match="regular file, not a symlink"):
244
+ load_ssmd_bundle(root)
245
+
246
+
247
+ def test_allows_item_symlink_target_inside_bundle(tmp_path: Path) -> None:
248
+ root = tmp_path / "item-link.ssmd-bundle"
249
+ _write_manifest(root, _book_manifest())
250
+ target = root / "chapter-source.ssmd.md"
251
+ target.write_text("chapter content", encoding="utf-8")
252
+ item_path = root / "chapters/chapter-0005.ssmd.md"
253
+ item_path.parent.mkdir()
254
+ try:
255
+ item_path.symlink_to(target)
256
+ except (OSError, NotImplementedError) as exc:
257
+ pytest.skip(f"symlinks unavailable: {exc}")
258
+
259
+ index = load_ssmd_bundle(root)
260
+ resolved_item = bundle_item_path(index, index.items[0], require_exists=True)
261
+ assert resolved_item == item_path
262
+ assert resolved_item.read_text(encoding="utf-8") == "chapter content"
@@ -196,5 +196,5 @@ def test_rejects_manifest_symlink_escape(tmp_path: Path) -> None:
196
196
  (root / "manifest.json").symlink_to(outside)
197
197
  except (OSError, NotImplementedError) as exc:
198
198
  pytest.skip(f"symlinks unavailable: {exc}")
199
- with pytest.raises(SsmdBundleError, match="cannot read bundle manifest"):
199
+ with pytest.raises(SsmdBundleError, match="regular file, not a symlink"):
200
200
  load_ssmd_bundle(root)
@@ -180,6 +180,7 @@ def replan_book(
180
180
  fail_fast: bool = False,
181
181
  on_progress: ReplanProgressCallback | None = None,
182
182
  on_start: BookReplanStartCallback | None = None,
183
+ _index: SsmdBundleIndex | None = None,
183
184
  ) -> BookReplanResult:
184
185
  """Refine selected active RenderPlans using their exact matching preflight reports.
185
186
 
@@ -197,7 +198,7 @@ def replan_book(
197
198
  dry_run=dry_run,
198
199
  fail_fast=fail_fast,
199
200
  )
200
- context = _prepare_book(request)
201
+ context = _prepare_book(request, index=_index)
201
202
  selected_ids = tuple(chapter.id for chapter in context.chapters)
202
203
  _ensure_report_not_input(context)
203
204
  if on_start is not None:
@@ -275,9 +276,16 @@ def replan_book(
275
276
  )
276
277
 
277
278
 
278
- def _prepare_book(request: BookReplanRequest) -> _BookContext:
279
+ def _prepare_book(
280
+ request: BookReplanRequest, *, index: SsmdBundleIndex | None = None
281
+ ) -> _BookContext:
279
282
  try:
280
- index = load_ssmd_bundle(request.book)
283
+ index = index if index is not None else load_ssmd_bundle(request.book)
284
+ if index.kind != "book":
285
+ raise BookReplanError(
286
+ "bundle manifest format is not a book",
287
+ code="renderplan.replan.book.bundle_invalid",
288
+ )
281
289
  selected = select_bundle_items(index, request.chapters)
282
290
  except SsmdBundleError as exc:
283
291
  code = (
@@ -29,6 +29,7 @@ from .renderplan_batch import RenderOutcome, RenderRequest, new_renderplan_set,
29
29
  from .role_coverage import profile_role_coverage
30
30
  from .ssmd_bundle import (
31
31
  SsmdBundleError,
32
+ SsmdBundleIndex,
32
33
  bundle_item_path,
33
34
  load_ssmd_bundle,
34
35
  select_bundle_items,
@@ -182,6 +183,7 @@ def compile_book(
182
183
  diagnostics: Literal["errors", "warnings", "all", "none"] = "errors",
183
184
  on_progress: BatchProgressCallback | None = None,
184
185
  on_start: BookCompileStartCallback | None = None,
186
+ _index: SsmdBundleIndex | None = None,
185
187
  ) -> BookCompileResult:
186
188
  """Compile selected chapters and atomically write schema-v7 UtterPlans.
187
189
 
@@ -201,7 +203,9 @@ def compile_book(
201
203
  planner_config=planner_config,
202
204
  )
203
205
  try:
204
- index = load_ssmd_bundle(request.book)
206
+ index = _index if _index is not None else load_ssmd_bundle(request.book)
207
+ if index.kind != "book":
208
+ raise BookServiceError("bundle manifest format is not a book")
205
209
  chapters_selected = select_bundle_items(index, request.chapters)
206
210
  sources = [bundle_item_path(index, chapter) for chapter in chapters_selected]
207
211
  except SsmdBundleError as exc:
@@ -294,6 +298,7 @@ def render_book(
294
298
  fail_fast: bool = False,
295
299
  on_progress: RenderProgressCallback | None = None,
296
300
  on_start: BookRenderStartCallback | None = None,
301
+ _index: SsmdBundleIndex | None = None,
297
302
  ) -> BookRenderResult:
298
303
  """Project selected schema-v7 FlowPlans to a profile-specific RenderPlan set."""
299
304
  request = BookRenderRequest(
@@ -308,7 +313,9 @@ def render_book(
308
313
  fail_fast=fail_fast,
309
314
  )
310
315
  try:
311
- index = load_ssmd_bundle(request.book)
316
+ index = _index if _index is not None else load_ssmd_bundle(request.book)
317
+ if index.kind != "book":
318
+ raise BookServiceError("bundle manifest format is not a book")
312
319
  chapters_selected = select_bundle_items(index, request.chapters)
313
320
  sources = [bundle_item_path(index, chapter) for chapter in chapters_selected]
314
321
  except SsmdBundleError as exc:
@@ -17,7 +17,7 @@ from .model import FlowPlan
17
17
  from .renderplan_export import build_render_plan
18
18
  from .renderplan_workspace import profile_fingerprint, render_variant_label
19
19
  from .role_coverage import assert_profile_role_coverage
20
- from .ssmd_bundle import SsmdBundleError, bundle_item_path, load_ssmd_bundle
20
+ from .ssmd_bundle import SsmdBundleError, SsmdBundleIndex, bundle_item_path, load_ssmd_bundle
21
21
  from .workspace_paths import renderplan_output_name, utterplan_output_name
22
22
 
23
23
 
@@ -94,6 +94,7 @@ def _compile_document_service(
94
94
  diagnostics: Literal["errors", "warnings", "all", "none"] = "errors",
95
95
  on_progress: BatchProgressCallback | None = None,
96
96
  on_start: DocumentCompileStartCallback | None = None,
97
+ bundle_index: SsmdBundleIndex | None = None,
97
98
  ) -> DocumentCompileResult:
98
99
  """Compile the current canonical SSMD document and write a one-item report."""
99
100
  request = DocumentCompileRequest(
@@ -106,7 +107,7 @@ def _compile_document_service(
106
107
  diagnostics=diagnostics,
107
108
  )
108
109
  try:
109
- index = load_ssmd_bundle(request.workspace)
110
+ index = bundle_index if bundle_index is not None else load_ssmd_bundle(request.workspace)
110
111
  if index.kind != "document":
111
112
  raise _DocumentBundleServiceError("bundle manifest format is not a document")
112
113
  item = index.items[0]
@@ -170,17 +171,6 @@ def _compile_document_service(
170
171
  )
171
172
 
172
173
 
173
- @dataclass(frozen=True, slots=True)
174
- class DocumentRenderRequest:
175
- workspace: Path
176
- profile: Path
177
- variant: str | None = None
178
- utterplan_dir: Path | None = None
179
- output_root: Path | None = None
180
- report: Path | None = None
181
- force: bool = False
182
-
183
-
184
174
  @dataclass(frozen=True, slots=True)
185
175
  class DocumentRenderStart:
186
176
  workspace: Path
@@ -230,10 +220,11 @@ def _render_document_service(
230
220
  report: str | Path | None = None,
231
221
  force: bool = False,
232
222
  on_start: DocumentRenderStartCallback | None = None,
223
+ bundle_index: SsmdBundleIndex | None = None,
233
224
  ) -> DocumentRenderResult:
234
225
  """Project the saved document FlowPlan to one profile-specific RenderPlan."""
235
226
  try:
236
- index = load_ssmd_bundle(workspace)
227
+ index = bundle_index if bundle_index is not None else load_ssmd_bundle(workspace)
237
228
  if index.kind != "document":
238
229
  raise _DocumentBundleServiceError("bundle manifest format is not a document")
239
230
  item = index.items[0]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.6.6'
22
- __version_tuple__ = version_tuple = (0, 6, 6)
21
+ __version__ = version = '0.6.7'
22
+ __version_tuple__ = version_tuple = (0, 6, 7)
23
23
 
24
- __commit_id__ = commit_id = 'ga70e4a166'
24
+ __commit_id__ = commit_id = 'g5282696da'
@@ -119,6 +119,7 @@ def compile_bundle(
119
119
  try:
120
120
  book_result = compile_book(
121
121
  index.root,
122
+ _index=index,
122
123
  chapters=selector,
123
124
  output_dir=output_dir,
124
125
  report=report,
@@ -160,6 +161,7 @@ def compile_bundle(
160
161
  try:
161
162
  document_result = _compile_document_service(
162
163
  index.root,
164
+ bundle_index=index,
163
165
  output_dir=output_dir,
164
166
  report=report,
165
167
  language=language,
@@ -221,6 +223,7 @@ def render_bundle(
221
223
  try:
222
224
  book_result = render_book(
223
225
  index.root,
226
+ _index=index,
224
227
  profile=profile,
225
228
  chapters=selector,
226
229
  variant=variant,
@@ -273,6 +276,7 @@ def render_bundle(
273
276
  try:
274
277
  document_result = _render_document_service(
275
278
  index.root,
279
+ bundle_index=index,
276
280
  profile=profile,
277
281
  variant=variant,
278
282
  utterplan_dir=utterplan_dir,
@@ -159,6 +159,7 @@ def replan_bundle(
159
159
  dry_run=dry_run,
160
160
  fail_fast=fail_fast,
161
161
  on_progress=on_progress,
162
+ _index=index,
162
163
  on_start=(
163
164
  lambda start: (
164
165
  on_start(
@@ -186,7 +186,9 @@ def build_parser() -> argparse.ArgumentParser:
186
186
  bundle_commands = bundle_parser.add_subparsers(dest="bundle_command", required=True)
187
187
 
188
188
  bundle_compile = bundle_commands.add_parser("compile", help="compile a book or document bundle")
189
- bundle_compile.add_argument("workspace", type=Path, help="canonical NAME.ssmd-bundle directory")
189
+ bundle_compile.add_argument(
190
+ "workspace", type=Path, help="path resolving to a canonical NAME.ssmd-bundle directory"
191
+ )
190
192
  bundle_compile.add_argument("--chapters", default="all", metavar="SELECTOR")
191
193
  bundle_compile.add_argument("--output-dir", type=Path)
192
194
  bundle_compile.add_argument("--language", "--lang", dest="language")
@@ -205,7 +207,9 @@ def build_parser() -> argparse.ArgumentParser:
205
207
  )
206
208
 
207
209
  bundle_render = bundle_commands.add_parser("render", help="render a book or document bundle")
208
- bundle_render.add_argument("workspace", type=Path, help="canonical NAME.ssmd-bundle directory")
210
+ bundle_render.add_argument(
211
+ "workspace", type=Path, help="path resolving to a canonical NAME.ssmd-bundle directory"
212
+ )
209
213
  bundle_render.add_argument("--profile", type=Path, required=True)
210
214
  bundle_render.add_argument("--chapters", default="all", metavar="SELECTOR")
211
215
  bundle_render.add_argument("--variant")
@@ -216,7 +220,9 @@ def build_parser() -> argparse.ArgumentParser:
216
220
  bundle_render.add_argument("--fail-fast", action="store_true")
217
221
 
218
222
  bundle_replan = bundle_commands.add_parser("replan", help="refine active bundle RenderPlans")
219
- bundle_replan.add_argument("workspace", type=Path, help="canonical NAME.ssmd-bundle directory")
223
+ bundle_replan.add_argument(
224
+ "workspace", type=Path, help="path resolving to a canonical NAME.ssmd-bundle directory"
225
+ )
220
226
  bundle_replan.add_argument("--variant", required=True, help="exact existing RenderPlan variant")
221
227
  bundle_replan.add_argument("--chapters", default="all", metavar="SELECTOR")
222
228
  bundle_replan.add_argument("--renderplan-root", type=Path)
@@ -218,20 +218,6 @@ def from_toml_data(data: Mapping[str, object]) -> UtterancePlan:
218
218
  return UtterancePlan.from_dict(_decode_toml_data(data))
219
219
 
220
220
 
221
- def _from_toml_data_draft(data: Mapping[str, object]) -> UtterancePlan:
222
- """Decode a candidate while enforcing topology but not renderability or identity."""
223
- from ..model import _check_shape, _from_current_dict, validate_plan_structure
224
-
225
- semantic = _decode_toml_data(data)
226
- try:
227
- _check_shape(semantic)
228
- plan = _from_current_dict(semantic)
229
- except (KeyError, TypeError, ValueError, IndexError) as exc:
230
- raise PlanFormatError(f"invalid draft candidate: {exc}", code="plan.value") from exc
231
- validate_plan_structure(plan)
232
- return plan
233
-
234
-
235
221
  def dumps_toml(plan: UtterancePlan) -> str:
236
222
  """Serialize a plan to deterministic, human-readable TOML text."""
237
223
  wire = to_toml_data(plan)