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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/.codex-plugin/plugin.json +1 -1
  2. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/PKG-INFO +9 -5
  3. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/README.en.md +8 -4
  4. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/README.md +8 -4
  5. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/__init__.py +1 -1
  6. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/installer.py +47 -13
  7. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/query.py +10 -7
  8. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/records.py +7 -0
  9. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/segments.py +3 -17
  10. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/trace/app.js +120 -20
  11. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/trace/page.html +1 -0
  12. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/trace/styles.css +13 -0
  13. agent_tune_kit-2.3.1/agent_tune_kit/visualize/assets/trace/vendor/markdown-it.LICENSE +158 -0
  14. agent_tune_kit-2.3.1/agent_tune_kit/visualize/assets/trace/vendor/markdown-it.min.js +13 -0
  15. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/timeline.py +2 -12
  16. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/trace.py +54 -3
  17. agent_tune_kit-2.3.1/docs/assets/agent_tune_kit_evaluation.png +0 -0
  18. agent_tune_kit-2.3.1/docs/assets/agent_tune_kit_trace.png +0 -0
  19. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/shared-versioning-and-confirmation.md +2 -2
  20. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/skill-template-pack-usage.md +10 -7
  21. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/trace-tuning-validation.md +15 -1
  22. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/trace-tuning.md +14 -2
  23. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/pyproject.toml +1 -1
  24. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/check-release.py +1 -1
  25. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/dev-refresh-install.sh +1 -1
  26. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/validate_skill_pack.py +3 -1
  27. agent_tune_kit-2.3.1/skills/atk-trace-detect/SKILL.md +78 -0
  28. agent_tune_kit-2.3.1/skills/atk-trace-diagnose/SKILL.md +59 -0
  29. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/conftest.py +1 -1
  30. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_install_plugin.py +12 -9
  31. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_plugin_cache.py +88 -23
  32. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_release_scripts.py +1 -1
  33. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_trace_cli.py +2 -0
  34. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_trace_records.py +41 -0
  35. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_visualize_trace.py +142 -1
  36. agent_tune_kit-2.3.1/tests/trace-selection-reason.cjs +41 -0
  37. agent_tune_kit-2.3.0/skills/atk-trace-detect/SKILL.md +0 -57
  38. agent_tune_kit-2.3.0/skills/atk-trace-diagnose/SKILL.md +0 -47
  39. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/.gitignore +0 -0
  40. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/__init__.py +0 -0
  41. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/__main__.py +0 -0
  42. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/dataset.py +0 -0
  43. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/evaluation.py +0 -0
  44. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/main.py +0 -0
  45. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/output.py +0 -0
  46. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/results.py +0 -0
  47. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/trace.py +0 -0
  48. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/cli/visualize.py +0 -0
  49. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/datasets/__init__.py +0 -0
  50. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/datasets/prepare.py +0 -0
  51. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/datasets/truth.py +0 -0
  52. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/datasets/updates.py +0 -0
  53. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/datasets/validation.py +0 -0
  54. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/errors.py +0 -0
  55. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/execution/__init__.py +0 -0
  56. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/execution/calibration.py +0 -0
  57. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/execution/logs.py +0 -0
  58. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/execution/rules.py +0 -0
  59. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/execution/runner.py +0 -0
  60. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/execution/runtime.py +0 -0
  61. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/execution/tracing.py +0 -0
  62. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/results/__init__.py +0 -0
  63. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/results/assessment.py +0 -0
  64. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/results/evidence.py +0 -0
  65. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/results/judgments.py +0 -0
  66. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/results/summary.py +0 -0
  67. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/results/versions.py +0 -0
  68. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/__init__.py +0 -0
  69. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/archive.py +0 -0
  70. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/capture.py +0 -0
  71. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/langfuse.py +0 -0
  72. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/migrate.py +0 -0
  73. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/traces/store.py +0 -0
  74. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/utils/__init__.py +0 -0
  75. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/utils/csv_io.py +0 -0
  76. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/utils/files.py +0 -0
  77. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/__init__.py +0 -0
  78. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/dataset/app.js +0 -0
  79. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/dataset/page.html +0 -0
  80. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/dataset/styles.css +0 -0
  81. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/dataset/vendor/echarts.min.js +0 -0
  82. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/failures/app.js +0 -0
  83. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/failures/page.html +0 -0
  84. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/failures/styles.css +0 -0
  85. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/assets/trace/lanes.js +0 -0
  86. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/dataset.py +0 -0
  87. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/failures.py +0 -0
  88. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/fields.py +0 -0
  89. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/history.py +0 -0
  90. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/visualize/render.py +0 -0
  91. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/agent_tune_kit/workspace.py +0 -0
  92. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/assets/arch-en.png +0 -0
  93. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/assets/arch.png +0 -0
  94. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/assets/architecture.en.svg +0 -0
  95. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/assets/architecture.svg +0 -0
  96. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/ato-atk-project-boundary-recommendation-2026-11-05.md +0 -0
  97. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/cli-skill-modularization-plan.md +0 -0
  98. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/codex_agent_tuning_prd.md +0 -0
  99. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/execution-evidence-and-tracing-plan.md +0 -0
  100. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/ground-truth-and-grading-assessment-2026-10-25.md +0 -0
  101. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/local-context.md +0 -0
  102. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/surginame-atk-capability-review-2026-10-27.md +0 -0
  103. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/trace-swimlane-design.md +0 -0
  104. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/docs/truth-and-assessment.md +0 -0
  105. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/install_plugin.py +0 -0
  106. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/publish-pypi.sh +0 -0
  107. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/publish-release.py +0 -0
  108. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/release-version.py +0 -0
  109. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/scripts/release-version.sh +0 -0
  110. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-dataset/SKILL.md +0 -0
  111. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-dataset/scripts/update_dataset.py +0 -0
  112. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-diagnose/SKILL.md +0 -0
  113. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-eval/SKILL.md +0 -0
  114. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-init/SKILL.md +0 -0
  115. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-optimize/SKILL.md +0 -0
  116. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-report/SKILL.md +0 -0
  117. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-trace-capture/SKILL.md +0 -0
  118. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-trace-init/SKILL.md +0 -0
  119. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-trace-optimize/SKILL.md +0 -0
  120. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-trace-verify/SKILL.md +0 -0
  121. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-visualize-dataset/SKILL.md +0 -0
  122. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-visualize-dataset/scripts/generate_dataset_browser.py +0 -0
  123. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-visualize-failures/SKILL.md +0 -0
  124. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-visualize-failures/scripts/generate_failure_browser.py +0 -0
  125. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/skills/atk-visualize-trace/SKILL.md +0 -0
  126. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/templates/.atk/runner/eval_runner.py.md +0 -0
  127. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/templates/.atk/runner/failure_rule.py.md +0 -0
  128. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/templates/.atk/runner/import_evidence.py.md +0 -0
  129. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/templates/.gitkeep +0 -0
  130. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/__init__.py +0 -0
  131. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/fixtures/legacy_eval_runner.py.txt +0 -0
  132. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/fixtures/managed_v1.py.md +0 -0
  133. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_assessment_contract.py +0 -0
  134. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_atk_init_contract.py +0 -0
  135. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_cli_workflow.py +0 -0
  136. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_dataset_updates.py +0 -0
  137. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_diagnosis.py +0 -0
  138. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_evidence_import.py +0 -0
  139. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_evidence_judgments.py +0 -0
  140. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_evidence_packaging.py +0 -0
  141. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_evidence_versions.py +0 -0
  142. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_execution_evidence.py +0 -0
  143. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_execution_outcomes.py +0 -0
  144. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_execution_tracing.py +0 -0
  145. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_generate_dataset_browser.py +0 -0
  146. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_generate_failure_browser.py +0 -0
  147. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_judge_calibration.py +0 -0
  148. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_local_context.py +0 -0
  149. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_runner_output_columns.py +0 -0
  150. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_runner_template_row_logging.py +0 -0
  151. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_runtime_contracts.py +0 -0
  152. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_snapshot_reads.py +0 -0
  153. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_trace_capture.py +0 -0
  154. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_trace_langfuse.py +0 -0
  155. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_trace_query.py +0 -0
  156. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_trace_segments.py +0 -0
  157. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_trace_store.py +0 -0
  158. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_truth_browser.py +0 -0
  159. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_truth_cli.py +0 -0
  160. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_truth_contract.py +0 -0
  161. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_truth_runner.py +0 -0
  162. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/test_v2_progress.py +0 -0
  163. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/trace-event-wheel.cjs +0 -0
  164. {agent_tune_kit-2.3.0 → agent_tune_kit-2.3.1}/tests/trace-preview-scroll.cjs +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-tune-kit",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
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.0
3
+ Version: 2.3.1
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
@@ -53,10 +53,12 @@ atk install
53
53
 
54
54
  </details>
55
55
 
56
- Finish active ATK tasks before reinstalling, and open a new session afterward to load updates. If you have installation problems, run `atk status` and confirm the plugin is enabled in Codex. [Installation and cache notes](docs/skill-template-pack-usage.md#local-plugin-install-and-smoke)
56
+ Finish active ATK tasks before reinstalling. `atk install` directly updates and verifies the Codex ATK cache, so no cache rebuild is needed afterward. Start a new session to replace instructions already read into a conversation; restart Codex if the skill list has not refreshed. If you have installation problems, run `atk status` and confirm the plugin is enabled in Codex. [Installation and cache notes](docs/skill-template-pack-usage.md#local-plugin-install-and-smoke)
57
57
 
58
58
  ## Evaluation-based tuning
59
59
 
60
+ ![Evaluation-based tuning architecture showing data preparation and Agent setup, batch evaluation, diagnosis, cross-round reporting, targeted tuning, and reruns.](docs/assets/agent_tune_kit_evaluation.png)
61
+
60
62
  Use a set of evaluation samples to find Agent failures and verify changes in later rounds. Prepare your Agent's entry point and a set of samples, preferably in CSV format. Five to twenty samples are enough to get started. Add expected answers, labels, or acceptance criteria where available.
61
63
 
62
64
  Open your Agent project in Codex and send the following `$atk-…` instructions in the conversation.
@@ -139,10 +141,10 @@ Each round saves results under `.atk/results/vN/` in your Agent project. Files a
139
141
 
140
142
  [More batch evaluation options and outputs](docs/skill-template-pack-usage.md#manual-22--26-loop)
141
143
 
142
- ![Evaluation-based tuning workflow, from preparing data and connecting an Agent through evaluation, diagnosis, reporting, tuning, and rerunning the evaluation.](docs/assets/architecture.en.svg)
143
-
144
144
  ## Continuous tuning from session traces
145
145
 
146
+ ![Trace tuning architecture showing session capture, issue detection, root-cause diagnosis, targeted fixes, and verification.](docs/assets/agent_tune_kit_trace.png)
147
+
146
148
  Start with an existing session and use its execution trace to check whether the Agent completed the task. Save diagnoses, fixes, and verification results by issue. Get the native Session ID, its source, and access to the trace, then open the Agent project in Codex. You can start without an evaluation dataset or runner.
147
149
 
148
150
  Run `atk trace --help` to check whether your CLI supports trace tuning. If the command is unavailable, follow the local source installation steps above with a version that supports trace tuning, then open a new Codex session.
@@ -161,7 +163,9 @@ $atk-trace-detect Check whether session <session-id> completed its task correctl
161
163
  $atk-visualize-trace Open the execution lanes for session <session-id> and jump from issues to the relevant trace segments and evidence fields.
162
164
  ```
163
165
 
164
- Detection lists issues, evidence, and what it inspected. Use Trace IDs to locate evidence within the session. You can generate a preview once traces are archived. Review any retained content in the HTML before sharing it.
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
+
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.
165
169
 
166
170
  ### 3. Investigate, Fix, and Verify an Issue as Needed
167
171
 
@@ -39,10 +39,12 @@ atk install
39
39
 
40
40
  </details>
41
41
 
42
- Finish active ATK tasks before reinstalling, and open a new session afterward to load updates. If you have installation problems, run `atk status` and confirm the plugin is enabled in Codex. [Installation and cache notes](docs/skill-template-pack-usage.md#local-plugin-install-and-smoke)
42
+ Finish active ATK tasks before reinstalling. `atk install` directly updates and verifies the Codex ATK cache, so no cache rebuild is needed afterward. Start a new session to replace instructions already read into a conversation; restart Codex if the skill list has not refreshed. If you have installation problems, run `atk status` and confirm the plugin is enabled in Codex. [Installation and cache notes](docs/skill-template-pack-usage.md#local-plugin-install-and-smoke)
43
43
 
44
44
  ## Evaluation-based tuning
45
45
 
46
+ ![Evaluation-based tuning architecture showing data preparation and Agent setup, batch evaluation, diagnosis, cross-round reporting, targeted tuning, and reruns.](docs/assets/agent_tune_kit_evaluation.png)
47
+
46
48
  Use a set of evaluation samples to find Agent failures and verify changes in later rounds. Prepare your Agent's entry point and a set of samples, preferably in CSV format. Five to twenty samples are enough to get started. Add expected answers, labels, or acceptance criteria where available.
47
49
 
48
50
  Open your Agent project in Codex and send the following `$atk-…` instructions in the conversation.
@@ -125,10 +127,10 @@ Each round saves results under `.atk/results/vN/` in your Agent project. Files a
125
127
 
126
128
  [More batch evaluation options and outputs](docs/skill-template-pack-usage.md#manual-22--26-loop)
127
129
 
128
- ![Evaluation-based tuning workflow, from preparing data and connecting an Agent through evaluation, diagnosis, reporting, tuning, and rerunning the evaluation.](docs/assets/architecture.en.svg)
129
-
130
130
  ## Continuous tuning from session traces
131
131
 
132
+ ![Trace tuning architecture showing session capture, issue detection, root-cause diagnosis, targeted fixes, and verification.](docs/assets/agent_tune_kit_trace.png)
133
+
132
134
  Start with an existing session and use its execution trace to check whether the Agent completed the task. Save diagnoses, fixes, and verification results by issue. Get the native Session ID, its source, and access to the trace, then open the Agent project in Codex. You can start without an evaluation dataset or runner.
133
135
 
134
136
  Run `atk trace --help` to check whether your CLI supports trace tuning. If the command is unavailable, follow the local source installation steps above with a version that supports trace tuning, then open a new Codex session.
@@ -147,7 +149,9 @@ $atk-trace-detect Check whether session <session-id> completed its task correctl
147
149
  $atk-visualize-trace Open the execution lanes for session <session-id> and jump from issues to the relevant trace segments and evidence fields.
148
150
  ```
149
151
 
150
- Detection lists issues, evidence, and what it inspected. Use Trace IDs to locate evidence within the session. You can generate a preview once traces are archived. Review any retained content in the HTML before sharing it.
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
+
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.
151
155
 
152
156
  ### 3. Investigate, Fix, and Verify an Issue as Needed
153
157
 
@@ -39,10 +39,12 @@ atk install
39
39
 
40
40
  </details>
41
41
 
42
- 重装前先结束正在运行的 ATK 任务,安装后新建会话以加载更新。遇到安装问题时,运行 `atk status` 检查安装,并在 Codex 中确认插件已启用。[安装与缓存说明](docs/skill-template-pack-usage.md#local-plugin-install-and-smoke)
42
+ 重装前先结束正在运行的 ATK 任务。`atk install` 会直接更新并校验 Codex 的 ATK 缓存,完成后无需等待缓存重建。已读入对话的旧指令仍需新建会话加载;技能列表未刷新时重启 Codex。遇到安装问题时,运行 `atk status` 检查安装,并在 Codex 中确认插件已启用。[安装与缓存说明](docs/skill-template-pack-usage.md#local-plugin-install-and-smoke)
43
43
 
44
44
  ## 评测调优
45
45
 
46
+ ![评测调优架构图,展示准备与接入、批量评测、判定与归因、报告与跨轮复核、定向优化及复测。](docs/assets/agent_tune_kit_evaluation.png)
47
+
46
48
  用一组评测样本找出 Agent 的问题,并用后续评测验证改动。准备 Agent 的入口和一组样本,推荐用 CSV,5 到 20 条即可开始。每条样本可附上期望答案、标签或可验收的行为标准。
47
49
 
48
50
  在 Codex 中打开 Agent 项目,在对话中发送下面的 `$atk-…` 指令。
@@ -125,10 +127,10 @@ $atk-report
125
127
 
126
128
  [更多批量评测运行选项和产物](docs/skill-template-pack-usage.md#manual-22--26-loop)
127
129
 
128
- ![评测调优流程,从准备数据和接入 Agent 到评测、诊断、报告、调优和复测。](docs/assets/architecture.svg)
129
-
130
130
  ## 轨迹持续调优
131
131
 
132
+ ![轨迹调优架构图,展示会话采集、问题识别、根因诊断、定向修复和执行验证。](docs/assets/agent_tune_kit_trace.png)
133
+
132
134
  从一个已有会话出发,结合实际执行轨迹判断任务是否完成,再按问题保存诊断、修复和验证记录。准备原始 Session ID、来源和可读取的轨迹,直接在 Codex 中打开 Agent 项目开始。无需评测数据集或 runner。
133
135
 
134
136
  可用 `atk trace --help` 检查当前 CLI 是否支持轨迹调优。如果命令不可用,按上面的本地源码方式安装支持轨迹调优的版本,再新建 Codex 会话。
@@ -147,7 +149,9 @@ $atk-trace-detect 分析 <session-id> 的任务结果是否正确,列出问题
147
149
  $atk-visualize-trace 打开 <session-id> 的执行泳道,从问题跳到相关轨迹片段和证据字段。
148
150
  ```
149
151
 
150
- 分析会列出问题、证据和检查范围。可用 Trace ID 定位会话内的证据。有归档轨迹即可生成预览,分享 HTML 前请核对其中留存的正文。
152
+ 分析保留影响严重、反复出现、与你明确关注点相关,或有具体实现改进线索的问题,并说明保留理由、证据和检查范围。实现线索包括代码、配置、工具契约、Prompt 和 Skill;根因尚未确定也可进入后续诊断。轻微且无上述保留理由的观察不单独记录,没有保留问题不代表会话完全正确。
153
+
154
+ 可用 Trace ID 定位会话内的证据,在问题详情中查看保留理由。有归档轨迹即可生成预览,分享 HTML 前请核对其中留存的正文。
151
155
 
152
156
  ### 3. 按问题调查、修复和验证(按需)
153
157
 
@@ -1,3 +1,3 @@
1
1
  """Agent Tune Kit package."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "2.3.1"
@@ -406,9 +406,8 @@ def cache_status(target: Path, cache: Path) -> tuple[bool, list[str]]:
406
406
  entries = sorted(cache.iterdir()) if cache.exists() else []
407
407
  entries = [entry for entry in entries if entry.name not in COPY_IGNORE_NAMES]
408
408
  if not entries:
409
- return True, ["缓存尚未生成,等待 Codex 加载;尚未确认会话生效。"]
409
+ return False, ["ATK 缓存缺失;运行 atk install 修复。"]
410
410
  expected = payload_hashes(target)
411
- version = current_manifest_version(target)
412
411
  ok = True
413
412
  lines: list[str] = []
414
413
  for entry in entries:
@@ -418,22 +417,57 @@ def cache_status(target: Path, cache: Path) -> tuple[bool, list[str]]:
418
417
  missing = sorted(expected.keys() - actual.keys())
419
418
  extra = sorted(actual.keys() - expected.keys())
420
419
  changed = sorted(key for key in expected.keys() & actual.keys() if expected[key] != actual[key])
421
- version_ok = entry.name in (version, "local")
422
- if not (missing or extra or changed) and version_ok:
420
+ if not (missing or extra or changed):
423
421
  lines.append(f"缓存与安装内容一致:{entry}")
424
422
  continue
425
423
  ok = False
426
424
  lines.append(f"缓存不一致:{entry}(修改 {len(changed)},缺失 {len(missing)},多余 {len(extra)})")
427
- if not version_ok:
428
- lines.append(f" 缓存目录版本 {entry.name} 与安装版本 {version} 不同。")
429
425
  differences = [("修改", changed), ("缺失", missing), ("多余", extra)]
430
426
  details = [f" {label}:{name}" for label, names in differences for name in names]
431
427
  lines.extend(details[:5])
432
428
  if not ok:
433
- lines.append("运行 atk install 刷新缓存,然后重启 Codex 并新建会话。")
429
+ lines.append("运行 atk install 更新缓存;已读入的旧指令需新建会话,技能列表未刷新时重启 Codex。")
434
430
  return ok, lines
435
431
 
436
432
 
433
+ def refresh_plugin_cache(target: Path, cache: Path, payload_source: PayloadSource) -> None:
434
+ entries = [entry for entry in cache.iterdir() if entry.name not in COPY_IGNORE_NAMES] if cache.exists() else []
435
+ for entry in entries:
436
+ if entry.is_symlink() or not entry.is_dir():
437
+ raise InstallError(f"unsafe plugin cache entry: {entry}")
438
+
439
+ cache.parent.mkdir(parents=True, exist_ok=True)
440
+ work = Path(tempfile.mkdtemp(prefix=f".{PLUGIN_NAME}-", dir=cache.parent))
441
+ staged, previous = work / "new", work / "previous"
442
+ try:
443
+ copy_payload_tree(payload_source, staged / "local")
444
+ # Keep paths already advertised to sessions readable with the new payload.
445
+ for entry in entries:
446
+ if entry.name != "local":
447
+ shutil.copytree(staged / "local", staged / entry.name)
448
+ valid, details = cache_status(target, staged)
449
+ if not valid:
450
+ raise InstallError("staged cache validation failed: " + "; ".join(details))
451
+
452
+ # shortcut: two renames leave a brief gap; use a platform directory exchange if live-read continuity is required.
453
+ if cache.exists():
454
+ os.replace(cache, previous)
455
+ try:
456
+ os.replace(staged, cache)
457
+ except BaseException:
458
+ if previous.exists():
459
+ try:
460
+ os.replace(previous, cache)
461
+ except OSError as exc:
462
+ raise InstallError(f"cache restore failed; previous cache preserved at {previous}: {exc}") from exc
463
+ raise
464
+ remove_path(previous)
465
+ finally:
466
+ # A failed restore must not delete the only surviving previous cache.
467
+ if not previous.exists():
468
+ remove_path(work)
469
+
470
+
437
471
  def collect_status(
438
472
  marketplace_path: Path, plugin_store: Path, payload_source: PayloadSource | None = None
439
473
  ) -> tuple[dict[str, bool], list[str]]:
@@ -561,7 +595,7 @@ def run_preview(args: argparse.Namespace) -> int:
561
595
  print("已有文件或注册与本次安装冲突;替换需要确认。")
562
596
  if args.smoke:
563
597
  smoke_check(marketplace_path, plugin_store, dry_run=True, payload_source=payload_source)
564
- print(f"安装校验成功后将清理 ATK 缓存:{cache}")
598
+ print(f"安装校验成功后将更新 ATK 缓存:{cache}")
565
599
  print("未修改文件。运行 atk install 进行安装。")
566
600
  return 0
567
601
 
@@ -595,16 +629,16 @@ def run_install(args: argparse.Namespace) -> int:
595
629
  smoke_check(marketplace_path, plugin_store, dry_run=False, payload_source=payload_source)
596
630
 
597
631
  validate_manifest(target / ".codex-plugin" / "plugin.json")
598
- # Invalidate only after a usable installation exists, including with --no-smoke.
632
+ # Publish only after a usable installation exists, including with --no-smoke.
599
633
  try:
600
634
  cache = plugin_cache_path(target, marketplace_path, payload_source)
601
- remove_path(cache)
635
+ refresh_plugin_cache(target, cache, payload_source)
602
636
  except (InstallError, OSError) as exc:
603
637
  raise InstallError(f"plugin installed, but cache refresh failed: {cache}: {exc}") from exc
604
638
 
605
639
  print(f"已为 Codex 本地安装 Agent Tune Kit {payload_source.package_version}。")
606
- print("ATK 缓存已清理或尚未生成;等待 Codex 重新加载。")
607
- print("下一步:刷新 Codex /plugins,确认启用 Agent Tune Kit;重启 Codex 并新建会话。")
640
+ print("ATK 缓存已更新并校验,后续文件读取使用最新内容。")
641
+ print("下一步:确认 Codex /plugins 已启用 Agent Tune Kit;已读入的旧指令需新建会话,技能列表未刷新时重启 Codex。")
608
642
  return 0
609
643
 
610
644
 
@@ -691,7 +725,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
691
725
  subparsers.add_parser(
692
726
  "preview", parents=[common], help="preview planned marketplace/plugin-store changes without writing"
693
727
  )
694
- subparsers.add_parser("install", parents=[common], help="install, validate, then clear personal ATK caches")
728
+ subparsers.add_parser("install", parents=[common], help="install, validate, then replace personal ATK caches")
695
729
  subparsers.add_parser(
696
730
  "status", parents=[common], help="check local installation and compare cached plugin contents (read-only)"
697
731
  )
@@ -10,12 +10,12 @@ from agent_tune_kit.errors import UserActionRequired
10
10
  from . import store
11
11
 
12
12
 
13
- def _milliseconds(value):
13
+ def timestamp_ms(value):
14
+ """Parse epoch milliseconds, interpreting unzoned timestamps as machine-local time."""
14
15
  if isinstance(value, str):
15
16
  try:
16
17
  moment = datetime.fromisoformat(value)
17
- if moment.utcoffset() is not None:
18
- return round(moment.timestamp() * 1_000_000) / 1000
18
+ return round(moment.timestamp() * 1_000_000) / 1000
19
19
  except (ValueError, OSError, OverflowError):
20
20
  pass
21
21
  return None
@@ -180,17 +180,20 @@ def overview(session_dir, capture_id=None, offset=0, limit=100, trace_id=None):
180
180
  ordered = sorted(
181
181
  items,
182
182
  key=lambda item: (
183
- _milliseconds(item.get("start_time")) is None,
184
- _milliseconds(item.get("start_time")) if _milliseconds(item.get("start_time")) is not None else inf,
183
+ timestamp_ms(item.get("start_time")) is None,
184
+ timestamp_ms(item.get("start_time")) if timestamp_ms(item.get("start_time")) is not None else inf,
185
185
  str(item.get("trace_id") or ""),
186
186
  str(item.get("id") or ""),
187
187
  ),
188
188
  )
189
- session_start = next((_milliseconds(item.get("start_time")) for item in ordered if _milliseconds(item.get("start_time")) is not None), None)
189
+ session_start = next(
190
+ (timestamp_ms(item.get("start_time")) for item in ordered if timestamp_ms(item.get("start_time")) is not None),
191
+ None,
192
+ )
190
193
 
191
194
  def row(item):
192
195
  summary = _summary(item)
193
- at = _milliseconds(item.get("start_time"))
196
+ at = timestamp_ms(item.get("start_time"))
194
197
  summary["node_id"] = item.get("id")
195
198
  summary["parent_id"] = item.get("parent_id")
196
199
  summary["session_offset_ms"] = None if session_start is None or at is None else at - session_start
@@ -138,6 +138,10 @@ def prepare_issue(session_dir, data, *, dry_run=False):
138
138
  data = copy.deepcopy(data)
139
139
  if not isinstance(data, dict):
140
140
  raise UserActionRequired("Issue data must be an object.")
141
+ if "selection_reason" in data and (
142
+ not isinstance(data["selection_reason"], str) or not data["selection_reason"].strip()
143
+ ):
144
+ raise UserActionRequired("Issue selection_reason must be a non-empty string when supplied.")
141
145
  issue_id = data.get("id", data.get("issue_id"))
142
146
  if issue_id is None:
143
147
  number = 1
@@ -215,6 +219,7 @@ def prepare_issue(session_dir, data, *, dry_run=False):
215
219
  "updated_at",
216
220
  "title",
217
221
  "description",
222
+ "selection_reason",
218
223
  "evidence",
219
224
  "segments",
220
225
  "status",
@@ -331,6 +336,8 @@ def save_diagnosis(session_dir, issue_id, text, diagnosis_id=None):
331
336
  "segments": issue.get("segments", []),
332
337
  "created_at": now(),
333
338
  }
339
+ if "selection_reason" in issue:
340
+ snapshot["selection_reason"] = issue["selection_reason"]
334
341
  _refs(directory, issue["evidence"])
335
342
  for segment in snapshot["segments"]:
336
343
  _refs(directory, segment["evidence"])
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections import defaultdict
6
- from datetime import datetime
7
6
  from math import inf
8
7
 
9
8
  from agent_tune_kit.errors import UserActionRequired
@@ -12,17 +11,6 @@ from . import query
12
11
  from .store import validate_ref
13
12
 
14
13
 
15
- def _milliseconds(value):
16
- if isinstance(value, str):
17
- try:
18
- moment = datetime.fromisoformat(value)
19
- if moment.utcoffset() is not None:
20
- return round(moment.timestamp() * 1_000_000) / 1000
21
- except (ValueError, OSError, OverflowError):
22
- pass
23
- return None
24
-
25
-
26
14
  def _node_pointer(pointer):
27
15
  if not isinstance(pointer, str) or not pointer:
28
16
  return "", ""
@@ -74,8 +62,8 @@ def _index_nodes(session_dir):
74
62
  if isinstance(identity, (str, int)):
75
63
  slot = (node.get("trace_id"), identity)
76
64
  by_identity[slot] = index if slot not in by_identity else None
77
- at = _milliseconds(node.get("start_time"))
78
- until = _milliseconds(node.get("end_time"))
65
+ at = query.timestamp_ms(node.get("start_time"))
66
+ until = query.timestamp_ms(node.get("end_time"))
79
67
  if until is None or at is None or until < at:
80
68
  until = at
81
69
  node["at"] = at
@@ -218,9 +206,7 @@ def resolve_segments(session_dir, segments, *, assign_ids=True):
218
206
  raise UserActionRequired(f"Segment {offset} from is later than to.")
219
207
  low, high = rank[start_i], rank[end_i]
220
208
  covered = {
221
- i
222
- for i in order[low : high + 1]
223
- if nodes[i].get("trace_id") == nodes[start_i].get("trace_id")
209
+ i for i in order[low : high + 1] if nodes[i].get("trace_id") == nodes[start_i].get("trace_id")
224
210
  }
225
211
  else:
226
212
  lo, hi = start_at, end_at
@@ -8,12 +8,16 @@
8
8
  const button = (text, action, className) => { const node = el("button", text, className); node.type = "button"; node.onclick = action; return node; };
9
9
  const pre = value => el("pre", pretty(value));
10
10
  const section = (title, value) => { const node = el("section", undefined, "detail-section"); node.append(el("h3", title)); if (value !== undefined) node.append(pre(value)); return node; };
11
+ const markdown = window.markdownit({html:false});
12
+ markdown.renderer.rules.image = (tokens, index) => markdown.utils.escapeHtml(tokens[index].content);
13
+ const sourceLinks = new Map(Object.entries(data.diagnosis_source_links).map(([href, target]) => [markdown.normalizeLink(href), target]));
11
14
  const options = (select, values, all) => { select.replaceChildren(); if (all !== undefined) { const item = el("option", all); item.value = ""; select.append(item); } for (const value of values) { const item = el("option", value); item.value = value; select.append(item); } };
12
15
  const references = value => { const result = []; const seen = new Set(); const walk = item => { if (Array.isArray(item)) item.forEach(walk); else if (item && typeof item === "object") { if (typeof item.path === "string" && typeof item.sha256 === "string") { const key = refKey(item); if (!seen.has(key)) { seen.add(key); result.push(item); } } else Object.values(item).forEach(walk); } }; walk(value); return result; };
13
16
  const RAIL_KEY = "atk-trace-rail-collapsed";
14
17
  let currentCapture = data.captures.at(-1) || null;
15
18
  let returnFocus = null, activeIssue = null, issueStage = "issue", drawerMode = null;
16
19
  let selectedNode = null, origin = null, issueTrigger = null;
20
+ let returnToDiagnosis = null;
17
21
  let investigation = null, scopeBefore = null, previewIssue = null, expandedFolds = new Set(), railFilter = null;
18
22
  const captureById = new Map(data.captures.map(c => [c.id, c]));
19
23
  const issueById = new Map(data.issues.map(item => [item.record.id, item]));
@@ -107,6 +111,7 @@
107
111
  $("drawer").hidden = true;
108
112
  drawerMode = null;
109
113
  activeIssue = null; origin = null;
114
+ returnToDiagnosis = null;
110
115
  if (restoreFocus && returnFocus?.isConnected) returnFocus.focus({preventScroll:true});
111
116
  else if (restoreFocus && investigation) $("fragment-fields").focus({preventScroll:true});
112
117
  else if (restoreFocus && issueTrigger) document.querySelector(`[data-issue-id="${CSS.escape(issueTrigger)}"]`)?.focus({preventScroll:true});
@@ -171,10 +176,8 @@
171
176
  const content = record.record || record.text;
172
177
  if (content !== undefined) {
173
178
  if (typeof content === "string") {
174
- const body = el("div");
175
- body.append(pre(content));
176
- linkSegmentCites(body, content);
177
- card.append(body);
179
+ card.firstElementChild.classList.add("diagnosis-path");
180
+ card.append(renderDiagnosis(content));
178
181
  } else {
179
182
  const summary = [];
180
183
  for (const key of ["status", "target_issues", "candidate_loaded"]) if (content[key] !== undefined) summary.push(`${key}: ${pretty(content[key])}`);
@@ -189,22 +192,114 @@
189
192
  appendReferences(card, record);
190
193
  return card;
191
194
  };
192
- function linkSegmentCites(container, text) {
193
- const matches = String(text || "").match(/ISSUE-\d+\/S\d+/g) || [];
194
- if (!matches.length) return;
195
- const box = el("p", undefined, "breadcrumb");
196
- box.append(el("span", "片段引用:", "muted"));
197
- for (const cite of [...new Set(matches)]) {
198
- box.append(button(cite, () => {
199
- const [issueId, seg] = cite.split("/");
200
- const item = issueById.get(issueId);
201
- if (!item) return;
202
- startIssue(item, item.fragments.findIndex(part => part.id === seg));
203
- }));
195
+ function appendDiagnoses(item) {
196
+ const container = $("drawer-content");
197
+ if (!item.diagnoses.length) {
198
+ container.append(el("p", "尚未记录诊断与修复/验证方案。", "empty"));
199
+ return;
200
+ }
201
+ for (const [index, diagnosis] of item.diagnoses.slice().reverse().entries()) {
202
+ const body = diagnosis.error ? el("p", diagnosis.error, "warning") : renderDiagnosis(diagnosis.text);
203
+ const headings = [...body.children].filter(node => node.tagName === "H2").map(node => node.textContent);
204
+ const structured = headings.join("|") === "异常描述|异常根因|最小修复方案"
205
+ || headings.join("|") === "Issue description|Root cause|Minimal fix";
206
+ if (index === 0 && structured) {
207
+ container.append(body);
208
+ continue;
209
+ }
210
+ if (index === 0) {
211
+ const description = section("异常描述");
212
+ description.append(el("p", item.record.description || item.record.title));
213
+ for (const [position, part] of item.fragments.entries()) {
214
+ const cite = button(part.id || `片段 ${position + 1}`, () => openDiagnosisSegment(item, position, cite), "diagnosis-cite");
215
+ description.append(cite);
216
+ }
217
+ container.append(description);
218
+ }
219
+ const details = el("details", undefined, "detail-section");
220
+ const name = diagnosis.reference.path.split("/").pop();
221
+ details.append(el("summary", `展开原诊断 · ${name}`), body);
222
+ container.append(details);
204
223
  }
205
- container.prepend(box);
224
+ }
225
+ function renderDiagnosis(text) {
226
+ const body = el("div", undefined, "diagnosis-markdown");
227
+ const template = document.createElement("template");
228
+ template.innerHTML = markdown.render(text);
229
+ for (const link of template.content.querySelectorAll("a[href]")) {
230
+ const href = link.getAttribute("href");
231
+ const source = location.protocol === "file:" && sourceLinks.get(href);
232
+ if (source || /^https?:\/\//i.test(href)) {
233
+ if (source) link.setAttribute("href", source);
234
+ link.target = "_blank";
235
+ link.rel = "noopener noreferrer";
236
+ } else {
237
+ let path = href;
238
+ try { path = decodeURI(href); } catch { /* Keep malformed escape sequences as text. */ }
239
+ const locationText = el("code", path);
240
+ locationText.hidden = true;
241
+ const copy = button("复制路径", async () => {
242
+ try { await navigator.clipboard.writeText(path); copy.textContent = "已复制"; }
243
+ catch { locationText.hidden = false; }
244
+ });
245
+ link.replaceWith(...link.childNodes, " ", el("code", path.split("/").pop() || path), copy, locationText);
246
+ }
247
+ }
248
+ for (const table of template.content.querySelectorAll("table")) {
249
+ const scroll = el("div", undefined, "diagnosis-table");
250
+ scroll.tabIndex = 0;
251
+ scroll.setAttribute("aria-label", "诊断表格");
252
+ table.before(scroll);
253
+ scroll.append(table);
254
+ }
255
+ body.append(template.content);
256
+ const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
257
+ const nodes = [];
258
+ while (walker.nextNode()) {
259
+ const node = walker.currentNode;
260
+ if (node.data.includes("ISSUE-") && !node.parentElement.closest("pre, a")) nodes.push(node);
261
+ }
262
+ for (const node of nodes) {
263
+ const parts = document.createDocumentFragment();
264
+ let offset = 0;
265
+ for (const match of node.data.matchAll(/\b(ISSUE-\d+)\/(S\d+)\b/g)) {
266
+ const item = issueById.get(match[1]);
267
+ const index = item?.fragments.findIndex(part => part.id === match[2]);
268
+ if (index === undefined || index < 0) continue;
269
+ const cite = button(match[0], () => openDiagnosisSegment(item, index, cite), "diagnosis-cite");
270
+ parts.append(node.data.slice(offset, match.index), cite);
271
+ offset = match.index + match[0].length;
272
+ }
273
+ if (offset) {
274
+ parts.append(node.data.slice(offset));
275
+ node.replaceWith(parts);
276
+ }
277
+ }
278
+ return body;
279
+ }
280
+ function openDiagnosisSegment(item, index, trigger) {
281
+ const drawerContent = $("drawer-content");
282
+ const sourceIssue = activeIssue, view = rememberView(), priorScope = scopeBefore;
283
+ const previous = investigation && {...investigation};
284
+ const top = drawerContent.scrollTop;
285
+ const citeIndex = [...drawerContent.querySelectorAll(".diagnosis-cite")].indexOf(trigger);
286
+ returnToDiagnosis = () => {
287
+ investigation = previous;
288
+ scopeBefore = priorScope;
289
+ restoreView(view);
290
+ showIssue(sourceIssue, "diagnoses");
291
+ const restored = drawerContent.querySelectorAll(".diagnosis-cite")[citeIndex];
292
+ const details = restored?.closest("details");
293
+ if (details) details.open = true;
294
+ drawerContent.scrollTop = top;
295
+ restored?.focus({preventScroll:true});
296
+ };
297
+ startIssue(item, index);
298
+ origin = null;
299
+ showFragmentEvidence();
206
300
  }
207
301
  const showIssue = (item, stage = "issue") => {
302
+ returnToDiagnosis = null;
208
303
  if (!activeIssue || activeIssue.record.id !== item.record.id) {
209
304
  origin = null;
210
305
  issueTrigger = item.record.id;
@@ -222,6 +317,11 @@
222
317
  const description = section("问题描述");
223
318
  description.append(el("p", issue.description || issue.title));
224
319
  $("drawer-content").append(description);
320
+ if (typeof issue.selection_reason === "string" && issue.selection_reason.trim()) {
321
+ const reason = section("保留理由");
322
+ reason.append(el("p", issue.selection_reason));
323
+ $("drawer-content").append(reason);
324
+ }
225
325
  const segments = issue.segments || [];
226
326
  if (segments.length) {
227
327
  const box = section("关联片段");
@@ -245,8 +345,7 @@
245
345
  return;
246
346
  }
247
347
  if (stage === "diagnoses") {
248
- if (!item.diagnoses.length) $("drawer-content").append(el("p", "尚未记录诊断与修复/验证方案。", "empty"));
249
- for (const diagnosis of item.diagnoses) $("drawer-content").append(recordCard(diagnosis.reference.path, diagnosis));
348
+ appendDiagnoses(item);
250
349
  } else if (stage === "changes") {
251
350
  if (!item.changes.length) $("drawer-content").append(el("p", "尚未记录实际修改。局部检查不代表 Agent 效果验证。", "empty"));
252
351
  for (const change of item.changes) $("drawer-content").append(recordCard(change.reference.path, change));
@@ -784,6 +883,7 @@
784
883
  const part = item.fragments[index];
785
884
  activeIssue = item; issueStage = "fragment"; issueTrigger = item.record.id;
786
885
  openDrawer(`${item.record.id} · ${part.id || `片段 ${index+1}`} 的证据`, "issue");
886
+ if (returnToDiagnosis) $("drawer-content").append(button("← 返回诊断", returnToDiagnosis));
787
887
  if (part.summary) $("drawer-content").append(el("p", part.summary, "muted"));
788
888
  function add(title, positions) {
789
889
  if (!positions?.length) return;
@@ -828,7 +928,7 @@
828
928
  box.append(note);
829
929
  }
830
930
  if (!filtered.length) {
831
- box.append(el("p", data.issues.length ? "没有符合筛选条件的问题。" : data.session.detections.length ? "已保存检测记录,尚无已记录问题。" : "尚未进行异常识别。", "empty"));
931
+ box.append(el("p", data.issues.length ? "没有符合筛选条件的问题。" : data.session.detections.length ? "已保存检测记录,尚无保留问题;请查看检查范围与缺口。" : "尚未进行异常识别。", "empty"));
832
932
  return;
833
933
  }
834
934
  for (const item of filtered) {
@@ -78,6 +78,7 @@
78
78
  </aside>
79
79
  <script id="trace-data" type="application/json">__DATA__</script>
80
80
  <script>__LANES__</script>
81
+ <script>__MARKDOWN__</script>
81
82
  <script>__APP__</script>
82
83
  </body>
83
84
  </html>
@@ -226,6 +226,19 @@ select:hover, .toolbar input:hover { border-color: #9baebb }
226
226
  .detail-section { background: white; padding: 12px; border: 1px solid var(--line); border-radius: 7px; margin-block: 10px }
227
227
  .detail-section h3 { margin-bottom: 6px }
228
228
  .detail-section button { font-size: 12px; margin: 2px }
229
+ .detail-section .diagnosis-path { color: var(--muted); font-size: 12px; font-weight: 500 }
230
+ .diagnosis-markdown { line-height: 1.65 }
231
+ .diagnosis-markdown h1 { font-size: 19px; line-height: 1.4; margin: 12px 0 }
232
+ .diagnosis-markdown h2 { font-size: 16px; margin: 20px 0 8px }
233
+ .diagnosis-markdown p, .diagnosis-markdown ul, .diagnosis-markdown ol { margin: 8px 0 }
234
+ .diagnosis-markdown ul, .diagnosis-markdown ol { padding-inline-start: 24px }
235
+ .diagnosis-markdown li + li { margin-top: 6px }
236
+ .diagnosis-markdown blockquote { margin: 10px 0; padding-inline-start: 12px; border-inline-start: 3px solid var(--line) }
237
+ .diagnosis-table { overflow-x: auto; margin-block: 12px }
238
+ .diagnosis-markdown table { border-collapse: collapse; width: 100%; font-size: 13px }
239
+ .diagnosis-markdown th, .diagnosis-markdown td { border: 1px solid var(--line); padding: 6px 8px; vertical-align: top }
240
+ .diagnosis-markdown th { background: var(--bg); text-align: start }
241
+ .diagnosis-markdown .diagnosis-cite { padding: 1px 5px; color: var(--accent); font: inherit; font-size: .88em; white-space: nowrap }
229
242
  .evidence-link { display: block; width: 100%; text-align: left; white-space: normal; overflow-wrap: anywhere }
230
243
  .evidence-link strong, .evidence-link code { display: block }
231
244
  .evidence-link code, #evidence-field>code { font-size: 12px; color: var(--muted) }