bookwright-cli 0.2.0__tar.gz → 0.3.2__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 (167) hide show
  1. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/PKG-INFO +51 -12
  2. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/README.md +50 -11
  3. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/__init__.py +1 -1
  4. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/cli.py +3 -1
  5. bookwright_cli-0.3.2/src/bookwright/commands/_envelope.py +116 -0
  6. bookwright_cli-0.3.2/src/bookwright/commands/_graph.py +135 -0
  7. bookwright_cli-0.3.2/src/bookwright/commands/_project.py +44 -0
  8. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/check.py +3 -2
  9. bookwright_cli-0.3.2/src/bookwright/commands/focus/__init__.py +24 -0
  10. bookwright_cli-0.3.2/src/bookwright/commands/focus/clear.py +40 -0
  11. bookwright_cli-0.3.2/src/bookwright/commands/focus/errors.py +27 -0
  12. bookwright_cli-0.3.2/src/bookwright/commands/focus/set_.py +71 -0
  13. bookwright_cli-0.3.2/src/bookwright/commands/focus/show.py +45 -0
  14. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/graph/build.py +18 -78
  15. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/graph/query.py +1 -3
  16. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/envelope.py +9 -3
  17. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/integration/use.py +6 -21
  18. bookwright_cli-0.3.2/src/bookwright/commands/status.py +251 -0
  19. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/validate.py +8 -23
  20. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/version.py +3 -4
  21. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/__init__.py +2 -0
  22. bookwright_cli-0.3.2/src/bookwright/core/_focus_block.py +94 -0
  23. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/manifest.py +66 -13
  24. bookwright_cli-0.3.2/src/bookwright/golem/deferrals.py +63 -0
  25. bookwright_cli-0.3.2/src/bookwright/integrations/constants.py +86 -0
  26. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/materialize.py +25 -7
  27. bookwright_cli-0.3.2/src/bookwright/io/_bible_builders.py +268 -0
  28. bookwright_cli-0.3.2/src/bookwright/io/_research_identity.py +72 -0
  29. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/bible.py +81 -197
  30. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/manuscript.py +6 -0
  31. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/research.py +38 -3
  32. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-bible.md +9 -4
  33. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-outline.md +4 -0
  34. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-research.md +36 -3
  35. bookwright_cli-0.3.2/src/bookwright/status/__init__.py +41 -0
  36. bookwright_cli-0.3.2/src/bookwright/status/model.py +157 -0
  37. bookwright_cli-0.3.2/src/bookwright/status/queries.py +207 -0
  38. bookwright_cli-0.3.2/src/bookwright/status/rules.py +176 -0
  39. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/anchor_queries.py +5 -2
  40. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/registry.py +6 -1
  41. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/validators/factual_anchor.py +45 -11
  42. bookwright_cli-0.2.0/src/bookwright/commands/_envelope.py +0 -36
  43. bookwright_cli-0.2.0/src/bookwright/commands/graph/envelope.py +0 -26
  44. bookwright_cli-0.2.0/src/bookwright/integrations/constants.py +0 -38
  45. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/.gitignore +0 -0
  46. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/LICENSE +0 -0
  47. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/NOTICE +0 -0
  48. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/pyproject.toml +0 -0
  49. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/__main__.py +0 -0
  50. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/__init__.py +0 -0
  51. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/graph/__init__.py +0 -0
  52. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/__init__.py +0 -0
  53. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/conflict.py +0 -0
  54. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/git.py +0 -0
  55. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/main.py +0 -0
  56. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/resolve.py +0 -0
  57. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/scaffold.py +0 -0
  58. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/init/validate.py +0 -0
  59. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/commands/integration/__init__.py +0 -0
  60. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/_blocks.py +0 -0
  61. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/_build.py +0 -0
  62. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/_research_block.py +0 -0
  63. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/_translate.py +0 -0
  64. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/errors.py +0 -0
  65. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/core/iso639_1.py +0 -0
  66. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/errors.py +0 -0
  67. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/__init__.py +0 -0
  68. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/base.py +0 -0
  69. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/errors.py +0 -0
  70. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/__init__.py +0 -0
  71. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/character.py +0 -0
  72. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/event.py +0 -0
  73. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/feature.py +0 -0
  74. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/inference.py +0 -0
  75. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/narrative.py +0 -0
  76. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/provenance.py +0 -0
  77. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/relationship.py +0 -0
  78. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/modules/setting.py +0 -0
  79. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/namespaces.py +0 -0
  80. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/serialize.py +0 -0
  81. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/golem/slug.py +0 -0
  82. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/indexers/__init__.py +0 -0
  83. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/indexers/base.py +0 -0
  84. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/indexers/errors.py +0 -0
  85. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/indexers/rdflib_indexer.py +0 -0
  86. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/__init__.py +0 -0
  87. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/base.py +0 -0
  88. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/claude/__init__.py +0 -0
  89. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/descriptions.py +0 -0
  90. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/errors.py +0 -0
  91. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/generic/__init__.py +0 -0
  92. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/lint.py +0 -0
  93. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/integrations/options.py +0 -0
  94. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/__init__.py +0 -0
  95. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/errors.py +0 -0
  96. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/frontmatter.py +0 -0
  97. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/fs.py +0 -0
  98. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/project.py +0 -0
  99. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/io/report.py +0 -0
  100. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/__init__.py +0 -0
  101. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-analyze.md +0 -0
  102. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-checklist.md +0 -0
  103. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-clarify.md +0 -0
  104. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-constitution.md +0 -0
  105. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-continuity.md +0 -0
  106. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-draft.md +0 -0
  107. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-scenes.md +0 -0
  108. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-synopsis.md +0 -0
  109. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/bookwright-verify.md +0 -0
  110. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/references/golem-character.md +0 -0
  111. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/references/golem-events-timeline.md +0 -0
  112. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/references/golem-relationships.md +0 -0
  113. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/references/greimas-actants.md +0 -0
  114. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/references/pending-protocol.md +0 -0
  115. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/references/propp-functions.md +0 -0
  116. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/commands/references/research-format.md +0 -0
  117. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/.bookwright/cache/.gitkeep +0 -0
  118. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/.bookwright/schema/.gitkeep +0 -0
  119. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/.bookwright/templates/.gitkeep +0 -0
  120. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/.gitignore +0 -0
  121. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/README.md.j2 +0 -0
  122. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/__init__.py +0 -0
  123. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/characters/.gitkeep +0 -0
  124. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/constitution.md.j2 +0 -0
  125. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/glossary.md +0 -0
  126. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/locations/.gitkeep +0 -0
  127. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/pov-structure.md +0 -0
  128. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/relationships.md +0 -0
  129. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/research/_index.md +0 -0
  130. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/research/sources.md +0 -0
  131. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/settings/.gitkeep +0 -0
  132. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/subplots.md +0 -0
  133. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/themes.md +0 -0
  134. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/bible/timeline.md +0 -0
  135. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/manuscript/.gitkeep +0 -0
  136. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/outline/arcs.md +0 -0
  137. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/outline/scenes.md +0 -0
  138. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/outline/structure.md +0 -0
  139. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/project/outline/synopsis.md +0 -0
  140. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/schemas/__init__.py +0 -0
  141. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/schemas/golem-1.1/VERSION +0 -0
  142. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/schemas/golem-1.1/golem.ttl +0 -0
  143. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/schemas/golem-1.1/version.json +0 -0
  144. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/__init__.py +0 -0
  145. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/bible/character.md.tmpl +0 -0
  146. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/bible/location.md.tmpl +0 -0
  147. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/bible/research/_index.md.tmpl +0 -0
  148. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/bible/research/sources.md.tmpl +0 -0
  149. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/bible/research/tema.md.tmpl +0 -0
  150. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/bible/setting.md.tmpl +0 -0
  151. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/manifest.template.toml +0 -0
  152. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/manuscript/chapter.md.tmpl +0 -0
  153. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/templates/scenes/scene.md.tmpl +0 -0
  154. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/vocabularies/__init__.py +0 -0
  155. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/vocabularies/greimas.ttl +0 -0
  156. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/vocabularies/propp.ttl +0 -0
  157. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/resources/vocabularies/sources.ttl +0 -0
  158. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/__init__.py +0 -0
  159. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/base.py +0 -0
  160. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/queries.py +0 -0
  161. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/report.py +0 -0
  162. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/runner.py +0 -0
  163. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/validators/__init__.py +0 -0
  164. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/validators/character_presence.py +0 -0
  165. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/validators/focalization.py +0 -0
  166. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/validators/setting_continuity.py +0 -0
  167. {bookwright_cli-0.2.0 → bookwright_cli-0.3.2}/src/bookwright/validation/validators/temporal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bookwright-cli
3
- Version: 0.2.0
3
+ Version: 0.3.2
4
4
  Summary: Spec-driven authoring toolkit for novels, essays, and memoirs.
5
5
  Project-URL: Homepage, https://github.com/jmorenobl/bookwright
6
6
  Project-URL: Repository, https://github.com/jmorenobl/bookwright
@@ -40,7 +40,7 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  <p align="center">
42
42
  <a href="https://github.com/jmorenobl/bookwright/actions/workflows/tests.yml"><img src="https://github.com/jmorenobl/bookwright/actions/workflows/tests.yml/badge.svg" alt="CI"></a>
43
- <a href="https://github.com/jmorenobl/bookwright/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/version-0.2.0-6f42c1" alt="Versión 0.2.0"></a>
43
+ <a href="https://github.com/jmorenobl/bookwright/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/version-0.3.0-6f42c1" alt="Versión 0.3.0"></a>
44
44
  <a href="https://github.com/jmorenobl/bookwright/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="Licencia: Apache-2.0"></a>
45
45
  <img src="https://img.shields.io/badge/python-3.11%2B-3776ab?logo=python&logoColor=white" alt="Python 3.11+">
46
46
  <img src="https://img.shields.io/badge/coverage-%E2%89%A580%25-2ea44f" alt="Cobertura ≥80%">
@@ -61,21 +61,32 @@ IA escriba a partir de *ellos*, no de un chat libre. Tu libro vive en
61
61
  texto plano, versionado en git, completamente auditable, y sobrevive al
62
62
  toolkit.
63
63
 
64
- > ### Estado: v0.2.0
64
+ > ### Estado: v0.3.0
65
65
  >
66
- > Dos hitos están en `main`. **v0.1.0** (el toolkit base, iteraciones
66
+ > Tres hitos están en `main`. **v0.1.0** (el toolkit base, iteraciones
67
67
  > 1–11): scaffolding del proyecto (`bookwright init`), el modelo de
68
68
  > dominio GOLEM, el indexer y los comandos `bookwright graph`, las skills
69
69
  > de autoría materializadas como Agent Skills, y el sistema de validación
70
70
  > de continuidad. **v0.2.0 / M4** (investigación y verificación,
71
71
  > iteraciones 12–18): el modelo de procedencia `Source` / `Finding` /
72
72
  > `Anchor`, las skills `/bookwright-research` y `/bookwright-verify`, el
73
- > validador `factual_anchor` y la envoltura `--json` unificada. La
74
- > documentación de usuario completa vive en el
73
+ > validador `factual_anchor` y la envoltura `--json` unificada.
74
+ > **v0.3.0 / M5** (orquestación de contexto, iteraciones 19–23): el foco
75
+ > autoral (`[focus]` + `bookwright focus`), el estado derivado
76
+ > `bookwright status` con sus `next_actions`, y las skills que lo
77
+ > consumen para guiar el siguiente paso. La documentación de usuario
78
+ > completa vive en el
75
79
  > [sitio de documentación](https://github.com/jmorenobl/bookwright/blob/main/docs/index.md).
76
80
 
77
81
  ## El loop del escritor
78
82
 
83
+ <p align="center">
84
+ <picture>
85
+ <source srcset="https://raw.githubusercontent.com/jmorenobl/bookwright/main/assets/loop.svg" type="image/svg+xml">
86
+ <img src="https://raw.githubusercontent.com/jmorenobl/bookwright/main/assets/loop.png" alt="El loop del escritor: idea → scaffolding → destila → build y valida → edita, y vuelta a empezar" width="100%">
87
+ </picture>
88
+ </p>
89
+
79
90
  1. **Idea libremente** — conversa con tu agente o tu libreta y vuelca un
80
91
  brief a Markdown.
81
92
  2. **Scaffolding del proyecto** —
@@ -108,15 +119,33 @@ toolkit.
108
119
 
109
120
  ## Instalación
110
121
 
122
+ El paquete en PyPI es `bookwright-cli`; el comando que instala es `bookwright`.
123
+
124
+ Desde PyPI (recomendado):
125
+
111
126
  ```bash
112
- uv build
113
- pipx install ./dist/bookwright_cli-*.whl # o: uv tool install ./dist/*.whl
127
+ uv tool install bookwright-cli # con uv
128
+ pipx install bookwright-cli # o con pipx
114
129
  bookwright version
115
130
  ```
116
131
 
117
- Para desarrollar sobre el toolkit, sincroniza el entorno del proyecto:
132
+ Directamente desde el repositorio (última versión de `main`):
118
133
 
119
134
  ```bash
135
+ uv tool install "git+https://github.com/jmorenobl/bookwright"
136
+ # o: pipx install "git+https://github.com/jmorenobl/bookwright"
137
+ ```
138
+
139
+ ¿Solo quieres probarlo una vez, sin instalar nada?
140
+
141
+ ```bash
142
+ uvx --from bookwright-cli bookwright version
143
+ ```
144
+
145
+ Para desarrollar sobre el toolkit, clona el repo y sincroniza el entorno:
146
+
147
+ ```bash
148
+ git clone https://github.com/jmorenobl/bookwright && cd bookwright
120
149
  uv sync
121
150
  uv run bookwright --help
122
151
  ```
@@ -156,6 +185,14 @@ bookwright graph query "SELECT ?c WHERE { ?c a golem:G1_Character }" --json
156
185
  bookwright validate # exit 0 si no hay errores
157
186
  ```
158
187
 
188
+ Para mantener el hilo conductor entre sesiones, fija tu foco autoral y deja que
189
+ Bookwright derive el estado y el siguiente paso:
190
+
191
+ ```bash
192
+ bookwright focus set --target "Cerrar la investigación del libro de jornales"
193
+ bookwright status --json # estado derivado + next_actions
194
+ ```
195
+
159
196
  ¿Quieres cambiar de integración (p. ej. de `claude` a `generic`)?
160
197
 
161
198
  ```bash
@@ -184,9 +221,11 @@ El recorrido completo está en
184
221
  ## Roadmap y fuera de scope
185
222
 
186
223
  Hecho: **v0.2 / M4** — investigación y verificación (modelo de procedencia,
187
- skills `research`/`verify`, validador `factual_anchor`). Planificado:
188
- **v0.3** búsqueda vectorial (ChromaDB sobre rdflib, desacoplada); **v1.0**
189
- export a EPUB / PDF / impresión vía pandoc.
224
+ skills `research`/`verify`, validador `factual_anchor`); **v0.3 / M5** —
225
+ orquestación de contexto (foco autoral `[focus]` + `bookwright focus`, estado
226
+ derivado `bookwright status` con `next_actions`, y las skills que lo consumen).
227
+ Planificado: **v0.4** — búsqueda vectorial (ChromaDB sobre rdflib, desacoplada);
228
+ **v1.0** — export a EPUB / PDF / impresión vía pandoc.
190
229
 
191
230
  **Cancelado (decisión del owner), no lo pidas:** presets de género / paquetes
192
231
  de plantilla (la resolución es de 2 capas, overrides → core); el motor
@@ -7,7 +7,7 @@
7
7
 
8
8
  <p align="center">
9
9
  <a href="https://github.com/jmorenobl/bookwright/actions/workflows/tests.yml"><img src="https://github.com/jmorenobl/bookwright/actions/workflows/tests.yml/badge.svg" alt="CI"></a>
10
- <a href="https://github.com/jmorenobl/bookwright/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/version-0.2.0-6f42c1" alt="Versión 0.2.0"></a>
10
+ <a href="https://github.com/jmorenobl/bookwright/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/version-0.3.0-6f42c1" alt="Versión 0.3.0"></a>
11
11
  <a href="https://github.com/jmorenobl/bookwright/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="Licencia: Apache-2.0"></a>
12
12
  <img src="https://img.shields.io/badge/python-3.11%2B-3776ab?logo=python&logoColor=white" alt="Python 3.11+">
13
13
  <img src="https://img.shields.io/badge/coverage-%E2%89%A580%25-2ea44f" alt="Cobertura ≥80%">
@@ -28,21 +28,32 @@ IA escriba a partir de *ellos*, no de un chat libre. Tu libro vive en
28
28
  texto plano, versionado en git, completamente auditable, y sobrevive al
29
29
  toolkit.
30
30
 
31
- > ### Estado: v0.2.0
31
+ > ### Estado: v0.3.0
32
32
  >
33
- > Dos hitos están en `main`. **v0.1.0** (el toolkit base, iteraciones
33
+ > Tres hitos están en `main`. **v0.1.0** (el toolkit base, iteraciones
34
34
  > 1–11): scaffolding del proyecto (`bookwright init`), el modelo de
35
35
  > dominio GOLEM, el indexer y los comandos `bookwright graph`, las skills
36
36
  > de autoría materializadas como Agent Skills, y el sistema de validación
37
37
  > de continuidad. **v0.2.0 / M4** (investigación y verificación,
38
38
  > iteraciones 12–18): el modelo de procedencia `Source` / `Finding` /
39
39
  > `Anchor`, las skills `/bookwright-research` y `/bookwright-verify`, el
40
- > validador `factual_anchor` y la envoltura `--json` unificada. La
41
- > documentación de usuario completa vive en el
40
+ > validador `factual_anchor` y la envoltura `--json` unificada.
41
+ > **v0.3.0 / M5** (orquestación de contexto, iteraciones 19–23): el foco
42
+ > autoral (`[focus]` + `bookwright focus`), el estado derivado
43
+ > `bookwright status` con sus `next_actions`, y las skills que lo
44
+ > consumen para guiar el siguiente paso. La documentación de usuario
45
+ > completa vive en el
42
46
  > [sitio de documentación](https://github.com/jmorenobl/bookwright/blob/main/docs/index.md).
43
47
 
44
48
  ## El loop del escritor
45
49
 
50
+ <p align="center">
51
+ <picture>
52
+ <source srcset="https://raw.githubusercontent.com/jmorenobl/bookwright/main/assets/loop.svg" type="image/svg+xml">
53
+ <img src="https://raw.githubusercontent.com/jmorenobl/bookwright/main/assets/loop.png" alt="El loop del escritor: idea → scaffolding → destila → build y valida → edita, y vuelta a empezar" width="100%">
54
+ </picture>
55
+ </p>
56
+
46
57
  1. **Idea libremente** — conversa con tu agente o tu libreta y vuelca un
47
58
  brief a Markdown.
48
59
  2. **Scaffolding del proyecto** —
@@ -75,15 +86,33 @@ toolkit.
75
86
 
76
87
  ## Instalación
77
88
 
89
+ El paquete en PyPI es `bookwright-cli`; el comando que instala es `bookwright`.
90
+
91
+ Desde PyPI (recomendado):
92
+
78
93
  ```bash
79
- uv build
80
- pipx install ./dist/bookwright_cli-*.whl # o: uv tool install ./dist/*.whl
94
+ uv tool install bookwright-cli # con uv
95
+ pipx install bookwright-cli # o con pipx
81
96
  bookwright version
82
97
  ```
83
98
 
84
- Para desarrollar sobre el toolkit, sincroniza el entorno del proyecto:
99
+ Directamente desde el repositorio (última versión de `main`):
85
100
 
86
101
  ```bash
102
+ uv tool install "git+https://github.com/jmorenobl/bookwright"
103
+ # o: pipx install "git+https://github.com/jmorenobl/bookwright"
104
+ ```
105
+
106
+ ¿Solo quieres probarlo una vez, sin instalar nada?
107
+
108
+ ```bash
109
+ uvx --from bookwright-cli bookwright version
110
+ ```
111
+
112
+ Para desarrollar sobre el toolkit, clona el repo y sincroniza el entorno:
113
+
114
+ ```bash
115
+ git clone https://github.com/jmorenobl/bookwright && cd bookwright
87
116
  uv sync
88
117
  uv run bookwright --help
89
118
  ```
@@ -123,6 +152,14 @@ bookwright graph query "SELECT ?c WHERE { ?c a golem:G1_Character }" --json
123
152
  bookwright validate # exit 0 si no hay errores
124
153
  ```
125
154
 
155
+ Para mantener el hilo conductor entre sesiones, fija tu foco autoral y deja que
156
+ Bookwright derive el estado y el siguiente paso:
157
+
158
+ ```bash
159
+ bookwright focus set --target "Cerrar la investigación del libro de jornales"
160
+ bookwright status --json # estado derivado + next_actions
161
+ ```
162
+
126
163
  ¿Quieres cambiar de integración (p. ej. de `claude` a `generic`)?
127
164
 
128
165
  ```bash
@@ -151,9 +188,11 @@ El recorrido completo está en
151
188
  ## Roadmap y fuera de scope
152
189
 
153
190
  Hecho: **v0.2 / M4** — investigación y verificación (modelo de procedencia,
154
- skills `research`/`verify`, validador `factual_anchor`). Planificado:
155
- **v0.3** búsqueda vectorial (ChromaDB sobre rdflib, desacoplada); **v1.0**
156
- export a EPUB / PDF / impresión vía pandoc.
191
+ skills `research`/`verify`, validador `factual_anchor`); **v0.3 / M5** —
192
+ orquestación de contexto (foco autoral `[focus]` + `bookwright focus`, estado
193
+ derivado `bookwright status` con `next_actions`, y las skills que lo consumen).
194
+ Planificado: **v0.4** — búsqueda vectorial (ChromaDB sobre rdflib, desacoplada);
195
+ **v1.0** — export a EPUB / PDF / impresión vía pandoc.
157
196
 
158
197
  **Cancelado (decisión del owner), no lo pidas:** presets de género / paquetes
159
198
  de plantilla (la resolución es de 2 capas, overrides → core); el motor
@@ -1,3 +1,3 @@
1
1
  """Bookwright — Spec-driven authoring toolkit."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.2"
@@ -2,7 +2,7 @@
2
2
 
3
3
  import typer
4
4
 
5
- from bookwright.commands import check, graph, init, integration, validate, version
5
+ from bookwright.commands import check, focus, graph, init, integration, status, validate, version
6
6
 
7
7
  app = typer.Typer(
8
8
  name="bookwright",
@@ -14,6 +14,8 @@ app = typer.Typer(
14
14
  app.command("version")(version.run)
15
15
  app.command("check")(check.run)
16
16
  app.command("init", context_settings=init.CONTEXT_SETTINGS)(init.run)
17
+ app.command("status")(status.run)
17
18
  app.command("validate")(validate.run)
18
19
  app.add_typer(graph.app, name="graph")
19
20
  app.add_typer(integration.app, name="integration")
21
+ app.add_typer(focus.app, name="focus")
@@ -0,0 +1,116 @@
1
+ """Shared command-layer envelope helpers.
2
+
3
+ Several agent-facing commands catch a ``ManifestError`` at their ``--json``
4
+ boundary and remap it to the contract's single ``invalid_manifest`` code. Rather
5
+ than re-build the ``{status,code,message}`` skeleton by hand in each command
6
+ module, the remap routes through the base ``BookwrightError.to_json`` — the one
7
+ place the envelope skeleton lives — exactly as ``commands.validate._UsageError``
8
+ already does for the same case.
9
+
10
+ The :func:`emit_json` / :func:`emit_error` pair is single-sourced here too: a
11
+ single-line ``json.dumps(payload, separators=(",", ":")) + "\\n"`` to stdout,
12
+ with human prose / progress going to stderr via a ``Console(stderr=True)`` owned
13
+ by each command. Every ``--json`` command (``check``, ``focus``, ``graph``,
14
+ ``init``, ``integration``, ``validate``, ``version``) routes its stdout encoding
15
+ through :func:`emit_json` instead of hand-rolling a per-group copy.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import sys
22
+ from typing import TYPE_CHECKING, Any
23
+
24
+ from bookwright.errors import BookwrightError
25
+
26
+ if TYPE_CHECKING:
27
+ from bookwright.io.errors import ProjectNotFoundError
28
+
29
+ #: The CLI-wide "configuration fault" exit status (missing project, unparseable
30
+ #: manifest, unknown engine/integration, bad scope, …). Single-sourced here so
31
+ #: the command groups cannot drift to different statuses for the same fault class.
32
+ EXIT_CONFIG = 2
33
+
34
+ #: The CLI-wide "slug collision" exit status. ``graph build`` and ``status``
35
+ #: must exit identically on the same corpus (020 research D4), so the literal
36
+ #: lives once here, beside :data:`EXIT_CONFIG`.
37
+ EXIT_COLLISION = 3
38
+
39
+
40
+ def ok_payload(**fields: Any) -> dict[str, Any]:
41
+ """The success-envelope skeleton: ``{"status": "ok", **fields}`` (Principle IX).
42
+
43
+ The success-side complement of ``BookwrightError.to_json()`` — the one place
44
+ the ``"status": "ok"`` literal lives for new success documents (020 research
45
+ D6). Existing ``check``/``focus``/``graph`` call sites keep their hand-built
46
+ dicts for now; migrating them is out of 020's scope.
47
+ """
48
+ return {"status": "ok", **fields}
49
+
50
+
51
+ def render_json(payload: dict[str, Any]) -> str:
52
+ """The canonical one-line JSON encoding of a contract document.
53
+
54
+ The single place the compact-separators + trailing-newline format lives:
55
+ :func:`emit_json` routes stdout through it, and ``status`` renders its
56
+ document once here so stdout and the cache file share the exact bytes
57
+ (020 research D6) by construction, not by parallel formatting calls.
58
+ """
59
+ return json.dumps(payload, separators=(",", ":")) + "\n"
60
+
61
+
62
+ def emit_json(payload: dict[str, Any]) -> None:
63
+ """Write exactly one JSON document to stdout (the only thing on stdout)."""
64
+ sys.stdout.write(render_json(payload))
65
+
66
+
67
+ def emit_error(payload: dict[str, Any], json_output: bool) -> None:
68
+ """Surface an error envelope: one JSON doc on stdout under ``--json``, else a
69
+ single ``bookwright: error: <message>`` line on stderr (Principle IX)."""
70
+ if json_output:
71
+ emit_json(payload)
72
+ else:
73
+ sys.stderr.write(f"bookwright: error: {payload['message']}\n")
74
+
75
+
76
+ #: The contract code every caught ``ManifestError`` collapses to at a ``--json``
77
+ #: boundary. Single-sourced here so the two remap sites — this module and
78
+ #: ``commands.validate._UsageError`` — cannot drift to different literals.
79
+ INVALID_MANIFEST_CODE = "invalid_manifest"
80
+
81
+
82
+ class _InvalidManifestError(BookwrightError):
83
+ """A caught ``ManifestError`` re-coded to the contract's ``invalid_manifest``.
84
+
85
+ Mirrors ``commands.validate._UsageError(INVALID_MANIFEST_CODE, ...)``: the
86
+ remap is expressed as a ``BookwrightError`` whose canonical ``to_json()``
87
+ builds the envelope, never a hand-rolled dict.
88
+ """
89
+
90
+ code = INVALID_MANIFEST_CODE
91
+
92
+
93
+ def invalid_manifest_payload(exc: Exception) -> dict[str, Any]:
94
+ """The ``invalid_manifest`` error envelope for a caught ``ManifestError``."""
95
+ return _InvalidManifestError(str(exc)).to_json()
96
+
97
+
98
+ #: The contract code for "ran outside a project" at a ``--json`` boundary.
99
+ #: Single-sourced here so the two remap sites — ``commands.status`` and
100
+ #: ``commands.validate._UsageError`` — cannot drift to different literals.
101
+ NO_PROJECT_CODE = "no_project"
102
+
103
+
104
+ class _NoProjectError(BookwrightError):
105
+ """A caught ``ProjectNotFoundError`` re-coded to the contract's ``no_project``.
106
+
107
+ Mirrors :class:`_InvalidManifestError`: the remap is a ``BookwrightError``
108
+ whose canonical ``to_json()`` builds the envelope, never a hand-rolled dict.
109
+ """
110
+
111
+ code = NO_PROJECT_CODE
112
+
113
+
114
+ def no_project_payload(exc: ProjectNotFoundError) -> dict[str, Any]:
115
+ """The ``no_project`` error envelope for a caught ``ProjectNotFoundError``."""
116
+ return _NoProjectError(str(exc), {"start": exc.start}).to_json()
@@ -0,0 +1,135 @@
1
+ """The shared graph-build pipeline (`graph build` + `status`, 020 research D1).
2
+
3
+ The pipeline body extracted from ``commands/graph/build.py``: map the bible to
4
+ GOLEM entities (with CIDOC provenance), map ``bible/research/``, feed every
5
+ triple into one manifest-selected engine, and refresh the derived
6
+ ``bible/graph.ttl`` cache. Both verbs consume this one implementation so the
7
+ graph they reason over can never diverge.
8
+
9
+ The fault model is the pipeline's own — :class:`MissingDirectoryError` for
10
+ absent prerequisites, ``UnknownIndexerError`` from engine resolution,
11
+ ``SlugCollisionError`` / ``ResearchError`` from the mappers. Callers own
12
+ project/manifest resolution and the exit-code mapping (`graph build` per
13
+ cli-graph.md R7; `status` per 020 research D4/D5).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass
19
+ from typing import TYPE_CHECKING
20
+
21
+ from bookwright.golem.namespaces import timeline_uri
22
+ from bookwright.indexers import Indexer, UnknownIndexerError, resolve_indexer
23
+ from bookwright.io.bible import build_provenance, map_bible
24
+ from bookwright.io.errors import MissingDirectoryError, ResearchError
25
+ from bookwright.io.manuscript import manuscript_present
26
+ from bookwright.io.report import BuildReport, ResearchTargetWarning
27
+ from bookwright.io.research import ResearchResult, map_research
28
+
29
+ if TYPE_CHECKING:
30
+ from pathlib import Path
31
+
32
+ from bookwright.core.manifest import Manifest
33
+ from bookwright.errors import BookwrightError
34
+
35
+ #: The pipeline's own exit-2 fault classes, single-sourced beside the pipeline
36
+ #: that raises them so the two consuming except-ladders (`graph build`,
37
+ #: `status`) cannot drift apart on the same corpus (020 research D4).
38
+ PIPELINE_CONFIG_FAULTS: tuple[type[BookwrightError], ...] = (
39
+ MissingDirectoryError,
40
+ UnknownIndexerError,
41
+ ResearchError,
42
+ )
43
+
44
+
45
+ def missing_prerequisite(root: Path, manifest: Manifest) -> tuple[str, Path] | None:
46
+ """The first absent build prerequisite as ``(label, path)``, or ``None``.
47
+
48
+ The single statement of "can this corpus build?": :func:`build_project_graph`
49
+ raises ``MissingDirectoryError`` from it, and ``status`` branches to its
50
+ degraded report on it (020 research D5) — one predicate, no drift.
51
+ """
52
+ bible_dir = root / manifest.paths.bible
53
+ if not bible_dir.is_dir():
54
+ return ("bible", bible_dir)
55
+ manuscript_dir = root / manifest.paths.manuscript
56
+ if not manuscript_present(manuscript_dir):
57
+ return ("manuscript", manuscript_dir)
58
+ return None
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class BuildOutcome:
63
+ """Everything one pipeline run yields (data-model 020 § 5).
64
+
65
+ ``report`` is what `graph build` emits; ``engine`` (already populated and
66
+ saved) and ``research`` (carrying the authored identities, research D2) are
67
+ what ``status`` aggregates over.
68
+ """
69
+
70
+ engine: Indexer
71
+ report: BuildReport
72
+ research: ResearchResult
73
+
74
+
75
+ def build_project_graph(root: Path, manifest: Manifest) -> BuildOutcome:
76
+ """Build the project graph from the bible and write ``manifest.paths.graph``.
77
+
78
+ Raises the fault-model exceptions documented in the module docstring; on
79
+ success the returned engine holds the full graph and the Turtle cache on
80
+ disk matches it.
81
+ """
82
+ missing = missing_prerequisite(root, manifest)
83
+ if missing is not None:
84
+ label, path = missing
85
+ raise MissingDirectoryError(label, str(path))
86
+
87
+ bible_dir = root / manifest.paths.bible
88
+ engine_cls = resolve_indexer(manifest.bookwright.indexer)
89
+ engine = engine_cls()
90
+
91
+ uri_base = manifest.bookwright.uri_base
92
+ result = map_bible(root, bible_dir, uri_base)
93
+
94
+ for mapped in result.mapped:
95
+ for triple in mapped.entity.to_triples():
96
+ engine.add_triple(*triple)
97
+ for assignment in build_provenance(mapped, uri_base):
98
+ for triple in assignment.to_triples():
99
+ engine.add_triple(*triple)
100
+
101
+ # Research pass: map bible/research/ and feed its triples into the same engine
102
+ # (one graph, one save — research D8). Research entities are already E13
103
+ # reifications, so they are NOT routed through build_provenance.
104
+ research = map_research(
105
+ root,
106
+ bible_dir / "research",
107
+ uri_base,
108
+ manifest.book.language,
109
+ result.entity_index,
110
+ timeline_uri(uri_base),
111
+ )
112
+ for entity in research.entities:
113
+ for triple in entity.to_triples():
114
+ engine.add_triple(*triple)
115
+
116
+ graph_rel = manifest.paths.graph
117
+ engine.save(root / graph_rel)
118
+
119
+ report = BuildReport(
120
+ files_processed=result.files_processed + research.files_processed,
121
+ entities=len(result.entities) + len(research.entities),
122
+ triples=engine.count(),
123
+ graph_path=graph_rel,
124
+ skipped=tuple(result.skipped),
125
+ unknown_keys=tuple(result.unknown_keys),
126
+ unresolved_participants=tuple(result.unresolved_participants),
127
+ sources=len(research.sources),
128
+ findings=len(research.findings),
129
+ anchors=len(research.anchors),
130
+ research_warnings=tuple(
131
+ ResearchTargetWarning(path=w.relpath, field=w.field, name=w.name)
132
+ for w in research.warnings
133
+ ),
134
+ )
135
+ return BuildOutcome(engine=engine, report=report, research=research)
@@ -0,0 +1,44 @@
1
+ """Shared project-load + ``--json`` fault boundary for the command layer.
2
+
3
+ Every manifest-reading subcommand locates the project and loads the manifest the
4
+ same way and remaps the same two faults to exit 2 (research D10). Factoring it
5
+ here — next to the envelope helpers in :mod:`bookwright.commands._envelope` —
6
+ keeps each command body to its own logic and gives later iterations (e.g. the
7
+ 020 ``bookwright status`` read path) the boundary without reaching into a
8
+ sibling command package.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ import typer
16
+
17
+ from bookwright.core.errors import ManifestError
18
+ from bookwright.core.manifest import Manifest
19
+ from bookwright.io.errors import ProjectNotFoundError
20
+ from bookwright.io.project import find_project_root
21
+
22
+ from ._envelope import EXIT_CONFIG, emit_error, invalid_manifest_payload
23
+
24
+
25
+ def load_manifest_or_exit(json_output: bool) -> tuple[Path, Manifest]:
26
+ """Return ``(manifest_path, manifest)`` or emit the fault envelope and exit 2.
27
+
28
+ A caught ``ManifestError`` collapses to the contract's ``invalid_manifest``
29
+ code; ``ProjectNotFoundError`` carries its own ``not_a_project`` code. Both
30
+ exit 2 — the structured distinction lives in the envelope ``code`` (research
31
+ D7), never the exit status.
32
+ """
33
+
34
+ try:
35
+ root = find_project_root()
36
+ path = root / "manifest.toml"
37
+ manifest = Manifest.load(path)
38
+ except ManifestError as exc:
39
+ emit_error(invalid_manifest_payload(exc), json_output)
40
+ raise typer.Exit(EXIT_CONFIG) from exc
41
+ except ProjectNotFoundError as exc:
42
+ emit_error(exc.to_json(), json_output)
43
+ raise typer.Exit(EXIT_CONFIG) from exc
44
+ return path, manifest
@@ -1,13 +1,14 @@
1
1
  """`bookwright check` — verify the running interpreter and runtime dependencies."""
2
2
 
3
3
  import importlib
4
- import json
5
4
  import sys
6
5
  from typing import TypedDict
7
6
 
8
7
  import typer
9
8
  from rich.console import Console
10
9
 
10
+ from ._envelope import emit_json
11
+
11
12
  RUNTIME_MODULES: tuple[str, ...] = (
12
13
  "typer",
13
14
  "rich",
@@ -64,7 +65,7 @@ def run(
64
65
  ok = all(c["status"] == "ok" for c in checks)
65
66
  payload = {"ok": ok, "checks": checks}
66
67
  if json_output:
67
- sys.stdout.write(json.dumps(payload, separators=(",", ":")) + "\n")
68
+ emit_json(payload)
68
69
  else:
69
70
  console = Console()
70
71
  for check in checks:
@@ -0,0 +1,24 @@
1
+ """The ``bookwright focus`` Typer sub-app.
2
+
3
+ ``show``, ``set``, and ``clear`` live in their own modules (Principle IV) and
4
+ register their callbacks here. The app is wired into the root CLI in
5
+ :mod:`bookwright.cli` via ``app.add_typer(focus.app, name="focus")``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import typer
11
+
12
+ app = typer.Typer(
13
+ name="focus",
14
+ help="Record, view, and clear the authored focus state.",
15
+ no_args_is_help=True,
16
+ add_completion=False,
17
+ )
18
+
19
+ # Each subcommand registers its callback on `app` at import time; the per-story
20
+ # `from . import …` lines are appended below as the modules land. The `as`
21
+ # redirect marks the import as an intentional re-export (registration side effect).
22
+ from . import clear as clear # noqa: E402
23
+ from . import set_ as set_ # noqa: E402 (module name avoids shadowing builtins.set)
24
+ from . import show as show # noqa: E402
@@ -0,0 +1,40 @@
1
+ """``bookwright focus clear [--json]``.
2
+
3
+ Remove the ``[focus]`` block, preserving the rest of the manifest. Absent block ⇒
4
+ a successful no-op (FR-010). Under ``--json`` emit
5
+ ``{"status":"ok","cleared":<bool>}`` — the boolean discriminator lets an agent
6
+ tell a real removal from a no-op without a second read; both exit 0. Human prose
7
+ goes to stderr (Principle IX).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import typer
13
+ from rich.console import Console
14
+
15
+ from .._envelope import emit_json
16
+ from .._project import load_manifest_or_exit
17
+ from . import app
18
+
19
+
20
+ @app.command("clear")
21
+ def run(
22
+ json_output: bool = typer.Option(
23
+ False, "--json", help="Emit the result as one JSON document on stdout."
24
+ ),
25
+ ) -> None:
26
+ """Clear the authored focus state (no-op when none is set)."""
27
+ path, manifest = load_manifest_or_exit(json_output)
28
+ had_focus = manifest.focus is not None
29
+
30
+ manifest.clear_focus()
31
+ # Only rewrite when something actually changed — a no-op leaves the bytes
32
+ # untouched rather than re-serializing an unchanged manifest.
33
+ if had_focus:
34
+ manifest.dump(path, overwrite=True)
35
+
36
+ if json_output:
37
+ emit_json({"status": "ok", "cleared": had_focus})
38
+ else:
39
+ message = "focus cleared" if had_focus else "no focus to clear"
40
+ Console(stderr=True, highlight=False).print(message)