narrativetrace 0.1.2__tar.gz → 0.2.0__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.
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/.gitignore +1 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/LICENSE +2 -2
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/PKG-INFO +4 -2
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/README.md +2 -1
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/pyproject.toml +14 -3
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/__init__.py +1 -1
- narrativetrace-0.2.0/src/narrativetrace/_skills/agents/add-narrative-tracing/SKILL.md +154 -0
- narrativetrace-0.2.0/src/narrativetrace/_skills/agents/narrativetrace-doctor/SKILL.md +88 -0
- narrativetrace-0.2.0/src/narrativetrace/_skills/catalogue.json +17 -0
- narrativetrace-0.2.0/src/narrativetrace/_skills/claude/add-narrative-tracing/SKILL.md +156 -0
- narrativetrace-0.2.0/src/narrativetrace/_skills/claude/narrativetrace-doctor/SKILL.md +90 -0
- narrativetrace-0.2.0/src/narrativetrace/doctor/__init__.py +27 -0
- narrativetrace-0.2.0/src/narrativetrace/doctor/cli_bin.py +442 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/doctor/environment.py +43 -6
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/escape.py +2 -2
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/logging_bridge.py +2 -2
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/national_id_shapes.py +1 -1
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/approval.py +5 -5
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/writer.py +16 -22
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/pipeline/__init__.py +2 -3
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/redacted_paths.py +15 -18
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/value_delta.py +1 -1
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/rendering.py +387 -85
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/secret_value_shapes.py +2 -1
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/signature.py +2 -2
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/span.py +2 -2
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/template.py +67 -33
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/trace_object.py +10 -3
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/tree_walk.py +2 -2
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/conftest.py +21 -1
- narrativetrace-0.2.0/tests/doctor/test_cli_built_wheels.py +223 -0
- narrativetrace-0.2.0/tests/doctor/test_cli_installer_arguments.py +226 -0
- narrativetrace-0.2.0/tests/doctor/test_cli_installer_verbs.py +796 -0
- narrativetrace-0.2.0/tests/doctor/test_doctor_cli_bin.py +181 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/doctor/test_doctor_environment.py +78 -13
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_chapter.py +6 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_check_no_license_headers.py +48 -23
- narrativetrace-0.2.0/tests/test_comment_hygiene.py +175 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_context_extra.py +8 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_contract_check.py +2 -3
- narrativetrace-0.2.0/tests/test_contract_dispatch_coverage.py +70 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_contract_lint.py +14 -65
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_coverage_source_completeness.py +3 -2
- narrativetrace-0.2.0/tests/test_deep_fixture_budget.py +251 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_distribution_licensing.py +51 -7
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_export.py +8 -0
- narrativetrace-0.2.0/tests/test_installer_from_another_package.py +181 -0
- narrativetrace-0.2.0/tests/test_mutation_baseline_selection.py +57 -0
- narrativetrace-0.2.0/tests/test_mutation_workers.py +135 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_no_poison_contract.py +28 -6
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_output.py +8 -0
- narrativetrace-0.2.0/tests/test_publish_public_host_venv.py +134 -0
- narrativetrace-0.2.0/tests/test_publish_workflow_packages.py +47 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_redacted_paths.py +22 -7
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_render.py +16 -0
- narrativetrace-0.2.0/tests/test_render_reads_state.py +544 -0
- narrativetrace-0.2.0/tests/test_render_reentrancy.py +264 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_rendering.py +92 -32
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_rendering_extra.py +23 -5
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_run_security_tool.py +4 -4
- narrativetrace-0.2.0/tests/test_skills_carrier_wheel_contents.py +104 -0
- narrativetrace-0.2.0/tests/test_skills_render.py +120 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_smoke.py +1 -1
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_snippet_check.py +92 -7
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_template.py +71 -33
- narrativetrace-0.2.0/tests/test_timeout_floor.py +167 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_trace_object.py +30 -4
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_tree.py +15 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_tree_canonical.py +6 -0
- narrativetrace-0.2.0/tests/test_tree_writes_guard.py +407 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_value_reference.py +8 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all_heavy.py +4 -4
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all_security.py +85 -1
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_publication_registry.py +57 -0
- narrativetrace-0.2.0/tests/test_version_literals.py +419 -0
- narrativetrace-0.2.0/tests/test_version_literals_props.py +70 -0
- narrativetrace-0.2.0/tests/wheel_support.py +86 -0
- narrativetrace-0.1.2/src/narrativetrace/doctor/__init__.py +0 -22
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/approval_traces.py +0 -37
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/llms_before_you_start.py +0 -46
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/not_traced_unused.py +0 -64
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/output_env.py +0 -52
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/package_versions.py +0 -51
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/parameter_names.py +0 -46
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/pytest_plugin_registered.py +0 -82
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/pytest_version.py +0 -50
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/python_version.py +0 -38
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/redaction_proof.py +0 -40
- narrativetrace-0.1.2/src/narrativetrace/doctor/checks/unknown_config_keys.py +0 -51
- narrativetrace-0.1.2/src/narrativetrace/doctor/cli_bin.py +0 -115
- narrativetrace-0.1.2/src/narrativetrace/doctor/doc_urls.py +0 -31
- narrativetrace-0.1.2/src/narrativetrace/doctor/doctor.py +0 -43
- narrativetrace-0.1.2/src/narrativetrace/doctor/finding.py +0 -21
- narrativetrace-0.1.2/src/narrativetrace/doctor/render.py +0 -48
- narrativetrace-0.1.2/src/narrativetrace/doctor/types.py +0 -89
- narrativetrace-0.1.2/src/narrativetrace/doctor/version_lite.py +0 -91
- narrativetrace-0.1.2/tests/doctor/conftest.py +0 -58
- narrativetrace-0.1.2/tests/doctor/snapshot_factory_types.py +0 -18
- narrativetrace-0.1.2/tests/doctor/test_check_approval_traces.py +0 -48
- narrativetrace-0.1.2/tests/doctor/test_check_llms_before_you_start.py +0 -36
- narrativetrace-0.1.2/tests/doctor/test_check_not_traced_unused.py +0 -60
- narrativetrace-0.1.2/tests/doctor/test_check_output_env.py +0 -48
- narrativetrace-0.1.2/tests/doctor/test_check_package_versions.py +0 -59
- narrativetrace-0.1.2/tests/doctor/test_check_parameter_names.py +0 -29
- narrativetrace-0.1.2/tests/doctor/test_check_pytest_plugin_registered.py +0 -80
- narrativetrace-0.1.2/tests/doctor/test_check_pytest_version.py +0 -59
- narrativetrace-0.1.2/tests/doctor/test_check_python_version.py +0 -47
- narrativetrace-0.1.2/tests/doctor/test_check_redaction_proof.py +0 -46
- narrativetrace-0.1.2/tests/doctor/test_check_unknown_config_keys.py +0 -31
- narrativetrace-0.1.2/tests/doctor/test_doctor_cli_bin.py +0 -130
- narrativetrace-0.1.2/tests/doctor/test_doctor_finding.py +0 -33
- narrativetrace-0.1.2/tests/doctor/test_doctor_registry.py +0 -51
- narrativetrace-0.1.2/tests/doctor/test_doctor_render.py +0 -53
- narrativetrace-0.1.2/tests/doctor/test_doctor_version_lite.py +0 -81
- narrativetrace-0.1.2/tests/test_llms_banner.py +0 -325
- narrativetrace-0.1.2/tests/test_skills_render.py +0 -45
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/_boundary.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/canonical.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/chapter.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/cli.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/concurrency.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/config.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/context.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/context_export.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/decorators.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/events.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/export.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/groups.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/identity.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/ids.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/levels.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/loss.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/markers.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/metadata.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/namer.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/nodes.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/outcomes.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/__init__.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/artifact_identity.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/line_diff.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/manifest.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/paths.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/reporter.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/run_identity.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/structural_delta.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/output/warnings.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/pipeline/bounded_buffer.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/pipeline/buffered_consumer.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/pipeline/dual_path.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/pipeline/event_store.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/pipeline/perishable.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/pipeline/subscriber.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/py.typed +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/redaction.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/__init__.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/base.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/camel.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/concurrency.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/frontmatter.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/indented.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/markdown.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/prose.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/scenario.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/scenario_result.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/structural.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/render/value_reference.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/tree.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/tree_canonical.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/src/narrativetrace/values.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/fixtures/java-negative-expense.nt +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/live_annotation_support.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/stress_support.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_approval.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_artifact_identity.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_async_concurrency_kind.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_async_lifecycle_contract.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_async_snapshot_adoption.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_bench_capture.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_bench_redaction.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_bench_rendering.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_canonical.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_canonical_props.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_capture_identity.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_chapter_props.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_cli.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_concurrency_info.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_config.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_console_delta.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_context.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_context_export.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_context_stress.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_decorators.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_duplication_check.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_duplication_report.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_escape.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_events.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_fuzz_report.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_groups.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_identity.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_identity_conformance.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_ids.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_levels.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_live_snapshot_scope_visibility.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_logging_bridge.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_manifest.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_metadata.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_mutation_accounting.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_mutation_gate.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_mutation_tooling.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_namer.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_national_id_shapes.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_nested_chain_adoption.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_nodes.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_outcomes.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_parameter_name_redaction.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_pipeline_buffered.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_pipeline_dual_path.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_pipeline_perishable.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_pipeline_props.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_pipeline_stress.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_pipeline_subscriber.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_publish_agents_md_section.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_publish_public.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_quality_gate_status.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_redacted_value_containment.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_redaction.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_run_identity.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_scenario_result.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_secret_value_shapes.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_signature.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_span.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_structlog_dependency.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_structural.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_structural_conformance.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_structural_delta.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_trace_loss.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_trace_stack_live_child.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_translation_check.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_tree_props.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_tree_walk.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_value_delta.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_values.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all_arch_stress.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all_exec.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all_schema.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all_static.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_all_testrun.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_publication_packages.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_verify_publication_provenance.py +0 -0
- {narrativetrace-0.1.2 → narrativetrace-0.2.0}/tests/test_writer_structural.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.
|
|
10
|
+
Licensed Work: NarrativeTrace for Python (the narrativetrace PyPI distributions) version 0.2.0. 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-
|
|
21
|
+
Change Date: Four years from the date the Licensed Work is published. (2030-10-05 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.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -12,11 +12,13 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Typing :: Typed
|
|
14
14
|
Requires-Python: >=3.12
|
|
15
|
+
Requires-Dist: narrativetrace-tooling
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
17
18
|
# narrativetrace
|
|
18
19
|
|
|
19
|
-
Human- and LLM-readable narrative execution tracing for Python.
|
|
20
|
+
Human- and LLM-readable narrative execution tracing for Python. The core, with no third-party
|
|
21
|
+
dependencies.
|
|
20
22
|
|
|
21
23
|
Part of the [narrativetrace](../../README.md) workspace. The
|
|
22
24
|
[Feature Guide](../../documentation/feature-guide.md) carries scope and status.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# narrativetrace
|
|
2
2
|
|
|
3
|
-
Human- and LLM-readable narrative execution tracing for Python.
|
|
3
|
+
Human- and LLM-readable narrative execution tracing for Python. The core, with no third-party
|
|
4
|
+
dependencies.
|
|
4
5
|
|
|
5
6
|
Part of the [narrativetrace](../../README.md) workspace. The
|
|
6
7
|
[Feature Guide](../../documentation/feature-guide.md) carries scope and status.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "narrativetrace"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Narrative execution tracing for Python — human- and LLM-readable traces"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -16,7 +16,11 @@ classifiers = [
|
|
|
16
16
|
"Programming Language :: Python :: 3.12",
|
|
17
17
|
"Typing :: Typed",
|
|
18
18
|
]
|
|
19
|
-
|
|
19
|
+
# One dependency, and it carries no further ones: narrativetrace-tooling is this family's own
|
|
20
|
+
# standard-library-only library (its architecture test pins that), holding the doctor's checks and
|
|
21
|
+
# the agent-skills installer behind the `narrativetrace` console script declared below. No
|
|
22
|
+
# third-party code reaches a consumer's environment through this distribution.
|
|
23
|
+
dependencies = ["narrativetrace-tooling"]
|
|
20
24
|
|
|
21
25
|
[project.urls]
|
|
22
26
|
Homepage = "https://github.com/narrativetrace/narrativetrace-python"
|
|
@@ -41,7 +45,14 @@ packages = ["src/narrativetrace"]
|
|
|
41
45
|
[tool.mutmut]
|
|
42
46
|
source_paths = ["src/narrativetrace/"]
|
|
43
47
|
also_copy = ["tests/"]
|
|
44
|
-
|
|
48
|
+
# "not distribution" deselects the tests marked `distribution`: they build real wheels/sdists via
|
|
49
|
+
# `uv build`, which an offline mutation run cannot do once its build backend is not already
|
|
50
|
+
# cached -- a distribution test proves packaging, not code under mutation, and has no place in
|
|
51
|
+
# this baseline (it still runs in the ordinary test suite). "not stress" is restated here, not
|
|
52
|
+
# just "not distribution" added, because pytest keeps only the LAST -m it sees rather than
|
|
53
|
+
# combining several -m flags -- a bare "not distribution" here would silently override the root
|
|
54
|
+
# addopts' own "not stress" and let the concurrency stress suite back into every mutation run.
|
|
55
|
+
pytest_add_cli_args_test_selection = ["tests/", "-m", "not stress and not distribution"]
|
|
45
56
|
# The plugin distribution imports narrativetrace at plugin-load time, before the mutated copy is on
|
|
46
57
|
# sys.path — loading it would test the installed package instead of the mutant.
|
|
47
58
|
pytest_add_cli_args = ["-p", "no:narrativetrace"]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-narrative-tracing
|
|
3
|
+
description: "Installs NarrativeTrace into a Python project and gets it to a first trace. Use when NarrativeTrace is not yet installed, a project needs its very first traced call, or traces need to reach a real logger instead of bare print statements. Installs narrativetrace with uv add, wraps an object with trace_object, renders and runs the first trace, then wires the stdlib logging bridge so traces reach your logger. Runs narrativetrace doctor to confirm the install is correctly wired -- narrativetrace-doctor owns diagnosis from there -- and previews the agent-skills install so the next session finds them. Say 'add narrative tracing to my service', 'install narrativetrace', 'get a trace in 60 seconds', 'wrap this object so I can see a trace', or 'send my traces to my logger' to invoke it."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# add-narrative-tracing
|
|
7
|
+
|
|
8
|
+
## 1. Install with the real toolchain
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uv sync --all-packages
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
15
|
+
report = json.load(sys.stdin)
|
|
16
|
+
bad = [f for f in report["findings"] if f["id"].startswith("toolchain.") and f["status"] != "pass"]
|
|
17
|
+
if bad:
|
|
18
|
+
print(json.dumps(bad))
|
|
19
|
+
sys.exit(1 if bad else 0)
|
|
20
|
+
'`
|
|
21
|
+
|
|
22
|
+
**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
|
+
|
|
24
|
+
## 2. First trace: wrap, call, render, run
|
|
25
|
+
|
|
26
|
+
<!-- snippet: examples/sixty_seconds/main.py -->
|
|
27
|
+
```python
|
|
28
|
+
# main.py
|
|
29
|
+
from narrativetrace import ContextVarNarrativeContext, IndentedTextRenderer, TraceId, trace_object
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class OrderService:
|
|
33
|
+
def place_order(self, customer_id, product_id, quantity):
|
|
34
|
+
return f"ORD-{customer_id}-{product_id}-{quantity}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# snippet:begin fixedTraceId
|
|
38
|
+
# A fixed trace id, adopted so this page's embedded output always names the same trace. A real
|
|
39
|
+
# run generates a random one every time (never this -- it is this DEMO's own constant, not the
|
|
40
|
+
# library default) via the same TraceId.adopt_trace_id a servlet-style boundary uses for an
|
|
41
|
+
# inbound trace header.
|
|
42
|
+
DEMO_TRACE_ID = TraceId("a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4")
|
|
43
|
+
|
|
44
|
+
# snippet:end fixedTraceId
|
|
45
|
+
|
|
46
|
+
context = ContextVarNarrativeContext()
|
|
47
|
+
context.adopt_trace_id(DEMO_TRACE_ID)
|
|
48
|
+
service = trace_object(OrderService(), context)
|
|
49
|
+
service.place_order("cust-1", "prod-42", 3)
|
|
50
|
+
|
|
51
|
+
print(IndentedTextRenderer().render(context.capture_trace()))
|
|
52
|
+
```
|
|
53
|
+
<!-- /snippet -->
|
|
54
|
+
|
|
55
|
+
**verify:** `uv run python main.py`
|
|
56
|
+
|
|
57
|
+
**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
|
|
58
|
+
|
|
59
|
+
## 3. Send it to your logger
|
|
60
|
+
|
|
61
|
+
<!-- snippet: examples/sixty_seconds/main_with_logger.py -->
|
|
62
|
+
```python
|
|
63
|
+
# main.py
|
|
64
|
+
import logging # new: stdlib logging -- the sink this step sends the trace to
|
|
65
|
+
import sys # new: stdout target for the handler below
|
|
66
|
+
|
|
67
|
+
from narrativetrace import (
|
|
68
|
+
ContextVarNarrativeContext,
|
|
69
|
+
IndentedTextRenderer,
|
|
70
|
+
NarrativeContextFilter, # new: injects traceName/runName onto every log record
|
|
71
|
+
TraceId,
|
|
72
|
+
export_to_logger, # new: replays an already-captured trace through your logger, one call
|
|
73
|
+
trace_object,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class OrderService:
|
|
78
|
+
def place_order(self, customer_id, product_id, quantity):
|
|
79
|
+
return f"ORD-{customer_id}-{product_id}-{quantity}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# snippet:begin fixedTraceId
|
|
83
|
+
# A fixed trace id, adopted so this page's embedded output always names the same trace. A real
|
|
84
|
+
# run generates a random one every time (never this -- it is this DEMO's own constant, not the
|
|
85
|
+
# library default) via the same TraceId.adopt_trace_id a servlet-style boundary uses for an
|
|
86
|
+
# inbound trace header.
|
|
87
|
+
DEMO_TRACE_ID = TraceId("a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4")
|
|
88
|
+
|
|
89
|
+
# snippet:end fixedTraceId
|
|
90
|
+
|
|
91
|
+
# new: a plain stdlib logging setup -- the shape a real app's own logging config already has
|
|
92
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
93
|
+
handler.addFilter(NarrativeContextFilter()) # new: makes traceName/runName available below
|
|
94
|
+
# new: DEBUG so export_to_logger's records pass the handler; the format reads the filter's keys
|
|
95
|
+
logging.basicConfig(
|
|
96
|
+
level=logging.DEBUG, format="[%(traceName)s] [%(runName)s] %(message)s", handlers=[handler]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
context = ContextVarNarrativeContext()
|
|
100
|
+
context.adopt_trace_id(DEMO_TRACE_ID)
|
|
101
|
+
service = trace_object(OrderService(), context)
|
|
102
|
+
service.place_order("cust-1", "prod-42", 3)
|
|
103
|
+
|
|
104
|
+
trace = context.capture_trace() # new: capture once, reuse for both the print and the export
|
|
105
|
+
print(IndentedTextRenderer().render(trace))
|
|
106
|
+
|
|
107
|
+
export_to_logger(trace) # new: sends the same captured trace through the configured logger
|
|
108
|
+
```
|
|
109
|
+
<!-- /snippet -->
|
|
110
|
+
|
|
111
|
+
**verify:** `uv run python main_with_logger.py`
|
|
112
|
+
|
|
113
|
+
## 4. Run the doctor and resolve its findings
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv run narrativetrace doctor || true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
120
|
+
report = json.load(sys.stdin)
|
|
121
|
+
findings = report.get("findings")
|
|
122
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 12 else 0)
|
|
123
|
+
'`
|
|
124
|
+
|
|
125
|
+
## 5. Install the skills for next time
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv run narrativetrace init --dry-run
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**verify:** `uv run python -c 'import pathlib, subprocess, sys
|
|
132
|
+
result = subprocess.run(
|
|
133
|
+
["uv", "run", "narrativetrace", "init", "--dry-run"], capture_output=True, text=True
|
|
134
|
+
)
|
|
135
|
+
shown = (
|
|
136
|
+
"action(s)" in result.stdout
|
|
137
|
+
and ".agents/skills" in result.stdout
|
|
138
|
+
and "AGENTS.md" in result.stdout
|
|
139
|
+
)
|
|
140
|
+
untouched = not pathlib.Path(".agents/skills").exists() and not pathlib.Path("AGENTS.md").exists()
|
|
141
|
+
sys.exit(0 if result.returncode == 0 and shown and untouched else 1)
|
|
142
|
+
'`
|
|
143
|
+
|
|
144
|
+
**failure:** the command exits 1 with no carrier resolved — no narrativetrace-skills distribution is installed and this project's own narrativetrace bundles no reachable fallback. Fix: run `uv add narrativetrace-skills`, or point --from at a local carrier directory or wheel
|
|
145
|
+
|
|
146
|
+
## Always
|
|
147
|
+
|
|
148
|
+
- 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)
|
|
149
|
+
|
|
150
|
+
## Never
|
|
151
|
+
|
|
152
|
+
- Never assume a step worked without running its verify. (self-reported success overstates reality -- a build claimed green that does not reproduce from clean is not evidence)
|
|
153
|
+
- Never skip the final narrativetrace doctor call. (it is the seam that catches anything these four steps did not -- narrativetrace-doctor owns diagnosis from here)
|
|
154
|
+
- Never apply the installer without showing its diff first. (it writes into AGENTS.md and the project's skill directories, and the approval for that is a person reading the diff -- run it with --dry-run, show the output, and let them run it again without the flag)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
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, and stale approval-trace diffs. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# narrativetrace-doctor
|
|
7
|
+
|
|
8
|
+
## 1. Run the doctor and read its report
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uv run narrativetrace doctor || true
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
15
|
+
report = json.load(sys.stdin)
|
|
16
|
+
findings = report.get("findings")
|
|
17
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 12 else 0)
|
|
18
|
+
'`
|
|
19
|
+
|
|
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
|
|
21
|
+
|
|
22
|
+
## 2. Prove redaction in a test
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv run python -c 'print(
|
|
26
|
+
"Render a call with a deny-listed parameter name (e.g. password or token) in a test and "
|
|
27
|
+
"assert the output contains [REDACTED], and that a neighboring non-sensitive value is still "
|
|
28
|
+
"present."
|
|
29
|
+
)
|
|
30
|
+
'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
34
|
+
report = json.load(sys.stdin)
|
|
35
|
+
by_id = {f["id"]: f for f in report["findings"]}
|
|
36
|
+
finding = by_id.get("trap.redaction-proof")
|
|
37
|
+
sys.exit(1 if finding is None or finding["status"] not in ("pass", "fail") else 0)
|
|
38
|
+
'`
|
|
39
|
+
|
|
40
|
+
**failure:** not_traced_field/__nt_not_traced__ is present but never applied — trusting redaction by inspection instead of proving it in a test. Fix: render a call with a deny-listed parameter name and assert the output contains "[REDACTED]"
|
|
41
|
+
|
|
42
|
+
## 3. Read the rendered trace before asserting
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv run python -c 'import pathlib
|
|
46
|
+
root = pathlib.Path("narrative-traces")
|
|
47
|
+
files = sorted(root.rglob("*.md"), key=lambda p: p.stat().st_mtime) if root.is_dir() else []
|
|
48
|
+
if not files:
|
|
49
|
+
print("no rendered .md file found yet under narrative-traces -- run your tests or app once")
|
|
50
|
+
else:
|
|
51
|
+
newest = files[-1]
|
|
52
|
+
print(newest)
|
|
53
|
+
print(newest.read_text(encoding="utf-8"))
|
|
54
|
+
'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 4. Approval flow: diff the structural trace, not just values
|
|
58
|
+
|
|
59
|
+
**Flagged:** unstudied -- eval cell pending
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv run python -c 'import pathlib
|
|
63
|
+
root = pathlib.Path("test-narratives")
|
|
64
|
+
received = sorted(root.rglob("*.received.nt")) if root.is_dir() else []
|
|
65
|
+
if not received:
|
|
66
|
+
print("no approval traces configured yet under test-narratives -- nothing pending")
|
|
67
|
+
else:
|
|
68
|
+
newest = received[-1]
|
|
69
|
+
approved = newest.with_name(newest.name.replace(".received.nt", ".approved.nt"))
|
|
70
|
+
print("received: " + str(newest))
|
|
71
|
+
if approved.is_file():
|
|
72
|
+
print("approved: " + str(approved))
|
|
73
|
+
print(approved.read_text(encoding="utf-8"))
|
|
74
|
+
print("--- vs received ---")
|
|
75
|
+
else:
|
|
76
|
+
print("no .approved.nt yet -- first approval, review then promote")
|
|
77
|
+
print(newest.read_text(encoding="utf-8"))
|
|
78
|
+
'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Always
|
|
82
|
+
|
|
83
|
+
- Run the doctor CLI and read its report before making any change. (the tested tooling already computed the finding -- re-deriving it by hand risks disagreeing with what ships)
|
|
84
|
+
|
|
85
|
+
## Never
|
|
86
|
+
|
|
87
|
+
- Never have this skill edit, generate, or delete a file. (narrativetrace-doctor is scoped read-only by design -- generation of the redaction-proof test itself is a separate, later skill)
|
|
88
|
+
- Never claim a finding passed without having run the doctor CLI in this session. (self-reported success overstates reality -- a build claimed green that does not reproduce from clean is not evidence; verify is never 'ask the agent')
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"runtime": "python",
|
|
3
|
+
"skills": [
|
|
4
|
+
{
|
|
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, and stale approval-trace diffs. Read-only -- makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it.",
|
|
7
|
+
"agents": "agents/narrativetrace-doctor/SKILL.md",
|
|
8
|
+
"claude": "claude/narrativetrace-doctor/SKILL.md"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "add-narrative-tracing",
|
|
12
|
+
"description": "Installs NarrativeTrace into a Python project and gets it to a first trace. Use when NarrativeTrace is not yet installed, a project needs its very first traced call, or traces need to reach a real logger instead of bare print statements. Installs narrativetrace with uv add, wraps an object with trace_object, renders and runs the first trace, then wires the stdlib logging bridge so traces reach your logger. Runs narrativetrace doctor to confirm the install is correctly wired -- narrativetrace-doctor owns diagnosis from there -- and previews the agent-skills install so the next session finds them. Say 'add narrative tracing to my service', 'install narrativetrace', 'get a trace in 60 seconds', 'wrap this object so I can see a trace', or 'send my traces to my logger' to invoke it.",
|
|
13
|
+
"agents": "agents/add-narrative-tracing/SKILL.md",
|
|
14
|
+
"claude": "claude/add-narrative-tracing/SKILL.md"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-narrative-tracing
|
|
3
|
+
description: "Installs NarrativeTrace into a Python project and gets it to a first trace. Use when NarrativeTrace is not yet installed, a project needs its very first traced call, or traces need to reach a real logger instead of bare print statements. Installs narrativetrace with uv add, wraps an object with trace_object, renders and runs the first trace, then wires the stdlib logging bridge so traces reach your logger. Runs narrativetrace doctor to confirm the install is correctly wired -- narrativetrace-doctor owns diagnosis from there -- and previews the agent-skills install so the next session finds them. Say 'add narrative tracing to my service', 'install narrativetrace', 'get a trace in 60 seconds', 'wrap this object so I can see a trace', or 'send my traces to my logger' to invoke it."
|
|
4
|
+
when_to_use: "A project does not have NarrativeTrace yet, or has the package installed but has never produced a trace, or traces print to the console but nothing forwards them to a real logger."
|
|
5
|
+
allowed-tools: uv, git
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# add-narrative-tracing
|
|
9
|
+
|
|
10
|
+
## 1. Install with the real toolchain
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv sync --all-packages
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
17
|
+
report = json.load(sys.stdin)
|
|
18
|
+
bad = [f for f in report["findings"] if f["id"].startswith("toolchain.") and f["status"] != "pass"]
|
|
19
|
+
if bad:
|
|
20
|
+
print(json.dumps(bad))
|
|
21
|
+
sys.exit(1 if bad else 0)
|
|
22
|
+
'`
|
|
23
|
+
|
|
24
|
+
**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
|
+
|
|
26
|
+
## 2. First trace: wrap, call, render, run
|
|
27
|
+
|
|
28
|
+
<!-- snippet: examples/sixty_seconds/main.py -->
|
|
29
|
+
```python
|
|
30
|
+
# main.py
|
|
31
|
+
from narrativetrace import ContextVarNarrativeContext, IndentedTextRenderer, TraceId, trace_object
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class OrderService:
|
|
35
|
+
def place_order(self, customer_id, product_id, quantity):
|
|
36
|
+
return f"ORD-{customer_id}-{product_id}-{quantity}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# snippet:begin fixedTraceId
|
|
40
|
+
# A fixed trace id, adopted so this page's embedded output always names the same trace. A real
|
|
41
|
+
# run generates a random one every time (never this -- it is this DEMO's own constant, not the
|
|
42
|
+
# library default) via the same TraceId.adopt_trace_id a servlet-style boundary uses for an
|
|
43
|
+
# inbound trace header.
|
|
44
|
+
DEMO_TRACE_ID = TraceId("a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4")
|
|
45
|
+
|
|
46
|
+
# snippet:end fixedTraceId
|
|
47
|
+
|
|
48
|
+
context = ContextVarNarrativeContext()
|
|
49
|
+
context.adopt_trace_id(DEMO_TRACE_ID)
|
|
50
|
+
service = trace_object(OrderService(), context)
|
|
51
|
+
service.place_order("cust-1", "prod-42", 3)
|
|
52
|
+
|
|
53
|
+
print(IndentedTextRenderer().render(context.capture_trace()))
|
|
54
|
+
```
|
|
55
|
+
<!-- /snippet -->
|
|
56
|
+
|
|
57
|
+
**verify:** `uv run python main.py`
|
|
58
|
+
|
|
59
|
+
**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
|
|
60
|
+
|
|
61
|
+
## 3. Send it to your logger
|
|
62
|
+
|
|
63
|
+
<!-- snippet: examples/sixty_seconds/main_with_logger.py -->
|
|
64
|
+
```python
|
|
65
|
+
# main.py
|
|
66
|
+
import logging # new: stdlib logging -- the sink this step sends the trace to
|
|
67
|
+
import sys # new: stdout target for the handler below
|
|
68
|
+
|
|
69
|
+
from narrativetrace import (
|
|
70
|
+
ContextVarNarrativeContext,
|
|
71
|
+
IndentedTextRenderer,
|
|
72
|
+
NarrativeContextFilter, # new: injects traceName/runName onto every log record
|
|
73
|
+
TraceId,
|
|
74
|
+
export_to_logger, # new: replays an already-captured trace through your logger, one call
|
|
75
|
+
trace_object,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class OrderService:
|
|
80
|
+
def place_order(self, customer_id, product_id, quantity):
|
|
81
|
+
return f"ORD-{customer_id}-{product_id}-{quantity}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# snippet:begin fixedTraceId
|
|
85
|
+
# A fixed trace id, adopted so this page's embedded output always names the same trace. A real
|
|
86
|
+
# run generates a random one every time (never this -- it is this DEMO's own constant, not the
|
|
87
|
+
# library default) via the same TraceId.adopt_trace_id a servlet-style boundary uses for an
|
|
88
|
+
# inbound trace header.
|
|
89
|
+
DEMO_TRACE_ID = TraceId("a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4")
|
|
90
|
+
|
|
91
|
+
# snippet:end fixedTraceId
|
|
92
|
+
|
|
93
|
+
# new: a plain stdlib logging setup -- the shape a real app's own logging config already has
|
|
94
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
95
|
+
handler.addFilter(NarrativeContextFilter()) # new: makes traceName/runName available below
|
|
96
|
+
# new: DEBUG so export_to_logger's records pass the handler; the format reads the filter's keys
|
|
97
|
+
logging.basicConfig(
|
|
98
|
+
level=logging.DEBUG, format="[%(traceName)s] [%(runName)s] %(message)s", handlers=[handler]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
context = ContextVarNarrativeContext()
|
|
102
|
+
context.adopt_trace_id(DEMO_TRACE_ID)
|
|
103
|
+
service = trace_object(OrderService(), context)
|
|
104
|
+
service.place_order("cust-1", "prod-42", 3)
|
|
105
|
+
|
|
106
|
+
trace = context.capture_trace() # new: capture once, reuse for both the print and the export
|
|
107
|
+
print(IndentedTextRenderer().render(trace))
|
|
108
|
+
|
|
109
|
+
export_to_logger(trace) # new: sends the same captured trace through the configured logger
|
|
110
|
+
```
|
|
111
|
+
<!-- /snippet -->
|
|
112
|
+
|
|
113
|
+
**verify:** `uv run python main_with_logger.py`
|
|
114
|
+
|
|
115
|
+
## 4. Run the doctor and resolve its findings
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv run narrativetrace doctor || true
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
122
|
+
report = json.load(sys.stdin)
|
|
123
|
+
findings = report.get("findings")
|
|
124
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 12 else 0)
|
|
125
|
+
'`
|
|
126
|
+
|
|
127
|
+
## 5. Install the skills for next time
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
uv run narrativetrace init --dry-run
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**verify:** `uv run python -c 'import pathlib, subprocess, sys
|
|
134
|
+
result = subprocess.run(
|
|
135
|
+
["uv", "run", "narrativetrace", "init", "--dry-run"], capture_output=True, text=True
|
|
136
|
+
)
|
|
137
|
+
shown = (
|
|
138
|
+
"action(s)" in result.stdout
|
|
139
|
+
and ".agents/skills" in result.stdout
|
|
140
|
+
and "AGENTS.md" in result.stdout
|
|
141
|
+
)
|
|
142
|
+
untouched = not pathlib.Path(".agents/skills").exists() and not pathlib.Path("AGENTS.md").exists()
|
|
143
|
+
sys.exit(0 if result.returncode == 0 and shown and untouched else 1)
|
|
144
|
+
'`
|
|
145
|
+
|
|
146
|
+
**failure:** the command exits 1 with no carrier resolved — no narrativetrace-skills distribution is installed and this project's own narrativetrace bundles no reachable fallback. Fix: run `uv add narrativetrace-skills`, or point --from at a local carrier directory or wheel
|
|
147
|
+
|
|
148
|
+
## Always
|
|
149
|
+
|
|
150
|
+
- 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)
|
|
151
|
+
|
|
152
|
+
## Never
|
|
153
|
+
|
|
154
|
+
- Never assume a step worked without running its verify. (self-reported success overstates reality -- a build claimed green that does not reproduce from clean is not evidence)
|
|
155
|
+
- Never skip the final narrativetrace doctor call. (it is the seam that catches anything these four steps did not -- narrativetrace-doctor owns diagnosis from here)
|
|
156
|
+
- Never apply the installer without showing its diff first. (it writes into AGENTS.md and the project's skill directories, and the approval for that is a person reading the diff -- run it with --dry-run, show the output, and let them run it again without the flag)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
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, and stale approval-trace diffs. 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."
|
|
5
|
+
allowed-tools: uv, git
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# narrativetrace-doctor
|
|
9
|
+
|
|
10
|
+
## 1. Run the doctor and read its report
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv run narrativetrace doctor || true
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
17
|
+
report = json.load(sys.stdin)
|
|
18
|
+
findings = report.get("findings")
|
|
19
|
+
sys.exit(1 if not isinstance(findings, list) or len(findings) != 12 else 0)
|
|
20
|
+
'`
|
|
21
|
+
|
|
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
|
|
23
|
+
|
|
24
|
+
## 2. Prove redaction in a test
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv run python -c 'print(
|
|
28
|
+
"Render a call with a deny-listed parameter name (e.g. password or token) in a test and "
|
|
29
|
+
"assert the output contains [REDACTED], and that a neighboring non-sensitive value is still "
|
|
30
|
+
"present."
|
|
31
|
+
)
|
|
32
|
+
'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**verify:** `uv run narrativetrace doctor --json | uv run python -c 'import json, sys
|
|
36
|
+
report = json.load(sys.stdin)
|
|
37
|
+
by_id = {f["id"]: f for f in report["findings"]}
|
|
38
|
+
finding = by_id.get("trap.redaction-proof")
|
|
39
|
+
sys.exit(1 if finding is None or finding["status"] not in ("pass", "fail") else 0)
|
|
40
|
+
'`
|
|
41
|
+
|
|
42
|
+
**failure:** not_traced_field/__nt_not_traced__ is present but never applied — trusting redaction by inspection instead of proving it in a test. Fix: render a call with a deny-listed parameter name and assert the output contains "[REDACTED]"
|
|
43
|
+
|
|
44
|
+
## 3. Read the rendered trace before asserting
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv run python -c 'import pathlib
|
|
48
|
+
root = pathlib.Path("narrative-traces")
|
|
49
|
+
files = sorted(root.rglob("*.md"), key=lambda p: p.stat().st_mtime) if root.is_dir() else []
|
|
50
|
+
if not files:
|
|
51
|
+
print("no rendered .md file found yet under narrative-traces -- run your tests or app once")
|
|
52
|
+
else:
|
|
53
|
+
newest = files[-1]
|
|
54
|
+
print(newest)
|
|
55
|
+
print(newest.read_text(encoding="utf-8"))
|
|
56
|
+
'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 4. Approval flow: diff the structural trace, not just values
|
|
60
|
+
|
|
61
|
+
**Flagged:** unstudied -- eval cell pending
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv run python -c 'import pathlib
|
|
65
|
+
root = pathlib.Path("test-narratives")
|
|
66
|
+
received = sorted(root.rglob("*.received.nt")) if root.is_dir() else []
|
|
67
|
+
if not received:
|
|
68
|
+
print("no approval traces configured yet under test-narratives -- nothing pending")
|
|
69
|
+
else:
|
|
70
|
+
newest = received[-1]
|
|
71
|
+
approved = newest.with_name(newest.name.replace(".received.nt", ".approved.nt"))
|
|
72
|
+
print("received: " + str(newest))
|
|
73
|
+
if approved.is_file():
|
|
74
|
+
print("approved: " + str(approved))
|
|
75
|
+
print(approved.read_text(encoding="utf-8"))
|
|
76
|
+
print("--- vs received ---")
|
|
77
|
+
else:
|
|
78
|
+
print("no .approved.nt yet -- first approval, review then promote")
|
|
79
|
+
print(newest.read_text(encoding="utf-8"))
|
|
80
|
+
'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Always
|
|
84
|
+
|
|
85
|
+
- Run the doctor CLI and read its report before making any change. (the tested tooling already computed the finding -- re-deriving it by hand risks disagreeing with what ships)
|
|
86
|
+
|
|
87
|
+
## Never
|
|
88
|
+
|
|
89
|
+
- Never have this skill edit, generate, or delete a file. (narrativetrace-doctor is scoped read-only by design -- generation of the redaction-proof test itself is a separate, later skill)
|
|
90
|
+
- Never claim a finding passed without having run the doctor CLI in this session. (self-reported success overstates reality -- a build claimed green that does not reproduce from clean is not evidence; verify is never 'ask the agent')
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
"""``narrativetrace doctor``'s one impure half, and the console script every verb goes through.
|
|
6
|
+
|
|
7
|
+
Every check, the report, both renderings and the whole agent-skills installer live in
|
|
8
|
+
:mod:`narrativetrace_tooling` — the zero-dependency library this distribution's console script
|
|
9
|
+
launches, and which a second entry point can embed without dragging the runtime in with it. What
|
|
10
|
+
stays here is what cannot move:
|
|
11
|
+
|
|
12
|
+
* :mod:`narrativetrace.doctor.environment` — builds the
|
|
13
|
+
:class:`~narrativetrace_tooling.doctor.types.DoctorSnapshot` by walking the real project and the
|
|
14
|
+
installed-distribution metadata. It resolves configuration through
|
|
15
|
+
:class:`narrativetrace.config.ConfigResolver`, the same resolver the runtime itself uses, so the
|
|
16
|
+
doctor reads a project exactly as the runtime does rather than guessing at the precedence again.
|
|
17
|
+
* :mod:`narrativetrace.doctor.cli_bin` — the ``narrativetrace`` console script: verb dispatch for
|
|
18
|
+
``doctor``, ``init`` and ``uninstall``, process exit codes, stdout and stderr. It sits beside the
|
|
19
|
+
doctor's reader rather than higher up because ``doctor`` was its first verb and this module path
|
|
20
|
+
is the one ``[project.scripts]`` names; the launcher itself is about all three.
|
|
21
|
+
|
|
22
|
+
Not part of the package's public API (:mod:`narrativetrace`'s ``__all__``) — reached only through
|
|
23
|
+
the ``narrativetrace`` console script, the same way ``narrativetrace-approve``
|
|
24
|
+
(:mod:`narrativetrace.cli`) is a console-script-only module.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|