agent-tune-kit 2.3.1__tar.gz → 2.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/.codex-plugin/plugin.json +1 -1
  2. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/PKG-INFO +7 -3
  3. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/README.en.md +6 -2
  4. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/README.md +6 -2
  5. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/__init__.py +1 -1
  6. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/trace.py +11 -1
  7. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/visualize.py +8 -2
  8. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/archive.py +3 -1
  9. agent_tune_kit-2.3.2/agent_tune_kit/traces/categories.py +173 -0
  10. agent_tune_kit-2.3.2/agent_tune_kit/traces/progress.py +95 -0
  11. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/records.py +58 -3
  12. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/store.py +1 -1
  13. agent_tune_kit-2.3.2/agent_tune_kit/visualize/assets/shared/base.css +95 -0
  14. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/trace/app.js +95 -38
  15. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/trace/page.html +1 -1
  16. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/trace/styles.css +15 -74
  17. agent_tune_kit-2.3.2/agent_tune_kit/visualize/assets/traces/app.js +403 -0
  18. agent_tune_kit-2.3.2/agent_tune_kit/visualize/assets/traces/page.html +41 -0
  19. agent_tune_kit-2.3.2/agent_tune_kit/visualize/assets/traces/styles.css +87 -0
  20. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/trace.py +76 -33
  21. agent_tune_kit-2.3.2/agent_tune_kit/visualize/traces.py +258 -0
  22. agent_tune_kit-2.3.2/docs/trace-change-verification-readability-diagnosis.md +124 -0
  23. agent_tune_kit-2.3.2/docs/trace-dashboard-design.md +325 -0
  24. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/trace-swimlane-design.md +4 -2
  25. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/trace-tuning-validation.md +27 -0
  26. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/trace-tuning.md +115 -7
  27. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/pyproject.toml +1 -1
  28. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/validate_skill_pack.py +1 -1
  29. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-trace-capture/SKILL.md +5 -2
  30. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-trace-detect/SKILL.md +17 -3
  31. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-trace-diagnose/SKILL.md +6 -3
  32. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-trace-init/SKILL.md +1 -1
  33. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-trace-optimize/SKILL.md +6 -3
  34. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-trace-verify/SKILL.md +6 -3
  35. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-visualize-trace/SKILL.md +11 -7
  36. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_install_plugin.py +1 -1
  37. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_release_scripts.py +1 -1
  38. agent_tune_kit-2.3.2/tests/test_trace_categories.py +217 -0
  39. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_trace_cli.py +7 -0
  40. agent_tune_kit-2.3.2/tests/test_trace_progress.py +132 -0
  41. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_trace_records.py +113 -0
  42. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_visualize_trace.py +54 -3
  43. agent_tune_kit-2.3.2/tests/test_visualize_traces.py +544 -0
  44. agent_tune_kit-2.3.2/tests/trace-dashboard.cjs +259 -0
  45. agent_tune_kit-2.3.2/tests/trace-stage-cards.cjs +117 -0
  46. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/.gitignore +0 -0
  47. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/__init__.py +0 -0
  48. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/__main__.py +0 -0
  49. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/dataset.py +0 -0
  50. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/evaluation.py +0 -0
  51. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/main.py +0 -0
  52. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/output.py +0 -0
  53. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/cli/results.py +0 -0
  54. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/datasets/__init__.py +0 -0
  55. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/datasets/prepare.py +0 -0
  56. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/datasets/truth.py +0 -0
  57. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/datasets/updates.py +0 -0
  58. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/datasets/validation.py +0 -0
  59. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/errors.py +0 -0
  60. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/execution/__init__.py +0 -0
  61. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/execution/calibration.py +0 -0
  62. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/execution/logs.py +0 -0
  63. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/execution/rules.py +0 -0
  64. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/execution/runner.py +0 -0
  65. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/execution/runtime.py +0 -0
  66. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/execution/tracing.py +0 -0
  67. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/installer.py +0 -0
  68. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/results/__init__.py +0 -0
  69. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/results/assessment.py +0 -0
  70. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/results/evidence.py +0 -0
  71. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/results/judgments.py +0 -0
  72. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/results/summary.py +0 -0
  73. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/results/versions.py +0 -0
  74. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/__init__.py +0 -0
  75. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/capture.py +0 -0
  76. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/langfuse.py +0 -0
  77. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/migrate.py +0 -0
  78. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/query.py +0 -0
  79. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/traces/segments.py +0 -0
  80. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/utils/__init__.py +0 -0
  81. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/utils/csv_io.py +0 -0
  82. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/utils/files.py +0 -0
  83. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/__init__.py +0 -0
  84. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/dataset/app.js +0 -0
  85. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/dataset/page.html +0 -0
  86. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/dataset/styles.css +0 -0
  87. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/dataset/vendor/echarts.min.js +0 -0
  88. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/failures/app.js +0 -0
  89. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/failures/page.html +0 -0
  90. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/failures/styles.css +0 -0
  91. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/trace/lanes.js +0 -0
  92. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/trace/vendor/markdown-it.LICENSE +0 -0
  93. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/assets/trace/vendor/markdown-it.min.js +0 -0
  94. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/dataset.py +0 -0
  95. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/failures.py +0 -0
  96. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/fields.py +0 -0
  97. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/history.py +0 -0
  98. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/render.py +0 -0
  99. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/visualize/timeline.py +0 -0
  100. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/agent_tune_kit/workspace.py +0 -0
  101. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/assets/agent_tune_kit_evaluation.png +0 -0
  102. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/assets/agent_tune_kit_trace.png +0 -0
  103. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/assets/arch-en.png +0 -0
  104. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/assets/arch.png +0 -0
  105. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/assets/architecture.en.svg +0 -0
  106. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/assets/architecture.svg +0 -0
  107. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/ato-atk-project-boundary-recommendation-2026-11-05.md +0 -0
  108. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/cli-skill-modularization-plan.md +0 -0
  109. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/codex_agent_tuning_prd.md +0 -0
  110. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/execution-evidence-and-tracing-plan.md +0 -0
  111. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/ground-truth-and-grading-assessment-2026-10-25.md +0 -0
  112. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/local-context.md +0 -0
  113. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/shared-versioning-and-confirmation.md +0 -0
  114. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/skill-template-pack-usage.md +0 -0
  115. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/surginame-atk-capability-review-2026-10-27.md +0 -0
  116. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/docs/truth-and-assessment.md +0 -0
  117. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/check-release.py +0 -0
  118. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/dev-refresh-install.sh +0 -0
  119. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/install_plugin.py +0 -0
  120. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/publish-pypi.sh +0 -0
  121. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/publish-release.py +0 -0
  122. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/release-version.py +0 -0
  123. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/scripts/release-version.sh +0 -0
  124. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-dataset/SKILL.md +0 -0
  125. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-dataset/scripts/update_dataset.py +0 -0
  126. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-diagnose/SKILL.md +0 -0
  127. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-eval/SKILL.md +0 -0
  128. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-init/SKILL.md +0 -0
  129. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-optimize/SKILL.md +0 -0
  130. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-report/SKILL.md +0 -0
  131. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-visualize-dataset/SKILL.md +0 -0
  132. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-visualize-dataset/scripts/generate_dataset_browser.py +0 -0
  133. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-visualize-failures/SKILL.md +0 -0
  134. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/skills/atk-visualize-failures/scripts/generate_failure_browser.py +0 -0
  135. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/templates/.atk/runner/eval_runner.py.md +0 -0
  136. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/templates/.atk/runner/failure_rule.py.md +0 -0
  137. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/templates/.atk/runner/import_evidence.py.md +0 -0
  138. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/templates/.gitkeep +0 -0
  139. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/__init__.py +0 -0
  140. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/conftest.py +0 -0
  141. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/fixtures/legacy_eval_runner.py.txt +0 -0
  142. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/fixtures/managed_v1.py.md +0 -0
  143. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_assessment_contract.py +0 -0
  144. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_atk_init_contract.py +0 -0
  145. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_cli_workflow.py +0 -0
  146. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_dataset_updates.py +0 -0
  147. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_diagnosis.py +0 -0
  148. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_evidence_import.py +0 -0
  149. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_evidence_judgments.py +0 -0
  150. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_evidence_packaging.py +0 -0
  151. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_evidence_versions.py +0 -0
  152. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_execution_evidence.py +0 -0
  153. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_execution_outcomes.py +0 -0
  154. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_execution_tracing.py +0 -0
  155. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_generate_dataset_browser.py +0 -0
  156. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_generate_failure_browser.py +0 -0
  157. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_judge_calibration.py +0 -0
  158. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_local_context.py +0 -0
  159. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_plugin_cache.py +0 -0
  160. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_runner_output_columns.py +0 -0
  161. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_runner_template_row_logging.py +0 -0
  162. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_runtime_contracts.py +0 -0
  163. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_snapshot_reads.py +0 -0
  164. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_trace_capture.py +0 -0
  165. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_trace_langfuse.py +0 -0
  166. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_trace_query.py +0 -0
  167. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_trace_segments.py +0 -0
  168. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_trace_store.py +0 -0
  169. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_truth_browser.py +0 -0
  170. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_truth_cli.py +0 -0
  171. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_truth_contract.py +0 -0
  172. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_truth_runner.py +0 -0
  173. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/test_v2_progress.py +0 -0
  174. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/trace-event-wheel.cjs +0 -0
  175. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/trace-preview-scroll.cjs +0 -0
  176. {agent_tune_kit-2.3.1 → agent_tune_kit-2.3.2}/tests/trace-selection-reason.cjs +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-tune-kit",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "Local Codex plugin for batch Agent evaluation and session trace tuning with evidence-backed issues, focused changes, verification and offline review.",
5
5
  "author": {
6
6
  "name": "hustyichi",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-tune-kit
3
- Version: 2.3.1
3
+ Version: 2.3.2
4
4
  Summary: Local Codex plugin for iterative Agent tuning with guided Skills, reusable runner templates, versioned results, and static validation.
5
5
  Project-URL: Homepage, https://github.com/hustyichi/agent-tune-kit
6
6
  Project-URL: Repository, https://github.com/hustyichi/agent-tune-kit
@@ -165,7 +165,11 @@ $atk-visualize-trace Open the execution lanes for session <session-id> and jump
165
165
 
166
166
  Detection retains issues with serious impact, recurrence, direct relevance to your stated focus, or a concrete implementation improvement lead, and explains the retention reason, evidence and inspection scope. Implementation leads include code, configuration, tool contracts, Prompt and Skill; an unresolved cause can proceed to diagnosis. Minor observations with none of these reasons are not saved separately. No retained issues does not prove the session was correct.
167
167
 
168
- Use Trace IDs to locate evidence within the session and open issue details to read the retention reason. You can generate a preview once traces are archived. Review any retained content in the HTML before sharing it.
168
+ The capture, detect, diagnose, optimize and verify Skills generate or update `.atk/traces/<session_id>/preview.html` at stage end without opening a browser. Use `$atk-visualize-trace` when you want to open it; refresh an already open page after the file changes. Initialization has no session trace and does not generate an empty page.
169
+
170
+ These stages also refresh the project dashboard `.atk/traces/index.html`, which shows issues and actual progress across all sessions by session or by issue category and links back to each session page. To open it, send `$atk-visualize-trace Open the trace dashboard`. Issues recorded before categories existed show as uncategorized until their session is analyzed again.
171
+
172
+ Use Trace IDs to locate evidence within the session and open issue details to read the retention reason. Review any retained content in the HTML before sharing it.
169
173
 
170
174
  ### 3. Investigate, Fix, and Verify an Issue as Needed
171
175
 
@@ -179,7 +183,7 @@ Start with diagnosis when you already have a specific issue and session evidence
179
183
 
180
184
  ### Where to Find Trace Outputs
181
185
 
182
- Each session archive is stored under `.atk/traces/<session_id>/` in your Agent project. Files appear as you run the corresponding steps.
186
+ Each session archive is stored under `.atk/traces/<session_id>/` in your Agent project. Files appear as you run the corresponding steps. The project dashboard `index.html` and the category list `categories.json` sit directly under `.atk/traces/`.
183
187
 
184
188
  | File or directory | What it shows |
185
189
  | --- | --- |
@@ -151,7 +151,11 @@ $atk-visualize-trace Open the execution lanes for session <session-id> and jump
151
151
 
152
152
  Detection retains issues with serious impact, recurrence, direct relevance to your stated focus, or a concrete implementation improvement lead, and explains the retention reason, evidence and inspection scope. Implementation leads include code, configuration, tool contracts, Prompt and Skill; an unresolved cause can proceed to diagnosis. Minor observations with none of these reasons are not saved separately. No retained issues does not prove the session was correct.
153
153
 
154
- Use Trace IDs to locate evidence within the session and open issue details to read the retention reason. You can generate a preview once traces are archived. Review any retained content in the HTML before sharing it.
154
+ The capture, detect, diagnose, optimize and verify Skills generate or update `.atk/traces/<session_id>/preview.html` at stage end without opening a browser. Use `$atk-visualize-trace` when you want to open it; refresh an already open page after the file changes. Initialization has no session trace and does not generate an empty page.
155
+
156
+ These stages also refresh the project dashboard `.atk/traces/index.html`, which shows issues and actual progress across all sessions by session or by issue category and links back to each session page. To open it, send `$atk-visualize-trace Open the trace dashboard`. Issues recorded before categories existed show as uncategorized until their session is analyzed again.
157
+
158
+ Use Trace IDs to locate evidence within the session and open issue details to read the retention reason. Review any retained content in the HTML before sharing it.
155
159
 
156
160
  ### 3. Investigate, Fix, and Verify an Issue as Needed
157
161
 
@@ -165,7 +169,7 @@ Start with diagnosis when you already have a specific issue and session evidence
165
169
 
166
170
  ### Where to Find Trace Outputs
167
171
 
168
- Each session archive is stored under `.atk/traces/<session_id>/` in your Agent project. Files appear as you run the corresponding steps.
172
+ Each session archive is stored under `.atk/traces/<session_id>/` in your Agent project. Files appear as you run the corresponding steps. The project dashboard `index.html` and the category list `categories.json` sit directly under `.atk/traces/`.
169
173
 
170
174
  | File or directory | What it shows |
171
175
  | --- | --- |
@@ -151,7 +151,11 @@ $atk-visualize-trace 打开 <session-id> 的执行泳道,从问题跳到相关
151
151
 
152
152
  分析保留影响严重、反复出现、与你明确关注点相关,或有具体实现改进线索的问题,并说明保留理由、证据和检查范围。实现线索包括代码、配置、工具契约、Prompt 和 Skill;根因尚未确定也可进入后续诊断。轻微且无上述保留理由的观察不单独记录,没有保留问题不代表会话完全正确。
153
153
 
154
- 可用 Trace ID 定位会话内的证据,在问题详情中查看保留理由。有归档轨迹即可生成预览,分享 HTML 前请核对其中留存的正文。
154
+ 采集、分析、诊断、修复和验证 Skill 在阶段结束后默认生成或更新 `.atk/traces/<session_id>/preview.html`,不自动打开浏览器。需要打开时使用 `$atk-visualize-trace`;已打开的页面在文件更新后需刷新。初始化没有会话轨迹,不生成空页面。
155
+
156
+ 这些阶段同时刷新项目大盘 `.atk/traces/index.html`,按会话或异常分类查看全部会话的问题和实际进度,并可跳回各会话页。需要打开时发送 `$atk-visualize-trace 打开轨迹大盘`。早于分类功能的问题显示为“未分类”,对应会话重新分析时补上。
157
+
158
+ 可用 Trace ID 定位会话内的证据,在问题详情中查看保留理由。分享 HTML 前请核对其中留存的正文。
155
159
 
156
160
  ### 3. 按问题调查、修复和验证(按需)
157
161
 
@@ -165,7 +169,7 @@ $atk-trace-verify 验证 <session-id> 的 ISSUE-001 对应候选,并检查相
165
169
 
166
170
  ### 查看轨迹产物
167
171
 
168
- 会话归档保存在 Agent 项目的 `.atk/traces/<session_id>/` 下,文件随对应步骤生成。
172
+ 会话归档保存在 Agent 项目的 `.atk/traces/<session_id>/` 下,文件随对应步骤生成。项目大盘 `index.html` 和分类表 `categories.json` 位于 `.atk/traces/` 根目录。
169
173
 
170
174
  | 文件或目录 | 用途 |
171
175
  | --- | --- |
@@ -1,3 +1,3 @@
1
1
  """Agent Tune Kit package."""
2
2
 
3
- __version__ = "2.3.1"
3
+ __version__ = "2.3.2"
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  from pathlib import Path
5
5
 
6
- from agent_tune_kit.traces import records, store
6
+ from agent_tune_kit.traces import categories, records, store
7
7
  from agent_tune_kit.traces.capture import fetch_session, import_session
8
8
  from agent_tune_kit.traces.query import overview, search, show
9
9
 
@@ -34,6 +34,12 @@ def register(subparsers):
34
34
  init.add_argument("--config", help="non-secret configuration JSON file")
35
35
  init.add_argument("--project-file", help="project context Markdown file")
36
36
  init.add_argument("--json", action="store_true")
37
+ category = actions.add_parser("category", help="list or update the project issue category vocabulary")
38
+ category.add_argument("--project-root", default=".")
39
+ mode = category.add_mutually_exclusive_group(required=True)
40
+ mode.add_argument("--list", action="store_true", help="list categories with per-session issue counts")
41
+ mode.add_argument("--data", help="category JSON; revising the vocabulary requires its current revision")
42
+ category.add_argument("--json", action="store_true")
37
43
  for action in [
38
44
  "migrate",
39
45
  "fetch",
@@ -100,6 +106,10 @@ def execute(args):
100
106
  if args.action == "init":
101
107
  text = Path(args.project_file).read_text(encoding="utf-8") if args.project_file else None
102
108
  return store.init_workspace(args.project_root, config=read_json(args.config), project_text=text)
109
+ if args.action == "category":
110
+ if args.list:
111
+ return categories.list_categories(args.project_root)
112
+ return categories.save_category(args.project_root, read_json(args.data))
103
113
  source = read_json(args.source)
104
114
  if args.action == "migrate":
105
115
  from agent_tune_kit.traces.migrate import migrate
@@ -4,6 +4,7 @@ import sys
4
4
  from agent_tune_kit.errors import UserActionRequired
5
5
  from agent_tune_kit.visualize import generate_dataset, generate_failures
6
6
  from agent_tune_kit.visualize.trace import generate_trace
7
+ from agent_tune_kit.visualize.traces import generate_traces
7
8
 
8
9
  from .output import emit
9
10
  from .trace import add_session_options, read_json
@@ -17,6 +18,8 @@ def add_options(parser, kind):
17
18
  parser.add_argument("--json", action="store_true")
18
19
  if kind == "dataset":
19
20
  parser.add_argument("--dataset-path", default=".atk/datasets/dataset.csv")
21
+ elif kind == "traces":
22
+ parser.add_argument("--project-root", default=".")
20
23
  elif kind == "trace":
21
24
  add_session_options(parser)
22
25
  parser.add_argument("--output-path")
@@ -31,14 +34,17 @@ def register(subparsers):
31
34
  commands = subparsers.add_parser("visualize", help="generate offline review pages").add_subparsers(
32
35
  dest="action", required=True
33
36
  )
34
- for kind in ["dataset", "failures", "trace"]:
35
- add_options(commands.add_parser(kind), kind)
37
+ helps = {"traces": "generate the project dashboard of every archived trace session"}
38
+ for kind in ["dataset", "failures", "trace", "traces"]:
39
+ add_options(commands.add_parser(kind, help=helps.get(kind)), kind)
36
40
 
37
41
 
38
42
  def execute(args):
39
43
  options = dict(overwrite=args.overwrite, open_browser=args.open_browser)
40
44
  if args.action == "dataset":
41
45
  return generate_dataset(args.dataset_path, **options)
46
+ if args.action == "traces":
47
+ return generate_traces(args.project_root, **options)
42
48
  if args.action == "trace":
43
49
  return generate_trace(
44
50
  args.project_root,
@@ -18,7 +18,9 @@ def component(value):
18
18
  if not isinstance(value, str) or not value.strip():
19
19
  raise UserActionRequired("An explicit nonempty native ID is required.")
20
20
  name = quote(value, safe="")
21
- if name.startswith(".") or name.casefold() in {"project.md", "config.json", "_export_summary"}:
21
+ # Names of the files that live beside session directories at the archive root.
22
+ reserved = {"project.md", "config.json", "categories.json", "index.html", "_export_summary"}
23
+ if name.startswith(".") or name.casefold() in reserved:
22
24
  name = "%" + format(ord(name[0]), "02X") + name[1:]
23
25
  if len(name) > 200:
24
26
  name = name[:150] + "~" + hashlib.sha256(value.encode()).hexdigest()
@@ -0,0 +1,173 @@
1
+ """Project-level issue category vocabulary.
2
+
3
+ The host decides which category fits an issue; this module only keeps the vocabulary consistent. Entries are never
4
+ deleted, so issues that reference a category stay displayable after it is deprecated.
5
+ """
6
+
7
+ import re
8
+ from pathlib import Path
9
+
10
+ from agent_tune_kit.errors import UserActionRequired
11
+ from agent_tune_kit.results.evidence import now, safe_path, write_manifest
12
+
13
+ from .store import ensure_workspace, read_json, session_write_lock, trace_root
14
+
15
+ FILENAME = "categories.json"
16
+ CATEGORY_ID = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*")
17
+ MAX_ID = 64
18
+
19
+
20
+ def _path(root):
21
+ return safe_path(Path(root), FILENAME)
22
+
23
+
24
+ def _check_catalog(value):
25
+ if (
26
+ value.get("schema_version") != 1
27
+ or not isinstance(value.get("revision"), int)
28
+ or isinstance(value.get("revision"), bool)
29
+ or not isinstance(value.get("categories"), list)
30
+ ):
31
+ raise UserActionRequired("Invalid Trace category vocabulary.")
32
+ seen = set()
33
+ for item in value["categories"]:
34
+ if (
35
+ not isinstance(item, dict)
36
+ or not isinstance(item.get("id"), str)
37
+ or not CATEGORY_ID.fullmatch(item["id"])
38
+ or item["id"] in seen
39
+ or any(not isinstance(item.get(key), str) or not item[key].strip() for key in ("name", "definition"))
40
+ or not isinstance(item.get("deprecated"), bool)
41
+ ):
42
+ raise UserActionRequired("Invalid Trace category entry.")
43
+ seen.add(item["id"])
44
+ return value
45
+
46
+
47
+ def load_catalog(root):
48
+ """Read the vocabulary from a traces root; a missing file is an empty vocabulary."""
49
+ path = _path(root)
50
+ if not path.exists():
51
+ return {"schema_version": 1, "revision": 0, "categories": []}
52
+ return _check_catalog(read_json(path))
53
+
54
+
55
+ def category_map(root):
56
+ """Return ``{id: entry}`` for display, tolerating a damaged file so one bad record cannot hide sessions."""
57
+ try:
58
+ return {item["id"]: item for item in load_catalog(root)["categories"]}
59
+ except UserActionRequired:
60
+ return {}
61
+
62
+
63
+ def check_issue_category(root, category, previous=None):
64
+ """Validate an issue category against the vocabulary; an unchanged deprecated value stays valid."""
65
+ if not isinstance(category, str) or not category:
66
+ raise UserActionRequired("Issue category must be a category ID from the project vocabulary.")
67
+ entry = next((item for item in load_catalog(root)["categories"] if item["id"] == category), None)
68
+ if entry is None:
69
+ raise UserActionRequired(
70
+ f"Unknown issue category {category!r}; list the vocabulary with `atk trace category --list` first."
71
+ )
72
+ if entry["deprecated"] and category != previous:
73
+ raise UserActionRequired(f"Issue category {category!r} is deprecated; choose a current category.")
74
+ return category
75
+
76
+
77
+ def save_category(project_root, data):
78
+ """Add or revise one category. Supplying ``revision`` is required once the vocabulary exists."""
79
+ if not isinstance(data, dict):
80
+ raise UserActionRequired("Category data must be an object.")
81
+ unknown = set(data) - {"id", "name", "definition", "deprecated", "revision", "expected_revision"}
82
+ if unknown:
83
+ raise UserActionRequired(f"Unsupported category fields: {', '.join(sorted(unknown))}.")
84
+ category_id = data.get("id")
85
+ if not isinstance(category_id, str) or not CATEGORY_ID.fullmatch(category_id) or len(category_id) > MAX_ID:
86
+ raise UserActionRequired("Category id must be lowercase kebab-case (letters, digits and single hyphens).")
87
+ expected = data.get("revision", data.get("expected_revision"))
88
+ if expected is not None and (not isinstance(expected, int) or isinstance(expected, bool)):
89
+ raise UserActionRequired("Category revision must be an integer.")
90
+ # The vocabulary lives in the private archive, so it gets the same Git ignore rule as captured sessions.
91
+ root, _ = ensure_workspace(project_root)
92
+ with session_write_lock(root):
93
+ catalog = load_catalog(root)
94
+ if expected is None and catalog["revision"] == 0:
95
+ expected = 0
96
+ if expected != catalog["revision"]:
97
+ raise UserActionRequired(
98
+ f"Stale or missing category revision; current vocabulary revision is {catalog['revision']}."
99
+ )
100
+ entries = catalog["categories"]
101
+ existing = next((item for item in entries if item["id"] == category_id), None)
102
+ stamp = now()
103
+ if existing is None:
104
+ for key in ("name", "definition"):
105
+ if not isinstance(data.get(key), str) or not data[key].strip():
106
+ raise UserActionRequired(f"A new category needs a nonempty {key}.")
107
+ if data.get("deprecated") not in (None, False):
108
+ raise UserActionRequired("A new category cannot start deprecated.")
109
+ entry = {
110
+ "id": category_id,
111
+ "name": data["name"].strip(),
112
+ "definition": data["definition"].strip(),
113
+ "deprecated": False,
114
+ "created_at": stamp,
115
+ "updated_at": stamp,
116
+ }
117
+ entries.append(entry)
118
+ else:
119
+ entry = existing
120
+ for key in ("name", "definition"):
121
+ if key in data:
122
+ if not isinstance(data[key], str) or not data[key].strip():
123
+ raise UserActionRequired(f"Category {key} must be a nonempty string.")
124
+ entry[key] = data[key].strip()
125
+ if "deprecated" in data:
126
+ if not isinstance(data["deprecated"], bool):
127
+ raise UserActionRequired("Category deprecated must be true or false.")
128
+ entry["deprecated"] = data["deprecated"]
129
+ entry["updated_at"] = stamp
130
+ catalog["revision"] += 1
131
+ write_manifest(_path(root), catalog)
132
+ return {**entry, "vocabulary_revision": catalog["revision"], "created": existing is None}
133
+
134
+
135
+ def list_categories(project_root):
136
+ """Return the vocabulary with how many issues in each session reference every category."""
137
+ from . import records
138
+
139
+ root = trace_root(project_root)
140
+ catalog = load_catalog(root)
141
+ usage = {item["id"]: {} for item in catalog["categories"]}
142
+ uncategorized = 0
143
+ warnings = []
144
+ sessions = sorted(child for child in root.iterdir() if child.is_dir()) if root.exists() else []
145
+ for session_dir in sessions:
146
+ if not (session_dir / "session.json").is_file() or session_dir.is_symlink():
147
+ continue
148
+ issue_root = session_dir / "issues"
149
+ for issue_file in sorted(issue_root.glob("*/issue.json")) if issue_root.is_dir() else []:
150
+ try:
151
+ issue = records.load_issue(session_dir, issue_file.parent.name)
152
+ except UserActionRequired as exc:
153
+ warnings.append({"session": session_dir.name, "issue_id": issue_file.parent.name, "error": str(exc)})
154
+ continue
155
+ category = issue.get("category")
156
+ if category is None:
157
+ uncategorized += 1
158
+ elif category in usage:
159
+ usage[category][session_dir.name] = usage[category].get(session_dir.name, 0) + 1
160
+ return {
161
+ "revision": catalog["revision"],
162
+ "categories": [
163
+ {
164
+ **item,
165
+ "sessions": len(usage[item["id"]]),
166
+ "issues": sum(usage[item["id"]].values()),
167
+ "by_session": usage[item["id"]],
168
+ }
169
+ for item in catalog["categories"]
170
+ ],
171
+ "uncategorized_issues": uncategorized,
172
+ "warnings": warnings,
173
+ }
@@ -0,0 +1,95 @@
1
+ """Actual issue progress derived from saved records.
2
+
3
+ The declared issue ``status`` is not a progress signal; only ``excluded`` is honored so that withdrawn issues do not
4
+ stay in the backlog. Everything else comes from diagnoses, changes and verification records. The functions here do no
5
+ I/O so the session preview and the project dashboard report the same result.
6
+ """
7
+
8
+ from datetime import datetime
9
+
10
+ # Workflow order: unfinished work first, closed outcomes last. Keys double as CSS class names.
11
+ PROGRESS = (
12
+ ("pending", "待诊断"),
13
+ ("diagnosed", "已诊断"),
14
+ ("changing", "修复中"),
15
+ ("awaiting", "待验证"),
16
+ ("verifying", "验证中"),
17
+ ("fail", "验证失败"),
18
+ ("inconclusive", "无法判断"),
19
+ ("pass", "验证通过"),
20
+ ("excluded", "已排除"),
21
+ )
22
+ LABELS = dict(PROGRESS)
23
+ ORDER = {key: index for index, (key, _) in enumerate(PROGRESS)}
24
+
25
+
26
+ def parse_instant(value):
27
+ """Return epoch seconds for an ISO timestamp, or None when it is missing, malformed or has no timezone."""
28
+ try:
29
+ stamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
30
+ if stamp.tzinfo is not None:
31
+ return stamp.timestamp()
32
+ except (TypeError, ValueError, AttributeError, OverflowError, OSError):
33
+ pass
34
+ return None
35
+
36
+
37
+ def instant(record):
38
+ """Return the creation instant in seconds, or infinity when it is missing or has no timezone."""
39
+ when = parse_instant(record.get("created_at")) if isinstance(record, dict) else None
40
+ return float("inf") if when is None else when
41
+
42
+
43
+ def _recency(record):
44
+ """Sort key where the newest known instant is largest; records with unknown time never count as newest."""
45
+ when = instant(record)
46
+ known = when != float("inf")
47
+ return known, when if known else 0.0, str(record.get("id", ""))
48
+
49
+
50
+ def _result(key):
51
+ return {"key": key, "label": LABELS[key]}
52
+
53
+
54
+ # Verification states whose per-issue conclusions count as an effect outcome. Failed, blocked and interrupted attempts
55
+ # stay visible on the session page but never replace an earlier finished outcome.
56
+ FINISHED = {"completed", "partial"}
57
+
58
+
59
+ def issue_progress(issue, changes):
60
+ """Return ``{key, label}`` for one issue.
61
+
62
+ ``changes`` pairs every change linked to the issue (including a shared change owned by another issue) with all
63
+ verification records of that change; an entry that could not be read is passed as ``None``. The newest completed
64
+ or running change decides the stage, because an older verified attempt says nothing about a newer one. Within that
65
+ change the newest finished verification gives the outcome. An unreadable verification might be the newest one, so
66
+ the outcome is then unknown rather than an older result.
67
+ """
68
+ if issue.get("status") == "excluded":
69
+ return _result("excluded")
70
+ active = [(change, items) for change, items in changes if change.get("status") in {"completed", "running"}]
71
+ if active:
72
+ change, verifications = max(active, key=lambda pair: _recency(pair[0]))
73
+ if change["status"] == "running":
74
+ return _result("changing")
75
+ if any(not isinstance(item, dict) for item in verifications):
76
+ return _result("inconclusive")
77
+ finished = [item for item in verifications if item.get("status") in FINISHED]
78
+ if finished:
79
+ latest = max(finished, key=_recency)
80
+ conclusions = latest.get("conclusions")
81
+ conclusion = conclusions.get(issue.get("id")) if isinstance(conclusions, dict) else None
82
+ outcome = conclusion.get("outcome") if isinstance(conclusion, dict) else None
83
+ return _result(outcome if outcome in {"pass", "fail"} else "inconclusive")
84
+ if any(item.get("status") == "running" for item in verifications):
85
+ return _result("verifying")
86
+ return _result("awaiting")
87
+ return _result("diagnosed" if issue.get("diagnoses") else "pending")
88
+
89
+
90
+ def summarize(keys):
91
+ """Count progress keys in workflow order, omitting nothing so callers can render stable legends."""
92
+ counts = dict.fromkeys(LABELS, 0)
93
+ for key in keys:
94
+ counts[key] += 1
95
+ return counts
@@ -9,6 +9,7 @@ from agent_tune_kit.errors import UserActionRequired
9
9
  from agent_tune_kit.results.evidence import json_bytes, now, safe_path, write_manifest
10
10
  from agent_tune_kit.utils.files import write_text_atomic
11
11
 
12
+ from .categories import check_issue_category
12
13
  from .segments import resolve_segments, segments_for_storage
13
14
  from .store import (
14
15
  STATES,
@@ -64,6 +65,7 @@ def load_issue(session_dir, issue_id):
64
65
  or any(not isinstance(issue.get(key), str) for key in ("title", "description", "created_at"))
65
66
  or any(not isinstance(issue.get(key), list) for key in ("notes", "history"))
66
67
  or issue.get("status") not in ISSUE_STATES
68
+ or ("category" in issue and not isinstance(issue["category"], str))
67
69
  ):
68
70
  raise UserActionRequired("Invalid issue identity or record structure.")
69
71
  segments = issue.get("segments", [])
@@ -171,7 +173,11 @@ def prepare_issue(session_dir, data, *, dry_run=False):
171
173
  "segments": [],
172
174
  }
173
175
  )
174
- for field in ("title", "description", "evidence", "status", "notes", "selection_reason", "segments"):
176
+ if "category" in data:
177
+ data["category"] = check_issue_category(
178
+ directory.parent, data["category"], previous.get("category") if previous else None
179
+ )
180
+ for field in ("title", "description", "evidence", "status", "notes", "selection_reason", "segments", "category"):
175
181
  if field in data:
176
182
  issue[field] = data[field]
177
183
  if not previous:
@@ -220,6 +226,7 @@ def prepare_issue(session_dir, data, *, dry_run=False):
220
226
  "title",
221
227
  "description",
222
228
  "selection_reason",
229
+ "category",
223
230
  "evidence",
224
231
  "segments",
225
232
  "status",
@@ -336,8 +343,9 @@ def save_diagnosis(session_dir, issue_id, text, diagnosis_id=None):
336
343
  "segments": issue.get("segments", []),
337
344
  "created_at": now(),
338
345
  }
339
- if "selection_reason" in issue:
340
- snapshot["selection_reason"] = issue["selection_reason"]
346
+ for optional in ("selection_reason", "category"):
347
+ if optional in issue:
348
+ snapshot[optional] = issue[optional]
341
349
  _refs(directory, issue["evidence"])
342
350
  for segment in snapshot["segments"]:
343
351
  _refs(directory, segment["evidence"])
@@ -407,6 +415,51 @@ def _sync_change_links(session_dir, owner, change):
407
415
  write_manifest(_issue_path(session_dir, issue_id), issue)
408
416
 
409
417
 
418
+ def _summary_text(value):
419
+ return isinstance(value, str) and bool(value.strip())
420
+
421
+
422
+ def _summary_list(value, *, allow_empty=False):
423
+ return isinstance(value, list) and (allow_empty or bool(value)) and all(_summary_text(item) for item in value)
424
+
425
+
426
+ def _validate_change_summary(change):
427
+ if "change_summary" in change or change["status"] == "completed":
428
+ summary = change.get("change_summary")
429
+ if (
430
+ not isinstance(summary, list)
431
+ or not summary
432
+ or any(
433
+ not isinstance(item, dict)
434
+ or not _summary_text(item.get("what"))
435
+ or not _summary_text(item.get("why"))
436
+ or not _summary_list(item.get("files", []), allow_empty=True)
437
+ for item in summary
438
+ )
439
+ ):
440
+ raise UserActionRequired("change_summary needs actual changes with nonempty what/why and optional files.")
441
+ if ("remaining_verification" in change or change["status"] == "completed") and not _summary_list(
442
+ change.get("remaining_verification")
443
+ ):
444
+ raise UserActionRequired("remaining_verification must list specific behavior still to verify.")
445
+
446
+
447
+ def _validate_verification_summary(result):
448
+ if "verification_summary" in result or result["status"] != "running":
449
+ summary = result.get("verification_summary")
450
+ if (
451
+ not isinstance(summary, dict)
452
+ or not _summary_text(summary.get("method"))
453
+ or not _summary_text(summary.get("sample_scope"))
454
+ or not _summary_list(summary.get("observations"))
455
+ ):
456
+ raise UserActionRequired("verification_summary needs method, sample_scope and nonempty observations.")
457
+ if ("limitations" in result or result["status"] != "running") and not _summary_list(
458
+ result.get("limitations"), allow_empty=True
459
+ ):
460
+ raise UserActionRequired("limitations must be a list of concise statements (empty when none are known).")
461
+
462
+
410
463
  @_locked_write
411
464
  def save_change(session_dir, issue_id, data):
412
465
  directory = Path(session_dir)
@@ -448,6 +501,7 @@ def save_change(session_dir, issue_id, data):
448
501
  raise UserActionRequired("A completed change needs actual candidate identity and a recorded diff.")
449
502
  if not previous and change["status"] != "running":
450
503
  raise UserActionRequired("Save a running change before applying the implementation.")
504
+ _validate_change_summary(change)
451
505
  for field in ("verifications", "schema_version", "owner_issue", "created_at"):
452
506
  if field in data and (not previous or data[field] != previous.get(field)):
453
507
  raise UserActionRequired(f"Change {field} is managed by its dedicated operation.")
@@ -517,6 +571,7 @@ def save_verification(session_dir, issue_id, change_id, data):
517
571
  raise UserActionRequired("Unverified candidate loading requires an inconclusive effect judgment.")
518
572
  if result["status"] == "completed" and not result["execution"]:
519
573
  raise UserActionRequired("Completed verification needs actual execution details.")
574
+ _validate_verification_summary(result)
520
575
  result.update(
521
576
  change=evidence_ref(directory, _change_path(directory, issue_id, change_id).relative_to(directory).as_posix()),
522
577
  schema_version=1,
@@ -88,7 +88,7 @@ def read_json(path):
88
88
  if not isinstance(value, dict):
89
89
  raise ValueError
90
90
  return value
91
- except (OSError, ValueError, UnicodeError):
91
+ except (OSError, ValueError, UnicodeError, RecursionError):
92
92
  raise UserActionRequired(f"Missing or damaged Trace record: {path.name}") from None
93
93
 
94
94