narrativetrace 0.4.0__tar.gz → 0.4.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 (260) hide show
  1. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/LICENSE +2 -2
  2. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/PKG-INFO +1 -1
  3. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/pyproject.toml +1 -1
  4. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/__init__.py +1 -1
  5. narrativetrace-0.4.2/src/narrativetrace/_generated.py +70 -0
  6. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/add-narrative-tracing/SKILL.md +40 -7
  7. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-doctor/SKILL.md +2 -2
  8. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-explain/SKILL.md +1 -1
  9. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-feedback/SKILL.md +1 -1
  10. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-verify/SKILL.md +3 -3
  11. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/catalogue.json +1 -1
  12. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/add-narrative-tracing/SKILL.md +40 -7
  13. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-doctor/SKILL.md +3 -3
  14. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-explain/SKILL.md +1 -1
  15. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-feedback/SKILL.md +1 -1
  16. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-verify/SKILL.md +3 -3
  17. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/config.py +4 -1
  18. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/context.py +39 -6
  19. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/environment.py +24 -0
  20. narrativetrace-0.4.2/src/narrativetrace/exclusions.py +171 -0
  21. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/metadata.py +38 -3
  22. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/redaction.py +65 -11
  23. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/trace_object.py +55 -2
  24. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_doctor_cli_bin.py +1 -1
  25. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_doctor_environment.py +36 -0
  26. narrativetrace-0.4.2/tests/test_adversarial_openai_plugin_pack.py +130 -0
  27. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context.py +63 -1
  28. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context7_check.py +5 -3
  29. narrativetrace-0.4.2/tests/test_exclusions.py +285 -0
  30. narrativetrace-0.4.2/tests/test_exclusions_docs.py +57 -0
  31. narrativetrace-0.4.2/tests/test_gate_lint_never_trusts_a_cache.py +51 -0
  32. narrativetrace-0.4.2/tests/test_generated_targets.py +119 -0
  33. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_identity.py +30 -0
  34. narrativetrace-0.4.2/tests/test_metadata.py +78 -0
  35. narrativetrace-0.4.2/tests/test_mutation_baseline_selection.py +94 -0
  36. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_notice_render_targets.py +5 -0
  37. narrativetrace-0.4.2/tests/test_openai_plugin_pack.py +273 -0
  38. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_promotion_render.py +23 -0
  39. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_redaction.py +82 -0
  40. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_skills_render.py +40 -0
  41. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_smoke.py +1 -1
  42. narrativetrace-0.4.2/tests/test_trace_object_catch_all_warning.py +91 -0
  43. narrativetrace-0.4.0/tests/test_metadata.py +0 -44
  44. narrativetrace-0.4.0/tests/test_mutation_baseline_selection.py +0 -57
  45. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/.gitignore +0 -0
  46. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/README.md +0 -0
  47. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_boundary.py +0 -0
  48. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/add-narrativetrace-clarity/SKILL.md +0 -0
  49. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/agents/narrativetrace-debug/SKILL.md +0 -0
  50. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/add-narrativetrace-clarity/SKILL.md +0 -0
  51. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/_skills/claude/narrativetrace-debug/SKILL.md +0 -0
  52. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/canonical.py +0 -0
  53. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/chapter.py +0 -0
  54. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/cli.py +0 -0
  55. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/concurrency.py +0 -0
  56. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/context_export.py +0 -0
  57. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/decorators.py +0 -0
  58. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/__init__.py +0 -0
  59. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/cli_bin.py +0 -0
  60. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/feedback_cli.py +0 -0
  61. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/doctor/gh_auth_probe.py +0 -0
  62. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/escape.py +0 -0
  63. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/events.py +0 -0
  64. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/export.py +0 -0
  65. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/groups.py +0 -0
  66. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/identity.py +0 -0
  67. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/ids.py +0 -0
  68. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/levels.py +0 -0
  69. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/logging_bridge.py +0 -0
  70. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/loss.py +0 -0
  71. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/markers.py +0 -0
  72. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/namer.py +0 -0
  73. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/national_id_shapes.py +0 -0
  74. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/nodes.py +0 -0
  75. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/outcomes.py +0 -0
  76. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/__init__.py +0 -0
  77. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/approval.py +0 -0
  78. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/artifact_identity.py +0 -0
  79. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/line_diff.py +0 -0
  80. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/manifest.py +0 -0
  81. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/paths.py +0 -0
  82. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/reporter.py +0 -0
  83. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/run_identity.py +0 -0
  84. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/structural_delta.py +0 -0
  85. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/warnings.py +0 -0
  86. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/output/writer.py +0 -0
  87. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/__init__.py +0 -0
  88. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/bounded_buffer.py +0 -0
  89. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/buffered_consumer.py +0 -0
  90. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/dual_path.py +0 -0
  91. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/event_store.py +0 -0
  92. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/perishable.py +0 -0
  93. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/pipeline/subscriber.py +0 -0
  94. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/py.typed +0 -0
  95. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/redacted_paths.py +0 -0
  96. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/__init__.py +0 -0
  97. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/base.py +0 -0
  98. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/camel.py +0 -0
  99. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/concurrency.py +0 -0
  100. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/frontmatter.py +0 -0
  101. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/indented.py +0 -0
  102. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/markdown.py +0 -0
  103. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/prose.py +0 -0
  104. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/scenario.py +0 -0
  105. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/scenario_result.py +0 -0
  106. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/span_id.py +0 -0
  107. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/structural.py +0 -0
  108. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/value_delta.py +0 -0
  109. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/render/value_reference.py +0 -0
  110. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/rendering.py +0 -0
  111. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/secret_value_shapes.py +0 -0
  112. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/signature.py +0 -0
  113. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/span.py +0 -0
  114. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/template.py +0 -0
  115. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/tree.py +0 -0
  116. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/tree_canonical.py +0 -0
  117. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/tree_walk.py +0 -0
  118. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/src/narrativetrace/values.py +0 -0
  119. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/conftest.py +0 -0
  120. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_adversarial_feedback_cli.py +0 -0
  121. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_built_wheels.py +0 -0
  122. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_feedback_arguments.py +0 -0
  123. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_feedback_verb.py +0 -0
  124. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_installer_arguments.py +0 -0
  125. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_cli_installer_verbs.py +0 -0
  126. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/doctor/test_gh_auth_probe.py +0 -0
  127. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/fixtures/java-negative-expense.nt +0 -0
  128. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/live_annotation_support.py +0 -0
  129. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/logging_wiring.py +0 -0
  130. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/stress_support.py +0 -0
  131. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_adversarial_skills_render.py +0 -0
  132. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_adversarial_vendor_validation.py +0 -0
  133. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_adversarial_verify_all_heavy.py +0 -0
  134. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_approval.py +0 -0
  135. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_artifact_identity.py +0 -0
  136. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_async_concurrency_kind.py +0 -0
  137. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_async_lifecycle_contract.py +0 -0
  138. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_async_snapshot_adoption.py +0 -0
  139. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_bench_capture.py +0 -0
  140. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_bench_redaction.py +0 -0
  141. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_bench_rendering.py +0 -0
  142. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_canonical.py +0 -0
  143. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_canonical_props.py +0 -0
  144. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_capture_identity.py +0 -0
  145. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_chapter.py +0 -0
  146. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_chapter_props.py +0 -0
  147. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_check_no_license_headers.py +0 -0
  148. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_cli.py +0 -0
  149. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_comment_hygiene.py +0 -0
  150. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_concurrency_info.py +0 -0
  151. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_config.py +0 -0
  152. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_console_delta.py +0 -0
  153. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context_export.py +0 -0
  154. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context_extra.py +0 -0
  155. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_context_stress.py +0 -0
  156. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_contract_check.py +0 -0
  157. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_contract_dispatch_coverage.py +0 -0
  158. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_contract_lint.py +0 -0
  159. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_coverage_source_completeness.py +0 -0
  160. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_decorators.py +0 -0
  161. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_deep_fixture_budget.py +0 -0
  162. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_distribution_licensing.py +0 -0
  163. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_duplication_check.py +0 -0
  164. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_duplication_report.py +0 -0
  165. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_errors_only_level.py +0 -0
  166. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_escape.py +0 -0
  167. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_events.py +0 -0
  168. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_export.py +0 -0
  169. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_feedback_docs.py +0 -0
  170. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_framework_table_docs.py +0 -0
  171. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_fuzz_report.py +0 -0
  172. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_groups.py +0 -0
  173. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_identity_conformance.py +0 -0
  174. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_ids.py +0 -0
  175. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_installer_from_another_package.py +0 -0
  176. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_issue_form_publishes.py +0 -0
  177. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_levels.py +0 -0
  178. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_live_snapshot_scope_visibility.py +0 -0
  179. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_logging_bridge.py +0 -0
  180. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_logging_wiring.py +0 -0
  181. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_manifest.py +0 -0
  182. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_accounting.py +0 -0
  183. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_gate.py +0 -0
  184. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_tooling.py +0 -0
  185. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_mutation_workers.py +0 -0
  186. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_namer.py +0 -0
  187. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_national_id_shapes.py +0 -0
  188. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_nested_chain_adoption.py +0 -0
  189. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_no_poison_contract.py +0 -0
  190. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_nodes.py +0 -0
  191. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_outcomes.py +0 -0
  192. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_output.py +0 -0
  193. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_parameter_name_redaction.py +0 -0
  194. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_parameter_value_redaction.py +0 -0
  195. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_buffered.py +0 -0
  196. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_dual_path.py +0 -0
  197. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_perishable.py +0 -0
  198. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_props.py +0 -0
  199. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_stress.py +0 -0
  200. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_pipeline_subscriber.py +0 -0
  201. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_platform_pointer_from_another_package.py +0 -0
  202. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_platform_table_docs.py +0 -0
  203. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_agents_md_section.py +0 -0
  204. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_public.py +0 -0
  205. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_public_host_venv.py +0 -0
  206. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_publish_workflow_packages.py +0 -0
  207. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_quality_gate_status.py +0 -0
  208. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_redacted_paths.py +0 -0
  209. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_redacted_value_containment.py +0 -0
  210. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_render.py +0 -0
  211. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_render_reads_state.py +0 -0
  212. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_render_reentrancy.py +0 -0
  213. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_rendering.py +0 -0
  214. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_rendering_extra.py +0 -0
  215. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_run_identity.py +0 -0
  216. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_run_security_tool.py +0 -0
  217. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_scenario_result.py +0 -0
  218. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_secret_value_shapes.py +0 -0
  219. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_signature.py +0 -0
  220. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_skills_carrier_wheel_contents.py +0 -0
  221. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_snippet_check.py +0 -0
  222. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span.py +0 -0
  223. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span_id.py +0 -0
  224. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span_id_flavours.py +0 -0
  225. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_span_id_props.py +0 -0
  226. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structlog_dependency.py +0 -0
  227. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structural.py +0 -0
  228. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structural_conformance.py +0 -0
  229. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_structural_delta.py +0 -0
  230. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_template.py +0 -0
  231. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_timeout_floor.py +0 -0
  232. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_trace_loss.py +0 -0
  233. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_trace_object.py +0 -0
  234. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_trace_stack_live_child.py +0 -0
  235. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_translation_check.py +0 -0
  236. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree.py +0 -0
  237. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_canonical.py +0 -0
  238. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_props.py +0 -0
  239. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_walk.py +0 -0
  240. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_tree_writes_guard.py +0 -0
  241. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_value_delta.py +0 -0
  242. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_value_reference.py +0 -0
  243. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_values.py +0 -0
  244. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_vendor_validation.py +0 -0
  245. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all.py +0 -0
  246. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_arch_stress.py +0 -0
  247. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_exec.py +0 -0
  248. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_heavy.py +0 -0
  249. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_schema.py +0 -0
  250. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_security.py +0 -0
  251. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_static.py +0 -0
  252. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_all_testrun.py +0 -0
  253. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_publication_packages.py +0 -0
  254. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_publication_provenance.py +0 -0
  255. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_verify_publication_registry.py +0 -0
  256. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_version_guard_ignores_a_stale_installed_carrier.py +0 -0
  257. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_version_literals.py +0 -0
  258. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_version_literals_props.py +0 -0
  259. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/test_writer_structural.py +0 -0
  260. {narrativetrace-0.4.0 → narrativetrace-0.4.2}/tests/wheel_support.py +0 -0
@@ -7,7 +7,7 @@ Parameters
7
7
 
8
8
  Licensor: Empower Agile
9
9
 
10
- Licensed Work: NarrativeTrace for Python (the narrativetrace PyPI distributions) version 0.4.0. The Licensed Work is (c) 2026 Empower Agile.
10
+ Licensed Work: NarrativeTrace for Python (the narrativetrace PyPI distributions) version 0.4.2. The Licensed Work is (c) 2026 Empower Agile.
11
11
 
12
12
  Additional Use Grant: You may make production use of the Licensed Work for any
13
13
  purpose, including internal use and use in products and
@@ -18,7 +18,7 @@ Additional Use Grant: You may make production use of the Licensed Work for any
18
18
  parties as a logging, tracing, or code-narrative product
19
19
  or service.
20
20
 
21
- Change Date: Four years from the date the Licensed Work is published. (2030-10-16 for this version)
21
+ Change Date: Four years from the date the Licensed Work is published. (2030-10-20 for this version)
22
22
 
23
23
  Change License: Apache License, Version 2.0
24
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: narrativetrace
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Narrative execution tracing for Python — human- and LLM-readable traces
5
5
  Project-URL: Homepage, https://github.com/narrativetrace/narrativetrace-python
6
6
  Project-URL: Source, https://github.com/narrativetrace/narrativetrace-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "narrativetrace"
3
- version = "0.4.0"
3
+ version = "0.4.2"
4
4
  description = "Narrative execution tracing for Python — human- and LLM-readable traces"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -97,7 +97,7 @@ from narrativetrace.values import (
97
97
  StringVal,
98
98
  )
99
99
 
100
- __version__ = "0.4.0"
100
+ __version__ = "0.4.2"
101
101
 
102
102
  __all__ = [
103
103
  "NOOP_CONTEXT",
@@ -0,0 +1,70 @@
1
+ # SPDX-License-Identifier: BUSL-1.1
2
+ # Licensed under the Business Source License 1.1 (see LICENSE); Change Date: four
3
+ # years from publication; Change License: Apache-2.0
4
+ # Copyright (c) 2026 Empower Agile
5
+ """Exclusion list 1 -- what is structurally never a business boundary in Python.
6
+
7
+ Built in and not configurable: no setting re-enables a member, and nothing here is reported.
8
+ Membership is a structural property of the object, never a vendor's module name:
9
+
10
+ * a target whose type is defined by the interpreter itself (``type(target).__module__ ==
11
+ "builtins"``: ``list``, ``dict``, ``str``, ...) -- C-accelerated, no business logic to narrate;
12
+ * a ``functools.partial`` / ``functools.partialmethod`` object, as a target or as a member -- a
13
+ library-built forwarder whose only behaviour is the callable it wraps;
14
+ * a member the C runtime supplies (builtin functions and bound builtin methods, method wrappers,
15
+ slot wrappers, method descriptors) -- ``types.BuiltinFunctionType`` and its siblings;
16
+ * a member whose ``__name__`` is not an identifier (``<lambda>``, ``<genexpr>``): a name no
17
+ ``def`` statement can produce, only the compiler;
18
+ * a ``_private`` / ``__dunder__`` member -- machinery the runtime calls on its own behalf.
19
+
20
+ **@llmNote** ``functools.lru_cache`` wrappers and ``functools.wraps``-decorated functions are
21
+ deliberately NOT members: ``wraps`` makes the wrapper indistinguishable from the function it
22
+ wraps, and a cached business method is still a boundary the reader wants to see.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import functools
28
+ import types
29
+
30
+ GENERATED_MEMBERS: tuple[tuple[str, str], ...] = (
31
+ (
32
+ "builtins.*",
33
+ "The target's type is defined by the interpreter itself (`list`, `dict`, `str`)",
34
+ ),
35
+ (
36
+ "functools.partial",
37
+ "A `functools.partial` / `partialmethod` object, as a target or as a member",
38
+ ),
39
+ (
40
+ "types.BuiltinFunctionType",
41
+ "A member the C runtime supplies: builtin functions, bound builtin methods, method "
42
+ "wrappers, slot wrappers, method descriptors",
43
+ ),
44
+ ("<lambda>", "A member whose `__name__` is not an identifier (`<lambda>`, `<genexpr>`)"),
45
+ ("_name", "A `_private` or `__dunder__` member: machinery the runtime calls for itself"),
46
+ )
47
+ """List 1 as the configuration guide publishes it: (what is never traced, its structural
48
+ property). A test holds the guide's table to this tuple."""
49
+
50
+ _C_RUNTIME_CALLABLES: tuple[type, ...] = (
51
+ types.BuiltinFunctionType,
52
+ types.MethodWrapperType,
53
+ types.WrapperDescriptorType,
54
+ types.MethodDescriptorType,
55
+ )
56
+ _FUNCTOOLS_FORWARDERS: tuple[type, ...] = (functools.partial, functools.partialmethod)
57
+
58
+
59
+ def is_generated_target(target: object) -> bool:
60
+ """True when ``target`` itself is never a business boundary (see the module docstring)."""
61
+ return type(target).__module__ == "builtins" or isinstance(target, _FUNCTOOLS_FORWARDERS)
62
+
63
+
64
+ def is_generated_member(name: str, member: object) -> bool:
65
+ """True when member ``name`` (already fetched as ``member``) is never a business boundary."""
66
+ if name.startswith("_"):
67
+ return True
68
+ if isinstance(member, _C_RUNTIME_CALLABLES + _FUNCTOOLS_FORWARDERS):
69
+ return True
70
+ return not getattr(member, "__name__", "x").isidentifier()
@@ -8,7 +8,8 @@ description: "Installs NarrativeTrace into a Python project and gets it to a fir
8
8
  ## 1. Install with the real toolchain
9
9
 
10
10
  ```bash
11
- uv sync --all-packages
11
+ uv add narrativetrace
12
+ uv sync
12
13
  ```
13
14
 
14
15
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
@@ -19,6 +20,10 @@ if bad:
19
20
  sys.exit(1 if bad else 0)
20
21
  '`
21
22
 
23
+ **failure:** the project is managed by pip, poetry or pdm rather than uv, or `uv add` just wrote a uv.lock beside another manager's lockfile — this page spells its commands in uv, and the project uses another manager. Fix: remove the uv.lock uv wrote; a project on pip, poetry or pdm adds the same distribution with its own command and checks with its own clean install
24
+
25
+ **failure:** `uv add` exits 2 with `No pyproject.toml found` — the directory is no Python project yet, and `uv add` does not create one. Fix: run `uv init --bare` once, then `uv add narrativetrace` again
26
+
22
27
  **failure:** a sibling narrativetrace-* package disagrees on version — an installed distribution outside the lockstep version the other narrativetrace-* packages share. Fix: run the narrativetrace-doctor skill's toolchain.package-versions check, then pin every narrativetrace-* dependency to the same version and `uv sync`
23
28
 
24
29
  ## 2. Wire the frameworks this project already uses
@@ -41,7 +46,7 @@ sys.exit(1 if bad else 0)
41
46
 
42
47
  ## 3. First trace: wrap, call, render, run
43
48
 
44
- **when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows
49
+ **when:** if the project already has tests — a test directory, a runner's table in its pyproject.toml or a test runner among its dependencies — and the doctor reports a test integration for that runner, do not add a demo script: run one existing test through that integration with output on, which writes that test's narrative files, .md and .nt, under the integration's output directory — the files narrativetrace-doctor, -verify, -debug and -explain read; print the rendered file so the trace is also on the console; the step is done when that test's files exist; a project whose runner has no integration keeps that runner and takes the console path below; if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows; nothing is written to disk by this script — the application's log is the record once the logging bridge in the later step is wired; this page never adds a test runner or a test to a project that has none
45
50
 
46
51
  <!-- snippet: examples/sixty_seconds/main.py -->
47
52
  ```python
@@ -76,7 +81,35 @@ print(IndentedTextRenderer().render(context.capture_trace()))
76
81
 
77
82
  **failure:** a *args method's parameters render as one args: [...] value — inspect.signature has nothing named to reconstruct per-argument that Python itself does not have. Fix: supply explicit names: @traced("first", "second", ...) above the method
78
83
 
79
- ## 4. Send it to your logger
84
+ **failure:** a bare function, a lambda or a functools.partial gives no spans at all, or a span carries the wrong class name — a span is named after type(target).__name__, and trace_object never traces a function, lambda or partial. Fix: trace an instance of a class of your own, or pass class_name="..." to trace_object to show a different name
85
+
86
+ **failure:** trace_object returns the object unwrapped and stderr says it cannot trace it (excluded module ...) — the class is defined under a module prefix on the exclude list, whose shipped default covers test and mocking libraries and APM runtimes. Fix: wrap a class of your own, or set NARRATIVETRACE_EXCLUDE (or the exclude key) to the full list you want, which replaces the default
87
+
88
+ ## 4. Name the service
89
+
90
+ <!-- snippet: examples/sixty_seconds/main_named_service.py -->
91
+ ```python
92
+ # main.py
93
+ from narrativetrace import ContextVarNarrativeContext, ServiceIdentity
94
+
95
+ context = ContextVarNarrativeContext(service_identity=ServiceIdentity("orders", "1.4.0", "prod"))
96
+ ```
97
+ <!-- /snippet -->
98
+
99
+ **done when:** the service has its own real name — set once, where the context is built or in the environment the service runs in (NARRATIVETRACE_SERVICE_NAME), never a placeholder; in an existing application the line that builds the context changes, no second context is added
100
+
101
+ **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
102
+ report = json.load(sys.stdin)
103
+ by_id = {f["id"]: f for f in report["findings"]}
104
+ finding = by_id.get("config.service-identity")
105
+ if finding is None or finding["status"] != "pass":
106
+ print(finding["fix"] if finding else "the doctor has no config.service-identity finding")
107
+ sys.exit(0 if finding is not None and finding["status"] == "pass" else 1)
108
+ '`
109
+
110
+ **failure:** traces say unknown_service:python on the platform — nobody named the service, so the fallback name is all there is. Fix: pass service_identity=ServiceIdentity("orders") to the ContextVarNarrativeContext, or set NARRATIVETRACE_SERVICE_NAME (and NARRATIVETRACE_SERVICE_VERSION, NARRATIVETRACE_ENVIRONMENT) in the environment the service runs in
111
+
112
+ ## 5. Send it to your logger
80
113
 
81
114
  **when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a second main.py or a second logging setup: add the filter and the export_to_logger call to the application's own logging configuration and the boundary you exercised; the verify below is for the standalone script, and in an existing application the step is done when that boundary's trace reaches the application's own logger; otherwise create the smallest script as follows
82
115
 
@@ -132,7 +165,7 @@ export_to_logger(trace) # new: sends the same captured trace through the config
132
165
 
133
166
  **verify:** `uv run python main_with_logger.py`
134
167
 
135
- ## 5. Run the doctor and resolve its findings
168
+ ## 6. Run the doctor and resolve its findings
136
169
 
137
170
  ```bash
138
171
  uv run narrativetrace doctor || true
@@ -141,10 +174,10 @@ uv run narrativetrace doctor || true
141
174
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
142
175
  report = json.load(sys.stdin)
143
176
  findings = report.get("findings")
144
- sys.exit(1 if not isinstance(findings, list) or len(findings) != 19 else 0)
177
+ sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
145
178
  '`
146
179
 
147
- ## 6. Install the skills for next time
180
+ ## 7. Install the skills for next time
148
181
 
149
182
  ```bash
150
183
  uv run narrativetrace init --dry-run
@@ -169,7 +202,7 @@ sys.exit(0 if result.returncode == 0 and shown and untouched else 1)
169
202
 
170
203
  ## Always
171
204
 
172
- - Reinstall clean (`uv sync`) rather than trusting whatever is already in the virtual environment. (a mismatched sibling version or a stale lockfile is the single most common install failure, and it only surfaces on a clean install)
205
+ - Check the install with a clean `uv sync` after adding the package, rather than trusting whatever is already in the virtual environment. (`uv add` installs; the clean sync is the check that the lockfile it wrote reproduces -- a mismatched sibling version or a stale lockfile is the single most common install failure, and it only surfaces on a clean install)
173
206
 
174
207
  ## Never
175
208
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: narrativetrace-doctor
3
- description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
3
+ description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed in this project and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
4
4
  ---
5
5
 
6
6
  # narrativetrace-doctor
@@ -14,7 +14,7 @@ uv run narrativetrace doctor || true
14
14
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
15
15
  report = json.load(sys.stdin)
16
16
  findings = report.get("findings")
17
- sys.exit(1 if not isinstance(findings, list) or len(findings) != 19 else 0)
17
+ sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
18
18
  '`
19
19
 
20
20
  **failure:** the CLI's JSON output does not parse, or is missing findings — the CLI crashed instead of reporting a finding. Fix: re-run `uv run narrativetrace doctor --json` directly and read the raw output -- a crash here is a doctor bug, never a project finding
@@ -57,7 +57,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
57
57
 
58
58
  ## 8. Explain the run by span id, and mark what you inferred
59
59
 
60
- **done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one
60
+ **done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one, and the branches this run did not take
61
61
 
62
62
  ## Which flavour answers which question
63
63
 
@@ -14,7 +14,7 @@ uv run narrativetrace doctor || true
14
14
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
15
15
  report = json.load(sys.stdin)
16
16
  findings = report.get("findings")
17
- sys.exit(1 if not isinstance(findings, list) or len(findings) != 19 else 0)
17
+ sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
18
18
  '`
19
19
 
20
20
  **failure:** the CLI is not found, or the project has no pyproject.toml — NarrativeTrace is not installed in this project's environment. Fix: report under the prompt or library category instead -- those do not need a doctor report
@@ -65,7 +65,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/s
65
65
 
66
66
  ## 5. Open values on the span that looks wrong, and only there
67
67
 
68
- **when:** only when the structural read named a span that does not match the intent; otherwise go on to the pin
68
+ **when:** when the structural read named a span that does not match the intent, or the shape matches but a symptom points at data — an assertion failed on a value, the output is wrong — then open the one span the symptom points at; otherwise go on to the pin
69
69
 
70
70
  ```bash
71
71
  uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/traces").rglob("*.md")), sep="\n")'
@@ -75,7 +75,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
75
75
 
76
76
  ## 6. Fix, re-run, read again
77
77
 
78
- **when:** only when the structural read named a span that does not match the intent; otherwise go on to the pin
78
+ **when:** only when the structural read named a span that does not match the intent, or the value read in step 5 was wrong; otherwise go on to the pin
79
79
 
80
80
  ```bash
81
81
  uv run pytest <the smallest test that drives the real path>
@@ -118,7 +118,7 @@ uv run narrativetrace-approve
118
118
 
119
119
  ## 11. Report what the trace showed
120
120
 
121
- **done when:** two sentences on what the trace showed, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; 'tests pass' alone is not the report
121
+ **done when:** two sentences on what the trace showed on the path this run took, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; the report names the branches and inputs this run did not exercise, because one trace shows one path, not every execution; 'tests pass' alone is not the report
122
122
 
123
123
  ## Which flavour answers which question
124
124
 
@@ -3,7 +3,7 @@
3
3
  "skills": [
4
4
  {
5
5
  "name": "narrativetrace-doctor",
6
- "description": "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it.",
6
+ "description": "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed in this project and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it.",
7
7
  "agents": "agents/narrativetrace-doctor/SKILL.md",
8
8
  "claude": "claude/narrativetrace-doctor/SKILL.md"
9
9
  },
@@ -10,7 +10,8 @@ allowed-tools: Bash(uv *), Bash(git *)
10
10
  ## 1. Install with the real toolchain
11
11
 
12
12
  ```bash
13
- uv sync --all-packages
13
+ uv add narrativetrace
14
+ uv sync
14
15
  ```
15
16
 
16
17
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
@@ -21,6 +22,10 @@ if bad:
21
22
  sys.exit(1 if bad else 0)
22
23
  '`
23
24
 
25
+ **failure:** the project is managed by pip, poetry or pdm rather than uv, or `uv add` just wrote a uv.lock beside another manager's lockfile — this page spells its commands in uv, and the project uses another manager. Fix: remove the uv.lock uv wrote; a project on pip, poetry or pdm adds the same distribution with its own command and checks with its own clean install
26
+
27
+ **failure:** `uv add` exits 2 with `No pyproject.toml found` — the directory is no Python project yet, and `uv add` does not create one. Fix: run `uv init --bare` once, then `uv add narrativetrace` again
28
+
24
29
  **failure:** a sibling narrativetrace-* package disagrees on version — an installed distribution outside the lockstep version the other narrativetrace-* packages share. Fix: run the narrativetrace-doctor skill's toolchain.package-versions check, then pin every narrativetrace-* dependency to the same version and `uv sync`
25
30
 
26
31
  ## 2. Wire the frameworks this project already uses
@@ -43,7 +48,7 @@ sys.exit(1 if bad else 0)
43
48
 
44
49
  ## 3. First trace: wrap, call, render, run
45
50
 
46
- **when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows
51
+ **when:** if the project already has tests — a test directory, a runner's table in its pyproject.toml or a test runner among its dependencies — and the doctor reports a test integration for that runner, do not add a demo script: run one existing test through that integration with output on, which writes that test's narrative files, .md and .nt, under the integration's output directory — the files narrativetrace-doctor, -verify, -debug and -explain read; print the rendered file so the trace is also on the console; the step is done when that test's files exist; a project whose runner has no integration keeps that runner and takes the console path below; if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a demo main.py: run the application the way it already runs, exercise one real boundary, and read that request's trace; the verify below is for the standalone script, and in an existing application the step is done when that request's trace is in the output; otherwise create the smallest script as follows; nothing is written to disk by this script — the application's log is the record once the logging bridge in the later step is wired; this page never adds a test runner or a test to a project that has none
47
52
 
48
53
  <!-- snippet: examples/sixty_seconds/main.py -->
49
54
  ```python
@@ -78,7 +83,35 @@ print(IndentedTextRenderer().render(context.capture_trace()))
78
83
 
79
84
  **failure:** a *args method's parameters render as one args: [...] value — inspect.signature has nothing named to reconstruct per-argument that Python itself does not have. Fix: supply explicit names: @traced("first", "second", ...) above the method
80
85
 
81
- ## 4. Send it to your logger
86
+ **failure:** a bare function, a lambda or a functools.partial gives no spans at all, or a span carries the wrong class name — a span is named after type(target).__name__, and trace_object never traces a function, lambda or partial. Fix: trace an instance of a class of your own, or pass class_name="..." to trace_object to show a different name
87
+
88
+ **failure:** trace_object returns the object unwrapped and stderr says it cannot trace it (excluded module ...) — the class is defined under a module prefix on the exclude list, whose shipped default covers test and mocking libraries and APM runtimes. Fix: wrap a class of your own, or set NARRATIVETRACE_EXCLUDE (or the exclude key) to the full list you want, which replaces the default
89
+
90
+ ## 4. Name the service
91
+
92
+ <!-- snippet: examples/sixty_seconds/main_named_service.py -->
93
+ ```python
94
+ # main.py
95
+ from narrativetrace import ContextVarNarrativeContext, ServiceIdentity
96
+
97
+ context = ContextVarNarrativeContext(service_identity=ServiceIdentity("orders", "1.4.0", "prod"))
98
+ ```
99
+ <!-- /snippet -->
100
+
101
+ **done when:** the service has its own real name — set once, where the context is built or in the environment the service runs in (NARRATIVETRACE_SERVICE_NAME), never a placeholder; in an existing application the line that builds the context changes, no second context is added
102
+
103
+ **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
104
+ report = json.load(sys.stdin)
105
+ by_id = {f["id"]: f for f in report["findings"]}
106
+ finding = by_id.get("config.service-identity")
107
+ if finding is None or finding["status"] != "pass":
108
+ print(finding["fix"] if finding else "the doctor has no config.service-identity finding")
109
+ sys.exit(0 if finding is not None and finding["status"] == "pass" else 1)
110
+ '`
111
+
112
+ **failure:** traces say unknown_service:python on the platform — nobody named the service, so the fallback name is all there is. Fix: pass service_identity=ServiceIdentity("orders") to the ContextVarNarrativeContext, or set NARRATIVETRACE_SERVICE_NAME (and NARRATIVETRACE_SERVICE_VERSION, NARRATIVETRACE_ENVIRONMENT) in the environment the service runs in
113
+
114
+ ## 5. Send it to your logger
82
115
 
83
116
  **when:** if the project already has an application entry point — a script or module that starts it, a web or application framework the doctor reports — do not create a second main.py or a second logging setup: add the filter and the export_to_logger call to the application's own logging configuration and the boundary you exercised; the verify below is for the standalone script, and in an existing application the step is done when that boundary's trace reaches the application's own logger; otherwise create the smallest script as follows
84
117
 
@@ -134,7 +167,7 @@ export_to_logger(trace) # new: sends the same captured trace through the config
134
167
 
135
168
  **verify:** `uv run python main_with_logger.py`
136
169
 
137
- ## 5. Run the doctor and resolve its findings
170
+ ## 6. Run the doctor and resolve its findings
138
171
 
139
172
  ```bash
140
173
  uv run narrativetrace doctor || true
@@ -143,10 +176,10 @@ uv run narrativetrace doctor || true
143
176
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
144
177
  report = json.load(sys.stdin)
145
178
  findings = report.get("findings")
146
- sys.exit(1 if not isinstance(findings, list) or len(findings) != 19 else 0)
179
+ sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
147
180
  '`
148
181
 
149
- ## 6. Install the skills for next time
182
+ ## 7. Install the skills for next time
150
183
 
151
184
  ```bash
152
185
  uv run narrativetrace init --dry-run
@@ -171,7 +204,7 @@ sys.exit(0 if result.returncode == 0 and shown and untouched else 1)
171
204
 
172
205
  ## Always
173
206
 
174
- - Reinstall clean (`uv sync`) rather than trusting whatever is already in the virtual environment. (a mismatched sibling version or a stale lockfile is the single most common install failure, and it only surfaces on a clean install)
207
+ - Check the install with a clean `uv sync` after adding the package, rather than trusting whatever is already in the virtual environment. (`uv add` installs; the clean sync is the check that the lockfile it wrote reproduces -- a mismatched sibling version or a stale lockfile is the single most common install failure, and it only surfaces on a clean install)
175
208
 
176
209
  ## Never
177
210
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: narrativetrace-doctor
3
- description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
4
- when_to_use: "A project already has NarrativeTrace installed and something about it is not working, or an agent wants a pre-flight check before wiring it into new code."
3
+ description: "Diagnoses a NarrativeTrace Python install and configuration. Use when nothing is being traced, no trace output files appear, DuplicateConfigurationError shows up on startup, a *args method's parameters render as one args: [...] value, or you are not sure NarrativeTrace is wired up correctly. Checks the interpreter and pytest versions, that all eight narrativetrace-* packages agree on one version, NARRATIVETRACE_OUTPUT, that the pytest plugin is registered and not disabled, whether the NarrativeTrace agent skills are installed in this project and current, unrecognized narrativetrace.toml keys, an imported-but-unused not_traced_field/__nt_not_traced__ marker, whether redaction is proven in a test, stale approval-trace diffs, and approved baselines nothing compares because approval mode is off. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
4
+ when_to_use: "A project already has NarrativeTrace installed and something about it is not working, or an agent wants a pre-flight check before wiring it into new code. Also when a user installed these skills as a personal plugin in their agent rather than in this project: a plugin install is invisible to this check by design, and only a project install reaches teammates and other agents."
5
5
  allowed-tools: Bash(uv *), Bash(git *)
6
6
  ---
7
7
 
@@ -16,7 +16,7 @@ uv run narrativetrace doctor || true
16
16
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
17
17
  report = json.load(sys.stdin)
18
18
  findings = report.get("findings")
19
- sys.exit(1 if not isinstance(findings, list) or len(findings) != 19 else 0)
19
+ sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
20
20
  '`
21
21
 
22
22
  **failure:** the CLI's JSON output does not parse, or is missing findings — the CLI crashed instead of reporting a finding. Fix: re-run `uv run narrativetrace doctor --json` directly and read the raw output -- a crash here is a doctor bug, never a project finding
@@ -58,7 +58,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
58
58
 
59
59
  ## 8. Explain the run by span id, and mark what you inferred
60
60
 
61
- **done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one
61
+ **done when:** the explanation is in the user's own words and in the trace's own order: the calls the flow made, each claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt or .md read in this session count — the branch it took, and where the time went when that was the question. Every sentence that goes beyond what the trace shows is marked as an inference, and the last part names what was NOT observed: the code no span covers, because only wrapped or intercepted code has one, and the branches this run did not take
62
62
 
63
63
  ## Which flavour answers which question
64
64
 
@@ -15,7 +15,7 @@ uv run narrativetrace doctor || true
15
15
  **verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
16
16
  report = json.load(sys.stdin)
17
17
  findings = report.get("findings")
18
- sys.exit(1 if not isinstance(findings, list) or len(findings) != 19 else 0)
18
+ sys.exit(1 if not isinstance(findings, list) or len(findings) != 21 else 0)
19
19
  '`
20
20
 
21
21
  **failure:** the CLI is not found, or the project has no pyproject.toml — NarrativeTrace is not installed in this project's environment. Fix: report under the prompt or library category instead -- those do not need a doctor report
@@ -66,7 +66,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/s
66
66
 
67
67
  ## 5. Open values on the span that looks wrong, and only there
68
68
 
69
- **when:** only when the structural read named a span that does not match the intent; otherwise go on to the pin
69
+ **when:** when the structural read named a span that does not match the intent, or the shape matches but a symptom points at data — an assertion failed on a value, the output is wrong — then open the one span the symptom points at; otherwise go on to the pin
70
70
 
71
71
  ```bash
72
72
  uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/traces").rglob("*.md")), sep="\n")'
@@ -76,7 +76,7 @@ uv run python -c 'import pathlib; print(*sorted(pathlib.Path("narrative-traces/t
76
76
 
77
77
  ## 6. Fix, re-run, read again
78
78
 
79
- **when:** only when the structural read named a span that does not match the intent; otherwise go on to the pin
79
+ **when:** only when the structural read named a span that does not match the intent, or the value read in step 5 was wrong; otherwise go on to the pin
80
80
 
81
81
  ```bash
82
82
  uv run pytest <the smallest test that drives the real path>
@@ -119,7 +119,7 @@ uv run narrativetrace-approve
119
119
 
120
120
  ## 11. Report what the trace showed
121
121
 
122
- **done when:** two sentences on what the trace showed, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; 'tests pass' alone is not the report
122
+ **done when:** two sentences on what the trace showed on the path this run took, every claim citing the span id it rests on — a claim without an id is not a claim, and only ids in the .nt that was read count — with the .nt attached or quoted; the report names the branches and inputs this run did not exercise, because one trace shows one path, not every execution; 'tests pass' alone is not the report
123
123
 
124
124
  ## Which flavour answers which question
125
125
 
@@ -60,9 +60,12 @@ def _load_toml(path: Path) -> dict[str, Any] | None:
60
60
 
61
61
 
62
62
  def _canonical(value: Any) -> str:
63
- """Renders a TOML scalar the way a properties file would carry it (``true``, not ``True``)."""
63
+ """Renders a TOML value the way a properties file would carry it (``true``, not ``True``; an
64
+ array as its items joined by ``;``, the delimiter list-valued keys such as ``exclude`` use)."""
64
65
  if isinstance(value, bool):
65
66
  return "true" if value else "false"
67
+ if isinstance(value, list):
68
+ return ";".join(_canonical(item) for item in value)
66
69
  return str(value)
67
70
 
68
71
 
@@ -223,8 +223,21 @@ class NarrativeContext(ABC):
223
223
  """Returns the current trace id, generating one eagerly if absent."""
224
224
  return TraceId.generate()
225
225
 
226
- def adopt_trace_id(self, trace_id: TraceId) -> None:
227
- """Adopts an inbound (e.g. W3C ``traceparent``) trace id for the current scope."""
226
+ def adopt_trace_id(
227
+ self,
228
+ trace_id: TraceId,
229
+ remote_parent_span_id: SpanId | None = None,
230
+ trace_flags: int = 0,
231
+ ) -> None:
232
+ """Adopts an inbound (e.g. W3C ``traceparent``) trace id for the current scope.
233
+
234
+ ``remote_parent_span_id`` is the caller's span the inbound request came from (the
235
+ ``traceparent`` parent-id field). When given, this scope's root spans record it as their
236
+ ``parent_span_id``, so a consumer that follows parent ids links this service's trace to the
237
+ caller's; it never appears as a span in this scope's own tree. ``trace_flags`` is the
238
+ ``traceparent`` flags byte (bit 0 = sampled); every span this scope creates carries it as
239
+ :attr:`~narrativetrace.span.SpanContext.trace_flags`.
240
+ """
228
241
 
229
242
  def story_id(self) -> str | None:
230
243
  """Story id derived from the first root-level call, or ``None``."""
@@ -290,6 +303,8 @@ class _TraceStack:
290
303
  trace_id: TraceId | None = None
291
304
  scoped_parent: SpanId | None = None
292
305
  snapshot_parent: SpanId | None = None
306
+ remote_parent: SpanId | None = None
307
+ trace_flags: int = 0
293
308
  story_id: str | None = None
294
309
  chapter_id: str | None = None
295
310
  metadata: _RequestMetadata = _EMPTY_METADATA
@@ -490,6 +505,11 @@ class ContextVarNarrativeContext(NarrativeContext):
490
505
 
491
506
  The stack lives in a :class:`contextvars.ContextVar`, so a fresh thread and a freshly
492
507
  created asyncio task each lazily obtain their own isolated stack while sharing one store.
508
+
509
+ ``service_identity`` names the service on every span. Left ``None`` it is read from
510
+ ``NARRATIVETRACE_SERVICE_NAME`` / ``NARRATIVETRACE_SERVICE_VERSION`` /
511
+ ``NARRATIVETRACE_ENVIRONMENT`` (:meth:`ServiceIdentity.from_environment`); with neither, traces
512
+ say ``unknown_service:python``.
493
513
  """
494
514
 
495
515
  def __init__(
@@ -499,7 +519,9 @@ class ContextVarNarrativeContext(NarrativeContext):
499
519
  store: EventStore | None = None,
500
520
  ) -> None:
501
521
  self._config = config if config is not None else NarrativeTraceConfig()
502
- self._service_identity = service_identity
522
+ self._service_identity = (
523
+ service_identity if service_identity is not None else ServiceIdentity.from_environment()
524
+ )
503
525
  self._store = store if store is not None else EventStore()
504
526
  self._span_contexts: dict[SpanId, SpanContext] = {}
505
527
  self._span_lock = threading.Lock()
@@ -556,7 +578,9 @@ class ContextVarNarrativeContext(NarrativeContext):
556
578
  active = stack.peek_active()
557
579
  if active is not None:
558
580
  return active
559
- return stack.snapshot_parent
581
+ if stack.snapshot_parent is not None:
582
+ return stack.snapshot_parent
583
+ return stack.remote_parent
560
584
 
561
585
  def _create_span_context(
562
586
  self, stack: _TraceStack, parent_span_id: SpanId | None
@@ -571,6 +595,7 @@ class ContextVarNarrativeContext(NarrativeContext):
571
595
  trace_id=trace_id,
572
596
  span_id=SpanId.generate(),
573
597
  parent_span_id=parent_span_id,
598
+ trace_flags=stack.trace_flags,
574
599
  http_method=metadata.http_method,
575
600
  http_route=metadata.http_route,
576
601
  client_ip=metadata.client_ip,
@@ -744,8 +769,16 @@ class ContextVarNarrativeContext(NarrativeContext):
744
769
  stack.trace_id = TraceId.generate()
745
770
  return stack.trace_id
746
771
 
747
- def adopt_trace_id(self, trace_id: TraceId) -> None:
748
- self._get_stack().trace_id = trace_id
772
+ def adopt_trace_id(
773
+ self,
774
+ trace_id: TraceId,
775
+ remote_parent_span_id: SpanId | None = None,
776
+ trace_flags: int = 0,
777
+ ) -> None:
778
+ stack = self._get_stack()
779
+ stack.trace_id = trace_id
780
+ stack.remote_parent = remote_parent_span_id
781
+ stack.trace_flags = trace_flags
749
782
 
750
783
  def story_id(self) -> str | None:
751
784
  return self._get_stack().story_id
@@ -22,8 +22,10 @@ import sys
22
22
  import tomllib
23
23
  from importlib import metadata as importlib_metadata
24
24
  from pathlib import Path
25
+ from typing import Literal
25
26
 
26
27
  from narrativetrace.config import ConfigResolver
28
+ from narrativetrace.exclusions import ExcludedModules
27
29
  from narrativetrace_tooling.doctor.types import DoctorSnapshot, Env, PackageInfo
28
30
  from narrativetrace_tooling.frameworks.manifests import is_manifest
29
31
  from narrativetrace_tooling.init import (
@@ -222,6 +224,24 @@ class _Walker:
222
224
  self._visit_file(entry)
223
225
 
224
226
 
227
+ def _resolve_exclude(
228
+ resolver: ConfigResolver, env: Env
229
+ ) -> tuple[tuple[str, ...] | None, Literal["default", "configured"], str | None]:
230
+ """The snapshot's exclusion-list fields: (effective list, where it came from, parse error).
231
+
232
+ ``env`` is consulted first so a snapshot built from an explicit environment reports that
233
+ environment; otherwise the resolver's own chain (process environment, config file) decides."""
234
+ given = env.get("NARRATIVETRACE_EXCLUDE")
235
+ try:
236
+ excluded = (
237
+ ExcludedModules.parse(given) if given is not None else ExcludedModules.resolve(resolver)
238
+ )
239
+ except ValueError as error:
240
+ return None, "default", str(error)
241
+ configured = given is not None or resolver.resolve("exclude", None) is not None
242
+ return excluded.prefixes, "configured" if configured else "default", None
243
+
244
+
225
245
  def build_snapshot(cwd: str, env: Env) -> DoctorSnapshot:
226
246
  """Builds a :class:`~narrativetrace_tooling.doctor.types.DoctorSnapshot` from the real
227
247
  filesystem rooted at ``cwd``."""
@@ -230,6 +250,7 @@ def build_snapshot(cwd: str, env: Env) -> DoctorSnapshot:
230
250
  config = resolver.file_values
231
251
  output_dir_name = str(config.get("output_dir") or "narrative-traces")
232
252
  approved_dir_name = str(config.get("approved_dir") or "test-narratives")
253
+ exclude = _resolve_exclude(resolver, env)
233
254
  walker = _Walker(root, output_dir_name, approved_dir_name)
234
255
  walker.walk()
235
256
  project = _read_project_state(root)
@@ -248,4 +269,7 @@ def build_snapshot(cwd: str, env: Env) -> DoctorSnapshot:
248
269
  pointer_readings=_resolve_pointer_readings(project),
249
270
  catalogue_skill_names=_resolve_catalogue_skill_names(),
250
271
  narrativetrace_version=project_family_version(),
272
+ effective_exclude=exclude[0],
273
+ exclude_source=exclude[1],
274
+ exclude_error=exclude[2],
251
275
  )