utterplan 0.4.0__tar.gz → 0.4.1__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.
- {utterplan-0.4.0 → utterplan-0.4.1}/.github/workflows/tests.yml +2 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/PKG-INFO +6 -1
- {utterplan-0.4.0 → utterplan-0.4.1}/README.md +5 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/changelog.md +20 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/cli.md +19 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/python-api.md +41 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_cli.py +59 -1
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_renderability_repair.py +107 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_ssmd_09_parser.py +81 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/__init__.py +6 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/_version.py +3 -3
- utterplan-0.4.1/utterplan/attempt_serialization.py +245 -0
- utterplan-0.4.1/utterplan/attempts.py +103 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/cli.py +105 -1
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/compiler.py +32 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/model.py +11 -6
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/parsers.py +179 -60
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/planner.py +102 -45
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/toml_codec.py +22 -3
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan.egg-info/PKG-INFO +6 -1
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan.egg-info/SOURCES.txt +2 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan.egg-info/scm_file_list.json +14 -0
- utterplan-0.4.1/utterplan.egg-info/scm_version.json +8 -0
- utterplan-0.4.0/utterplan.egg-info/scm_version.json +0 -8
- {utterplan-0.4.0 → utterplan-0.4.1}/.codecrate.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/.github/workflows/codecov.yml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/.github/workflows/pre-commit.yml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/.github/workflows/python-publish.yml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/.gitignore +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/.pre-commit-config.yaml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/AGENTS.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/LICENSE +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/MANIFEST.in +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/NOTICE +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/Makefile +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/architecture.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/conf.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/consumer-guide.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/coordinate-spaces.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/debugging.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/format.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/getting-started.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/index.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/make.bat +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/phase-a-completion.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/pykokoro-integration.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/docs/requirements.txt +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/examples/basic.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/examples/chapter.ssmd +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/examples/compile_file.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/examples/inspect_plan.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/examples/multilingual.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/examples/ssmd_example.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/pyproject.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/setup.cfg +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/spec/schemas/v1.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/spec/schemas/v2.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/spec/schemas/v3.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/spec/schemas/v4.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/spec/utterplan.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/__init__.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/fixtures/canonical_consumer_contract.expected.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/fixtures/canonical_consumer_contract.ssmd +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/fixtures/ssmd_09_comprehensive.ssmd +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/basic_en.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/directives.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/markers.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/multilingual.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/parenthetical.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/spokenform_offsets.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/ssmd_09_comprehensive.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/golden/ssmd_breaks.utterplan.toml +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/__init__.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/cases.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/basic_en.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/directives.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/markers.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/multilingual.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/parenthetical.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/spokenform_offsets.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/ssmd_09_comprehensive.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/fixtures/v4/ssmd_breaks.utterplan.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/goldens/multilingual_preparation.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/goldens/spokenform_numbers.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/goldens/ssmd_break.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/goldens/ssmd_language_detection.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/goldens/ssmd_marker.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/goldens/ssmd_pronunciation.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/test_legacy_json_import.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/migration/test_semantic_migration_regressions.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/README.md +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v1/basic.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v1/directives.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v1/markers.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v1/multilingual.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v1/parenthetical.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v1/spokenform_offsets.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v1/ssmd_breaks.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v2/directives.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v3/multilingual.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/schema_history/v3/ssmd_09_comprehensive.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_atomic_io.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_batch.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_boundaries.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_cli_batch.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_compiler.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_consumer_contract.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_consumer_readiness.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_coordinates.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_directives.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_explain.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_fake_renderer.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_format.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_golden.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_identity_units.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_import_boundaries.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_language.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_linguistics.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_migration_framework.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_migration_v3_to_v4.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_pauses.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_planner.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_preparation.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_progress.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_public_identity.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_renderability.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_schema_history.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_schema_registry.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_schema_strict.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_segment_topology.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_semantic_boundaries.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_toml_format.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/tests/test_version.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/__main__.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/atomic_io.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/batch.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/config.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/diagnostics.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/directives.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/exceptions.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/explain.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/format.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/hashing.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/language.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/linguistics.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/migration.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/migrations/__init__.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/migrations/registry.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/migrations/v1_to_v2.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/migrations/v2_to_v3.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/migrations/v3_to_v4.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/pauses.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/preparation.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/progress.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/py.typed +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/renderability.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/schema_registry.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/schemas/v1.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/schemas/v2.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/schemas/v3.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/schemas/v4.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/serialization.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/spacy_models.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/spans.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/__init__.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/document/__init__.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/document/plain.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/document/ssmd.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/preparation/__init__.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/preparation/identity.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/preparation/spokenform.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/stages/protocols.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/units.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/utterplan.schema.json +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan/versioning.py +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan.egg-info/dependency_links.txt +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan.egg-info/entry_points.txt +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan.egg-info/requires.txt +0 -0
- {utterplan-0.4.0 → utterplan-0.4.1}/utterplan.egg-info/top_level.txt +0 -0
|
@@ -53,6 +53,8 @@ jobs:
|
|
|
53
53
|
"typing_extensions==4.0.0" \
|
|
54
54
|
"tomlkit==0.13.0"
|
|
55
55
|
python -m pip install --no-deps -e .
|
|
56
|
+
- name: Verify SSMD 0.9.3 front-matter scene-break handling
|
|
57
|
+
run: python -m pytest -q tests/test_ssmd_09_parser.py -k front_matter_scene_break
|
|
56
58
|
- name: Run lower-bound tests
|
|
57
59
|
run: python -m pytest -q
|
|
58
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: utterplan
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -293,9 +293,14 @@ utterplan validate chapter.utterplan.toml
|
|
|
293
293
|
utterplan inspect chapter.utterplan.toml --segment 0
|
|
294
294
|
utterplan inspect chapter.utterplan.toml --semantic-boundaries
|
|
295
295
|
utterplan explain chapter.utterplan.toml
|
|
296
|
+
utterplan inspect-attempt chapter.attempt.toml --issues
|
|
296
297
|
```
|
|
297
298
|
|
|
298
299
|
`explain` presents the compiled plan as a human-readable speech plan, while `inspect` exposes lower-level diagnostic fields.
|
|
300
|
+
|
|
301
|
+
`inspect-attempt` reads the separate `utterplan.planning-attempt.v1` TOML artifact,
|
|
302
|
+
not a canonical plan. Use it to review renderability issues and safe repair
|
|
303
|
+
assessments on blocked planning outcomes; such candidates remain inspect-only drafts.
|
|
299
304
|
Canonical plan output is TOML (`.utterplan.toml`) and is written to stdout when no output file is supplied. Status messages use stderr, and existing output files require `--force`. Safe punctuation repair is the default; use `--renderability strict` to reject every repair opportunity.
|
|
300
305
|
|
|
301
306
|
## SSMD source contract
|
|
@@ -50,9 +50,14 @@ utterplan validate chapter.utterplan.toml
|
|
|
50
50
|
utterplan inspect chapter.utterplan.toml --segment 0
|
|
51
51
|
utterplan inspect chapter.utterplan.toml --semantic-boundaries
|
|
52
52
|
utterplan explain chapter.utterplan.toml
|
|
53
|
+
utterplan inspect-attempt chapter.attempt.toml --issues
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
`explain` presents the compiled plan as a human-readable speech plan, while `inspect` exposes lower-level diagnostic fields.
|
|
57
|
+
|
|
58
|
+
`inspect-attempt` reads the separate `utterplan.planning-attempt.v1` TOML artifact,
|
|
59
|
+
not a canonical plan. Use it to review renderability issues and safe repair
|
|
60
|
+
assessments on blocked planning outcomes; such candidates remain inspect-only drafts.
|
|
56
61
|
Canonical plan output is TOML (`.utterplan.toml`) and is written to stdout when no output file is supplied. Status messages use stderr, and existing output files require `--force`. Safe punctuation repair is the default; use `--renderability strict` to reject every repair opportunity.
|
|
57
62
|
|
|
58
63
|
## SSMD source contract
|
|
@@ -4,6 +4,26 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.4.1] - 2026-10-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added persistable planning attempts with inspectable renderability outcomes and safe repair assessments
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Changed planning APIs to expose blocked candidates without weakening canonical plan validation
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed legacy SSMD scene breaks to preserve front-matter boundaries and clean-to-source offsets
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- Documented planning-attempt artifacts, inspection workflows, and renderability guarantees
|
|
24
|
+
|
|
25
|
+
<!-- generated by releaseledger -->
|
|
26
|
+
|
|
7
27
|
## [0.4.0] - 2026-10-06
|
|
8
28
|
|
|
9
29
|
### Added
|
|
@@ -100,6 +100,24 @@ utterplan explain chapter.utterplan.toml --details
|
|
|
100
100
|
|
|
101
101
|
The default output shows prepared wording, render units, ordered segments, languages, resolved pauses, headings, SSMD version/title/document language, effective typed directives, metadata, and warning/error codes with source locations. Add `--details` for IDs, offsets, provenance, hashes, plan identity, and token analysis beneath each segment. Use `inspect --tokens` for a compact token/provenance view.
|
|
102
102
|
|
|
103
|
+
## Inspect a persisted planning attempt
|
|
104
|
+
|
|
105
|
+
`inspect` reads canonical plans. Persistable planning outcomes use the separate
|
|
106
|
+
`utterplan.planning-attempt.v1` artifact and are opened with `inspect-attempt`:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
utterplan inspect-attempt chapter.attempt.toml
|
|
110
|
+
utterplan inspect-attempt chapter.attempt.toml --issues
|
|
111
|
+
utterplan inspect-attempt chapter.attempt.toml --segment seg-000001 --unit unit-000001
|
|
112
|
+
utterplan inspect-attempt chapter.attempt.toml --json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The default view summarizes status, renderability, and candidate size. `--issues`
|
|
116
|
+
shows source context and conservative repair assessments; segment and unit selectors
|
|
117
|
+
accept an ID or zero-based index. `--json` emits the complete artifact as JSON for
|
|
118
|
+
inspection, but the persisted attempt itself remains TOML. A blocked candidate is an
|
|
119
|
+
inspect-only draft, not a canonical plan, and is rejected by `UtterancePlan.load()`.
|
|
120
|
+
|
|
103
121
|
## Planning controls
|
|
104
122
|
|
|
105
123
|
- `--unit paragraph|sentence` chooses render-unit grouping.
|
|
@@ -126,6 +144,7 @@ utterplan inspect chapter.utterplan.toml --segment 0
|
|
|
126
144
|
utterplan inspect chapter.utterplan.toml --unit 0 --boundaries --tokens
|
|
127
145
|
utterplan inspect chapter.utterplan.toml --preparation
|
|
128
146
|
utterplan inspect chapter.utterplan.toml --semantic-boundaries
|
|
147
|
+
utterplan inspect-attempt chapter.attempt.toml --issues
|
|
129
148
|
```
|
|
130
149
|
|
|
131
150
|
`inspect --preparation` reports the preparation backend and version, structural and spoken text lengths, replacement count, and each replacement's structural and spoken ranges and text. This is the supported human-facing preparation diagnostic; raw coordinate lookup tables are intentionally absent from the persisted TOML plan.
|
|
@@ -41,6 +41,47 @@ the plan, is not renderer input, and does not change `plan.plan_id`. Its source
|
|
|
41
41
|
ranges index SSMD-clean structural text; transformation output ranges index prepared
|
|
42
42
|
spoken text, using Python string character offsets.
|
|
43
43
|
|
|
44
|
+
## Persistable planning attempts
|
|
45
|
+
|
|
46
|
+
`compile_attempt` and `UtterancePlanner.compile_attempt` expose a finalized planning
|
|
47
|
+
outcome without raising solely because a segment is not renderable. They still raise
|
|
48
|
+
for input, configuration, and planning failures. The existing `compile_document`,
|
|
49
|
+
`UtterancePlanner.compile`, and `plan` APIs remain strict and continue raising
|
|
50
|
+
`PlanRenderabilityError` when renderability blocks completion.
|
|
51
|
+
|
|
52
|
+
Attempts are separate from canonical plans. A blocked attempt retains an inspect-only
|
|
53
|
+
candidate draft; it is not a valid `UtterancePlan` and cannot be loaded by
|
|
54
|
+
`UtterancePlan.load()`. Persist it with `PlanningAttempt.save()` and restore it with
|
|
55
|
+
`PlanningAttempt.load()`; the attempt TOML schema is
|
|
56
|
+
`utterplan.planning-attempt.v1`, independent of canonical plan schema v4.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from utterplan import PlannerConfig, compile_attempt
|
|
60
|
+
|
|
61
|
+
attempt = compile_attempt(
|
|
62
|
+
"Hello.\n\n.\n\nWorld.",
|
|
63
|
+
input_format="plain",
|
|
64
|
+
config=PlannerConfig(
|
|
65
|
+
language="en-us",
|
|
66
|
+
renderability_mode="strict",
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
assert attempt.status == "blocked"
|
|
70
|
+
attempt.save("chapter.attempt.toml")
|
|
71
|
+
|
|
72
|
+
for issue in attempt.renderability.issues:
|
|
73
|
+
assessment = issue.repair_assessment
|
|
74
|
+
if assessment is not None:
|
|
75
|
+
print(issue.segment_id, assessment.safe, assessment.action)
|
|
76
|
+
print(assessment.blockers)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Repair assessments report the existing conservative options; they do not rewrite
|
|
80
|
+
text speculatively. In repair mode, only the planner's established safe repairs are
|
|
81
|
+
applied. `utterplan inspect-attempt chapter.attempt.toml --issues` shows issues,
|
|
82
|
+
source locations, and repair assessments; `--segment` and `--unit` select candidate
|
|
83
|
+
topology, and `--json` emits the complete attempt as JSON for inspection.
|
|
84
|
+
|
|
44
85
|
## TOML persistence
|
|
45
86
|
|
|
46
87
|
Schema v4 remains the semantic contract, while `.utterplan.toml` is the canonical persisted format. `to_toml()` and `from_toml()` round-trip the complete plan; `save()` writes atomically, and `load()` accepts TOML only. Normal loading does not auto-detect or fall back to JSON.
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from utterplan import PlannerConfig, UtterancePlan, UtterancePlanner
|
|
9
|
+
from utterplan import PlannerConfig, UtterancePlan, UtterancePlanner, compile_attempt
|
|
10
10
|
from utterplan.cli import build_parser, main
|
|
11
11
|
|
|
12
12
|
|
|
@@ -466,3 +466,61 @@ def test_validate_source_documents_and_preserve_saved_plan_validation(
|
|
|
466
466
|
assert "plain input requires --language" in capsys.readouterr().err
|
|
467
467
|
assert main(["validate", str(plain), "--input-format", "plain", "--language", "en-us"]) == 0
|
|
468
468
|
assert "valid" in capsys.readouterr().out
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _save_blocked_attempt(tmp_path: Path):
|
|
472
|
+
config = PlannerConfig(
|
|
473
|
+
language="en-us",
|
|
474
|
+
document_format="plain",
|
|
475
|
+
text_preparation="identity",
|
|
476
|
+
renderability_mode="strict",
|
|
477
|
+
)
|
|
478
|
+
attempt = compile_attempt("Hello.\n\n.\n\nWorld.", input_format="plain", config=config)
|
|
479
|
+
path = tmp_path / "blocked.attempt.toml"
|
|
480
|
+
attempt.save(path)
|
|
481
|
+
return attempt, path
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def test_inspect_attempt_reports_issues_segment_and_unit(
|
|
485
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
486
|
+
) -> None:
|
|
487
|
+
attempt, path = _save_blocked_attempt(tmp_path)
|
|
488
|
+
issue_segment = attempt.renderability.issues[0].segment_id
|
|
489
|
+
unit = attempt.candidate.units[0]
|
|
490
|
+
|
|
491
|
+
assert (
|
|
492
|
+
main(
|
|
493
|
+
[
|
|
494
|
+
"inspect-attempt",
|
|
495
|
+
str(path),
|
|
496
|
+
"--issues",
|
|
497
|
+
"--segment",
|
|
498
|
+
issue_segment,
|
|
499
|
+
"--unit",
|
|
500
|
+
unit.id,
|
|
501
|
+
]
|
|
502
|
+
)
|
|
503
|
+
== 0
|
|
504
|
+
)
|
|
505
|
+
output = capsys.readouterr().out
|
|
506
|
+
assert "status: blocked" in output
|
|
507
|
+
assert "renderability: blocked (strict)" in output
|
|
508
|
+
assert "renderability.punctuation_only" in output
|
|
509
|
+
assert "safe: true" in output
|
|
510
|
+
assert f"Segment {issue_segment}" in output
|
|
511
|
+
assert f"Unit {unit.id}" in output
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def test_inspect_attempt_json_exposes_separate_artifact_schema(
|
|
515
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
516
|
+
) -> None:
|
|
517
|
+
attempt, path = _save_blocked_attempt(tmp_path)
|
|
518
|
+
|
|
519
|
+
assert main(["inspect-attempt", str(path), "--json"]) == 0
|
|
520
|
+
output = capsys.readouterr().out
|
|
521
|
+
payload = json.loads(output)
|
|
522
|
+
assert payload["schema"] == "utterplan.planning-attempt.v1"
|
|
523
|
+
assert payload["attempt"]["status"] == "blocked"
|
|
524
|
+
assert payload["candidate"]["segments"]
|
|
525
|
+
assert payload["renderability"]["issues"]
|
|
526
|
+
assert payload["attempt"]["attempt_id"] == attempt.attempt_id
|
|
@@ -8,14 +8,17 @@ from utterplan import (
|
|
|
8
8
|
AnnotationSpan,
|
|
9
9
|
BoundaryEvent,
|
|
10
10
|
EmphasisDirective,
|
|
11
|
+
PlanFormatError,
|
|
11
12
|
PlannerConfig,
|
|
12
13
|
PlanRenderabilityError,
|
|
13
14
|
PlanSegment,
|
|
14
15
|
SegmentDirectives,
|
|
15
16
|
SemanticBoundary,
|
|
17
|
+
UtterancePlan,
|
|
16
18
|
UtterancePlanner,
|
|
17
19
|
assess_renderability_repair,
|
|
18
20
|
classify_segment,
|
|
21
|
+
compile_attempt,
|
|
19
22
|
)
|
|
20
23
|
from utterplan.config import PauseConfig
|
|
21
24
|
from utterplan.planner import _repair_renderer_segments
|
|
@@ -327,3 +330,107 @@ def test_symbol_only_content_fails_without_a_guessed_repair() -> None:
|
|
|
327
330
|
assert issue.repair_assessment.safe is False
|
|
328
331
|
assert "will not guess" in str(error.value)
|
|
329
332
|
assert error.value.mode == mode
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def test_strict_attempt_retains_blocked_candidate_and_compile_still_raises() -> None:
|
|
336
|
+
source = "Hello.\n\n.\n\nWorld."
|
|
337
|
+
config = PlannerConfig(
|
|
338
|
+
language="en-US",
|
|
339
|
+
text_preparation="identity",
|
|
340
|
+
renderability_mode="strict",
|
|
341
|
+
)
|
|
342
|
+
planner = UtterancePlanner(config)
|
|
343
|
+
|
|
344
|
+
attempt = planner.compile_attempt(source)
|
|
345
|
+
functional = compile_attempt(source, input_format="plain", config=config)
|
|
346
|
+
assert functional.status == "blocked"
|
|
347
|
+
|
|
348
|
+
assert attempt.status == "blocked"
|
|
349
|
+
assert not attempt.ok
|
|
350
|
+
assert attempt.renderability.issues
|
|
351
|
+
issue = attempt.renderability.issues[0]
|
|
352
|
+
segment = next(item for item in attempt.candidate.segments if item.id == issue.segment_id)
|
|
353
|
+
assert segment.text == issue.text == "."
|
|
354
|
+
assert issue.source_start is not None
|
|
355
|
+
assert issue.source_end is not None
|
|
356
|
+
assert source[issue.source_start : issue.source_end] == issue.text
|
|
357
|
+
assert attempt.candidate.plan_id == ""
|
|
358
|
+
with pytest.raises(ValueError, match="blocked planning-attempt draft"):
|
|
359
|
+
attempt.candidate.to_plan()
|
|
360
|
+
|
|
361
|
+
with pytest.raises(PlanRenderabilityError) as error:
|
|
362
|
+
planner.compile(source)
|
|
363
|
+
assert error.value.mode == "strict"
|
|
364
|
+
assert error.value.issues == attempt.renderability.issues
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_safe_repair_attempt_returns_a_canonical_plan() -> None:
|
|
368
|
+
config = PlannerConfig(language="en-US", text_preparation="identity")
|
|
369
|
+
attempt = UtterancePlanner(config).compile_attempt("Hello.\n\n.\n\nWorld.")
|
|
370
|
+
|
|
371
|
+
assert attempt.status == "repaired"
|
|
372
|
+
assert attempt.ok
|
|
373
|
+
assert len(attempt.repairs) == 1
|
|
374
|
+
assert attempt.repairs[0].repair_assessment is not None
|
|
375
|
+
assert attempt.repairs[0].repair_assessment.safe
|
|
376
|
+
plan = attempt.candidate.to_plan()
|
|
377
|
+
plan.validate()
|
|
378
|
+
assert [segment.text for segment in plan.segments] == ["Hello.", "World."]
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_unsafe_repair_attempt_is_blocked_and_draft_structure_is_checked() -> None:
|
|
382
|
+
from utterplan.exceptions import PlanValidationError
|
|
383
|
+
from utterplan.model import validate_plan_structure
|
|
384
|
+
|
|
385
|
+
config = PlannerConfig(
|
|
386
|
+
language="en-US",
|
|
387
|
+
text_preparation="identity",
|
|
388
|
+
renderability_mode="repair",
|
|
389
|
+
)
|
|
390
|
+
attempt = UtterancePlanner(config).compile_attempt("€")
|
|
391
|
+
|
|
392
|
+
assert attempt.status == "blocked"
|
|
393
|
+
assert attempt.renderability.issues[0].repair_assessment is not None
|
|
394
|
+
assert not attempt.renderability.issues[0].repair_assessment.safe
|
|
395
|
+
with pytest.raises(ValueError, match="blocked planning-attempt draft"):
|
|
396
|
+
attempt.candidate.to_plan()
|
|
397
|
+
|
|
398
|
+
malformed = replace(
|
|
399
|
+
attempt.candidate._plan,
|
|
400
|
+
segments=(replace(attempt.candidate.segments[0], spoken_end=100),),
|
|
401
|
+
)
|
|
402
|
+
with pytest.raises(PlanValidationError, match="segment range"):
|
|
403
|
+
validate_plan_structure(malformed)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@pytest.mark.parametrize(
|
|
407
|
+
("source", "mode", "expected_status"),
|
|
408
|
+
[
|
|
409
|
+
("Ready.", "strict", "renderable"),
|
|
410
|
+
("Hello.\n\n.\n\nWorld.", "repair", "repaired"),
|
|
411
|
+
("€", "strict", "blocked"),
|
|
412
|
+
],
|
|
413
|
+
)
|
|
414
|
+
def test_planning_attempt_toml_roundtrips_all_outcomes_and_stays_noncanonical(
|
|
415
|
+
source: str, mode: str, expected_status: str
|
|
416
|
+
) -> None:
|
|
417
|
+
config = PlannerConfig(
|
|
418
|
+
language="en-US",
|
|
419
|
+
text_preparation="identity",
|
|
420
|
+
renderability_mode=mode,
|
|
421
|
+
)
|
|
422
|
+
attempt = UtterancePlanner(config).compile_attempt(source)
|
|
423
|
+
repeated = UtterancePlanner(config).compile_attempt(source)
|
|
424
|
+
serialized = attempt.to_toml()
|
|
425
|
+
restored = type(attempt).from_toml(serialized)
|
|
426
|
+
|
|
427
|
+
assert attempt.status == expected_status
|
|
428
|
+
assert repeated.attempt_id == attempt.attempt_id
|
|
429
|
+
assert restored == attempt
|
|
430
|
+
assert restored.to_toml() == serialized
|
|
431
|
+
assert restored.to_dict() == attempt.to_dict()
|
|
432
|
+
assert 'schema = "utterplan.planning-attempt.v1"' in serialized
|
|
433
|
+
with pytest.raises(PlanFormatError):
|
|
434
|
+
UtterancePlan.from_toml(serialized)
|
|
435
|
+
with pytest.raises(PlanFormatError):
|
|
436
|
+
UtterancePlan.from_dict(attempt.to_dict())
|
|
@@ -39,6 +39,87 @@ def test_legacy_scene_break_is_structural_not_renderer_text() -> None:
|
|
|
39
39
|
assert all(segment.text != "---" for segment in plan.segments)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
@pytest.mark.parametrize(
|
|
43
|
+
("body", "scene_break_count"),
|
|
44
|
+
[
|
|
45
|
+
("Before.\n\n---\n\nAfter.", 1),
|
|
46
|
+
("Before.\n\n---\n\nMiddle.\n\n---\n\nAfter.", 2),
|
|
47
|
+
("Before.\n\n...p\n\nAfter.", 1),
|
|
48
|
+
("---\n\nAfter.\n\n---", 2),
|
|
49
|
+
("Before --- after.", 0),
|
|
50
|
+
("Before.\n\n\\---\n\nAfter.", 0),
|
|
51
|
+
],
|
|
52
|
+
)
|
|
53
|
+
def test_front_matter_scene_breaks_are_boundaries_not_renderer_text(
|
|
54
|
+
body: str, scene_break_count: int
|
|
55
|
+
) -> None:
|
|
56
|
+
source = f'---\nssmd_version: "0.9"\ntitle: Demo\n---\n{body}'
|
|
57
|
+
plan = _planner().plan(source)
|
|
58
|
+
|
|
59
|
+
scene_breaks = [
|
|
60
|
+
event
|
|
61
|
+
for event in plan.boundaries
|
|
62
|
+
if event.kind == "explicit"
|
|
63
|
+
and (
|
|
64
|
+
event.attrs.get("semantic") == "scene_break"
|
|
65
|
+
or event.attrs.get("legacy_horizontal_rule")
|
|
66
|
+
or (event.origin == "ssmd" and event.strength == "x-strong")
|
|
67
|
+
)
|
|
68
|
+
]
|
|
69
|
+
assert len(scene_breaks) == scene_break_count
|
|
70
|
+
if scene_break_count:
|
|
71
|
+
assert "---" not in plan.texts.structural or body.startswith("---")
|
|
72
|
+
assert all(segment.text.strip() != "---" for segment in plan.segments)
|
|
73
|
+
assert plan.document_metadata["planning"]["renderability"]["guaranteed"] is True
|
|
74
|
+
elif "Before --- after." in body:
|
|
75
|
+
assert "Before --- after." in plan.texts.structural
|
|
76
|
+
assert any("---" in segment.text for segment in plan.segments)
|
|
77
|
+
elif "\\---" in body:
|
|
78
|
+
assert "---" in plan.texts.structural
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_legacy_parser_front_matter_scene_break_uses_clean_to_source_mapping(
|
|
82
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
83
|
+
) -> None:
|
|
84
|
+
import ssmd
|
|
85
|
+
|
|
86
|
+
from utterplan.parsers import SSMDDocumentParser, map_structural_span_to_source
|
|
87
|
+
|
|
88
|
+
header = '---\nssmd_version: "0.9"\ntitle: Demo\n---\n'
|
|
89
|
+
body = "Before.\n\n---\n\nAfter."
|
|
90
|
+
source = header + body
|
|
91
|
+
legacy_result = SimpleNamespace(
|
|
92
|
+
clean_text=body,
|
|
93
|
+
annotations=[],
|
|
94
|
+
events=[],
|
|
95
|
+
header={"ssmd_version": "0.9", "title": "Demo"},
|
|
96
|
+
warnings=[],
|
|
97
|
+
diagnostics=[],
|
|
98
|
+
text_spans=[
|
|
99
|
+
SimpleNamespace(
|
|
100
|
+
char_start=0,
|
|
101
|
+
char_end=len(body),
|
|
102
|
+
source_start=len(header),
|
|
103
|
+
source_end=len(source),
|
|
104
|
+
)
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
monkeypatch.setattr(ssmd, "parse_structure", lambda *args, **kwargs: legacy_result)
|
|
108
|
+
|
|
109
|
+
parsed = SSMDDocumentParser().parse(source, _planner().config)
|
|
110
|
+
assert parsed.structural_text == "Before.\n\nAfter."
|
|
111
|
+
source_after = source.index("After.")
|
|
112
|
+
assert map_structural_span_to_source(
|
|
113
|
+
parsed, len("Before.\n\n"), len(parsed.structural_text)
|
|
114
|
+
) == (source_after, source_after + len("After."))
|
|
115
|
+
scene = next(event for event in parsed.boundaries if event.attrs.get("legacy_horizontal_rule"))
|
|
116
|
+
assert scene.attrs["source_start"] == source.index("---", len(header))
|
|
117
|
+
assert scene.attrs["source_end"] == source_after
|
|
118
|
+
|
|
119
|
+
plan = _planner().plan(source)
|
|
120
|
+
assert [segment.text for segment in plan.segments] == ["Before.", "After."]
|
|
121
|
+
|
|
122
|
+
|
|
42
123
|
def _planner() -> UtterancePlanner:
|
|
43
124
|
return UtterancePlanner(
|
|
44
125
|
PlannerConfig(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from ._version import __version__
|
|
2
|
+
from .attempts import PlanDraft, PlanningAttempt, PlanningAttemptStatus
|
|
2
3
|
from .batch import BatchProgressCallback, CompileOutcome, CompileRequest, compile_to_files
|
|
3
4
|
from .compiler import (
|
|
4
5
|
CompileResult,
|
|
@@ -6,6 +7,7 @@ from .compiler import (
|
|
|
6
7
|
PreparationChange,
|
|
7
8
|
PreparationTrace,
|
|
8
9
|
PreparationTraceUnit,
|
|
10
|
+
compile_attempt,
|
|
9
11
|
compile_document,
|
|
10
12
|
)
|
|
11
13
|
from .config import (
|
|
@@ -89,6 +91,10 @@ __all__ = [
|
|
|
89
91
|
"PreparationTrace",
|
|
90
92
|
"PreparationTraceUnit",
|
|
91
93
|
"compile_document",
|
|
94
|
+
"compile_attempt",
|
|
95
|
+
"PlanningAttempt",
|
|
96
|
+
"PlanningAttemptStatus",
|
|
97
|
+
"PlanDraft",
|
|
92
98
|
"compile_to_files",
|
|
93
99
|
"PlannerConfig",
|
|
94
100
|
"PauseConfig",
|
|
@@ -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.4.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
21
|
+
__version__ = version = '0.4.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g293fe7cd5'
|