agent-tune-kit 2.3.2__tar.gz → 2.3.3__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 (180) hide show
  1. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/.codex-plugin/plugin.json +1 -1
  2. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/PKG-INFO +10 -3
  3. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/README.en.md +9 -2
  4. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/README.md +9 -2
  5. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/__init__.py +1 -1
  6. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/trace.py +17 -0
  7. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/archive.py +1 -1
  8. agent_tune_kit-2.3.3/agent_tune_kit/traces/projects.py +201 -0
  9. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/records.py +23 -2
  10. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/store.py +1 -0
  11. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/trace/app.js +26 -1
  12. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/trace/page.html +2 -1
  13. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/traces/app.js +87 -28
  14. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/traces/page.html +13 -10
  15. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/traces/styles.css +32 -10
  16. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/trace.py +15 -2
  17. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/traces.py +12 -0
  18. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/trace-dashboard-design.md +2 -2
  19. agent_tune_kit-2.3.3/docs/trace-project-attribution-design.md +244 -0
  20. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/trace-tuning.md +15 -3
  21. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/pyproject.toml +1 -1
  22. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/validate_skill_pack.py +1 -1
  23. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-trace-detect/SKILL.md +10 -0
  24. agent_tune_kit-2.3.3/skills/atk-trace-diagnose/SKILL.md +81 -0
  25. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-trace-init/SKILL.md +12 -0
  26. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-trace-optimize/SKILL.md +10 -0
  27. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-trace-verify/SKILL.md +10 -0
  28. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-visualize-trace/SKILL.md +10 -0
  29. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_install_plugin.py +1 -1
  30. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_release_scripts.py +1 -1
  31. agent_tune_kit-2.3.3/tests/test_trace_projects.py +57 -0
  32. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/trace-dashboard.cjs +97 -10
  33. agent_tune_kit-2.3.2/skills/atk-trace-diagnose/SKILL.md +0 -62
  34. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/.gitignore +0 -0
  35. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/__init__.py +0 -0
  36. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/__main__.py +0 -0
  37. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/dataset.py +0 -0
  38. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/evaluation.py +0 -0
  39. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/main.py +0 -0
  40. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/output.py +0 -0
  41. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/results.py +0 -0
  42. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/cli/visualize.py +0 -0
  43. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/datasets/__init__.py +0 -0
  44. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/datasets/prepare.py +0 -0
  45. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/datasets/truth.py +0 -0
  46. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/datasets/updates.py +0 -0
  47. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/datasets/validation.py +0 -0
  48. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/errors.py +0 -0
  49. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/execution/__init__.py +0 -0
  50. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/execution/calibration.py +0 -0
  51. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/execution/logs.py +0 -0
  52. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/execution/rules.py +0 -0
  53. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/execution/runner.py +0 -0
  54. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/execution/runtime.py +0 -0
  55. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/execution/tracing.py +0 -0
  56. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/installer.py +0 -0
  57. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/results/__init__.py +0 -0
  58. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/results/assessment.py +0 -0
  59. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/results/evidence.py +0 -0
  60. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/results/judgments.py +0 -0
  61. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/results/summary.py +0 -0
  62. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/results/versions.py +0 -0
  63. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/__init__.py +0 -0
  64. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/capture.py +0 -0
  65. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/categories.py +0 -0
  66. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/langfuse.py +0 -0
  67. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/migrate.py +0 -0
  68. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/progress.py +0 -0
  69. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/query.py +0 -0
  70. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/traces/segments.py +0 -0
  71. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/utils/__init__.py +0 -0
  72. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/utils/csv_io.py +0 -0
  73. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/utils/files.py +0 -0
  74. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/__init__.py +0 -0
  75. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/dataset/app.js +0 -0
  76. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/dataset/page.html +0 -0
  77. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/dataset/styles.css +0 -0
  78. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/dataset/vendor/echarts.min.js +0 -0
  79. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/failures/app.js +0 -0
  80. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/failures/page.html +0 -0
  81. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/failures/styles.css +0 -0
  82. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/shared/base.css +0 -0
  83. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/trace/lanes.js +0 -0
  84. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/trace/styles.css +0 -0
  85. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/trace/vendor/markdown-it.LICENSE +0 -0
  86. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/assets/trace/vendor/markdown-it.min.js +0 -0
  87. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/dataset.py +0 -0
  88. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/failures.py +0 -0
  89. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/fields.py +0 -0
  90. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/history.py +0 -0
  91. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/render.py +0 -0
  92. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/visualize/timeline.py +0 -0
  93. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/agent_tune_kit/workspace.py +0 -0
  94. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/assets/agent_tune_kit_evaluation.png +0 -0
  95. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/assets/agent_tune_kit_trace.png +0 -0
  96. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/assets/arch-en.png +0 -0
  97. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/assets/arch.png +0 -0
  98. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/assets/architecture.en.svg +0 -0
  99. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/assets/architecture.svg +0 -0
  100. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/ato-atk-project-boundary-recommendation-2026-11-05.md +0 -0
  101. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/cli-skill-modularization-plan.md +0 -0
  102. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/codex_agent_tuning_prd.md +0 -0
  103. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/execution-evidence-and-tracing-plan.md +0 -0
  104. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/ground-truth-and-grading-assessment-2026-10-25.md +0 -0
  105. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/local-context.md +0 -0
  106. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/shared-versioning-and-confirmation.md +0 -0
  107. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/skill-template-pack-usage.md +0 -0
  108. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/surginame-atk-capability-review-2026-10-27.md +0 -0
  109. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/trace-change-verification-readability-diagnosis.md +0 -0
  110. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/trace-swimlane-design.md +0 -0
  111. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/trace-tuning-validation.md +0 -0
  112. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/docs/truth-and-assessment.md +0 -0
  113. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/check-release.py +0 -0
  114. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/dev-refresh-install.sh +0 -0
  115. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/install_plugin.py +0 -0
  116. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/publish-pypi.sh +0 -0
  117. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/publish-release.py +0 -0
  118. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/release-version.py +0 -0
  119. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/scripts/release-version.sh +0 -0
  120. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-dataset/SKILL.md +0 -0
  121. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-dataset/scripts/update_dataset.py +0 -0
  122. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-diagnose/SKILL.md +0 -0
  123. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-eval/SKILL.md +0 -0
  124. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-init/SKILL.md +0 -0
  125. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-optimize/SKILL.md +0 -0
  126. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-report/SKILL.md +0 -0
  127. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-trace-capture/SKILL.md +0 -0
  128. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-visualize-dataset/SKILL.md +0 -0
  129. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-visualize-dataset/scripts/generate_dataset_browser.py +0 -0
  130. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-visualize-failures/SKILL.md +0 -0
  131. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/skills/atk-visualize-failures/scripts/generate_failure_browser.py +0 -0
  132. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/templates/.atk/runner/eval_runner.py.md +0 -0
  133. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/templates/.atk/runner/failure_rule.py.md +0 -0
  134. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/templates/.atk/runner/import_evidence.py.md +0 -0
  135. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/templates/.gitkeep +0 -0
  136. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/__init__.py +0 -0
  137. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/conftest.py +0 -0
  138. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/fixtures/legacy_eval_runner.py.txt +0 -0
  139. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/fixtures/managed_v1.py.md +0 -0
  140. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_assessment_contract.py +0 -0
  141. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_atk_init_contract.py +0 -0
  142. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_cli_workflow.py +0 -0
  143. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_dataset_updates.py +0 -0
  144. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_diagnosis.py +0 -0
  145. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_evidence_import.py +0 -0
  146. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_evidence_judgments.py +0 -0
  147. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_evidence_packaging.py +0 -0
  148. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_evidence_versions.py +0 -0
  149. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_execution_evidence.py +0 -0
  150. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_execution_outcomes.py +0 -0
  151. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_execution_tracing.py +0 -0
  152. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_generate_dataset_browser.py +0 -0
  153. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_generate_failure_browser.py +0 -0
  154. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_judge_calibration.py +0 -0
  155. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_local_context.py +0 -0
  156. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_plugin_cache.py +0 -0
  157. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_runner_output_columns.py +0 -0
  158. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_runner_template_row_logging.py +0 -0
  159. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_runtime_contracts.py +0 -0
  160. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_snapshot_reads.py +0 -0
  161. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_capture.py +0 -0
  162. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_categories.py +0 -0
  163. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_cli.py +0 -0
  164. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_langfuse.py +0 -0
  165. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_progress.py +0 -0
  166. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_query.py +0 -0
  167. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_records.py +0 -0
  168. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_segments.py +0 -0
  169. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_trace_store.py +0 -0
  170. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_truth_browser.py +0 -0
  171. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_truth_cli.py +0 -0
  172. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_truth_contract.py +0 -0
  173. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_truth_runner.py +0 -0
  174. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_v2_progress.py +0 -0
  175. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_visualize_trace.py +0 -0
  176. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/test_visualize_traces.py +0 -0
  177. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/trace-event-wheel.cjs +0 -0
  178. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/trace-preview-scroll.cjs +0 -0
  179. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/trace-selection-reason.cjs +0 -0
  180. {agent_tune_kit-2.3.2 → agent_tune_kit-2.3.3}/tests/trace-stage-cards.cjs +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-tune-kit",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
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.2
3
+ Version: 2.3.3
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
@@ -171,15 +171,18 @@ These stages also refresh the project dashboard `.atk/traces/index.html`, which
171
171
 
172
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.
173
173
 
174
- ### 3. Investigate, Fix, and Verify an Issue as Needed
174
+ ### 3. Investigate a Session or Specific Issues, Then Fix and Verify as Needed
175
175
 
176
176
  ```text
177
+ $atk-trace-diagnose Investigate session <session-id> and propose repair and verification plans.
177
178
  $atk-trace-diagnose Investigate ISSUE-001 in session <session-id> and propose a repair and verification plan.
178
179
  $atk-trace-optimize Implement the selected plan for ISSUE-001 in session <session-id>.
179
180
  $atk-trace-verify Verify the candidate for ISSUE-001 in session <session-id> and check relevant behavior that should still work.
180
181
  ```
181
182
 
182
- Start with diagnosis when you already have a specific issue and session evidence. Each verification records a conclusion per issue. If the evidence cannot establish which candidate loaded or whether key verification conditions were met, the outcome is `inconclusive`. As new issues surface, work through them individually or start another analysis with a new session trace.
183
+ Without specified issues, diagnosis reviews all available session traces and works through existing and newly found anomalies, prioritizing causes directly related to project code, configuration, tool contracts, Prompt or Skill. Findings established as unrelated to the existing implementation may be skipped with an explanation. Findings with uncertain relevance remain open for investigation. Specified issues or concrete symptoms limit the investigation scope. You can start directly with diagnosis without a separate detection invocation.
184
+
185
+ Diagnoses and verification conclusions are saved per issue. If the evidence cannot establish which candidate loaded or whether key verification conditions were met, the verification outcome is `inconclusive`. Investigation results identify unread ranges and evidence gaps. As new issues surface, work through them individually or start another analysis with a new session trace.
183
186
 
184
187
  ### Where to Find Trace Outputs
185
188
 
@@ -192,3 +195,7 @@ Each session archive is stored under `.atk/traces/<session_id>/` in your Agent p
192
195
  | `issues/<issue-id>/` | Issue evidence, diagnoses, and changes. Each verification's `result.json` records its status, conclusion, and evidence references |
193
196
 
194
197
  [Trace tuning guide for source configuration, supported formats, and archive layout](docs/trace-tuning.md)
198
+
199
+ [Multi-project attribution and filtering proposal (not yet implemented, Chinese)](docs/trace-project-attribution-design.md)
200
+
201
+ Use `atk trace project` to register workspace projects and submodules. Issues can have multiple root-cause projects, filterable in the dashboard and session issue list. Repair details describe modification locations.
@@ -157,15 +157,18 @@ These stages also refresh the project dashboard `.atk/traces/index.html`, which
157
157
 
158
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.
159
159
 
160
- ### 3. Investigate, Fix, and Verify an Issue as Needed
160
+ ### 3. Investigate a Session or Specific Issues, Then Fix and Verify as Needed
161
161
 
162
162
  ```text
163
+ $atk-trace-diagnose Investigate session <session-id> and propose repair and verification plans.
163
164
  $atk-trace-diagnose Investigate ISSUE-001 in session <session-id> and propose a repair and verification plan.
164
165
  $atk-trace-optimize Implement the selected plan for ISSUE-001 in session <session-id>.
165
166
  $atk-trace-verify Verify the candidate for ISSUE-001 in session <session-id> and check relevant behavior that should still work.
166
167
  ```
167
168
 
168
- Start with diagnosis when you already have a specific issue and session evidence. Each verification records a conclusion per issue. If the evidence cannot establish which candidate loaded or whether key verification conditions were met, the outcome is `inconclusive`. As new issues surface, work through them individually or start another analysis with a new session trace.
169
+ Without specified issues, diagnosis reviews all available session traces and works through existing and newly found anomalies, prioritizing causes directly related to project code, configuration, tool contracts, Prompt or Skill. Findings established as unrelated to the existing implementation may be skipped with an explanation. Findings with uncertain relevance remain open for investigation. Specified issues or concrete symptoms limit the investigation scope. You can start directly with diagnosis without a separate detection invocation.
170
+
171
+ Diagnoses and verification conclusions are saved per issue. If the evidence cannot establish which candidate loaded or whether key verification conditions were met, the verification outcome is `inconclusive`. Investigation results identify unread ranges and evidence gaps. As new issues surface, work through them individually or start another analysis with a new session trace.
169
172
 
170
173
  ### Where to Find Trace Outputs
171
174
 
@@ -178,3 +181,7 @@ Each session archive is stored under `.atk/traces/<session_id>/` in your Agent p
178
181
  | `issues/<issue-id>/` | Issue evidence, diagnoses, and changes. Each verification's `result.json` records its status, conclusion, and evidence references |
179
182
 
180
183
  [Trace tuning guide for source configuration, supported formats, and archive layout](docs/trace-tuning.md)
184
+
185
+ [Multi-project attribution and filtering proposal (not yet implemented, Chinese)](docs/trace-project-attribution-design.md)
186
+
187
+ Use `atk trace project` to register workspace projects and submodules. Issues can have multiple root-cause projects, filterable in the dashboard and session issue list. Repair details describe modification locations.
@@ -157,15 +157,18 @@ $atk-visualize-trace 打开 <session-id> 的执行泳道,从问题跳到相关
157
157
 
158
158
  可用 Trace ID 定位会话内的证据,在问题详情中查看保留理由。分享 HTML 前请核对其中留存的正文。
159
159
 
160
- ### 3. 按问题调查、修复和验证(按需)
160
+ ### 3. 调查会话或指定问题,按需修复和验证
161
161
 
162
162
  ```text
163
+ $atk-trace-diagnose 调查 <session-id>,给出修复和验证方案。
163
164
  $atk-trace-diagnose 调查 <session-id> 的 ISSUE-001,给出修复和验证方案。
164
165
  $atk-trace-optimize 实施 <session-id> 的 ISSUE-001 已选定的方案。
165
166
  $atk-trace-verify 验证 <session-id> 的 ISSUE-001 对应候选,并检查相关正常行为。
166
167
  ```
167
168
 
168
- 已有明确问题和会话证据时,可以从调查开始。每次验证按问题保存结论。证据不足以确认实际加载的候选或关键验证条件时,效果记为 `inconclusive`(无法判断)。后续发现新的问题,可继续按问题处理,或用新会话的轨迹开始下一次分析。
169
+ 未指定异常时,调查默认检查会话中可用的全部轨迹,逐项处理已记录和新发现的异常,优先定位与项目代码、配置、工具契约、Prompt 或 Skill 直接相关的原因。证据确认与现有实现无关的异常可以忽略,并说明理由。关联尚不明确的异常保留待查。指定异常或具体症状时,按指定范围调查。可以直接从调查开始,无需先单独执行检测。
170
+
171
+ 诊断和验证结论分别按问题保存。证据不足以确认实际加载的候选或关键验证条件时,验证效果记为 `inconclusive`(无法判断)。调查结果会说明未检查的范围和证据缺口。后续发现新的问题,可继续按问题处理,或用新会话的轨迹开始下一次分析。
169
172
 
170
173
  ### 查看轨迹产物
171
174
 
@@ -178,3 +181,7 @@ $atk-trace-verify 验证 <session-id> 的 ISSUE-001 对应候选,并检查相
178
181
  | `issues/<issue-id>/` | 查看问题证据、诊断和改动。各次验证的 `result.json` 记录验证状态、结论和证据引用 |
179
182
 
180
183
  [轨迹调优指南,包含来源配置、支持格式和归档结构](docs/trace-tuning.md)
184
+
185
+ [多工程关联与项目筛选方案(待实现)](docs/trace-project-attribution-design.md)
186
+
187
+ 支持通过 `atk trace project` 登记工作区及子模块;诊断可记录多个根因项目,大盘与会话问题栏按根因项目筛选。修改位置在修复详情中说明。
@@ -1,3 +1,3 @@
1
1
  """Agent Tune Kit package."""
2
2
 
3
- __version__ = "2.3.2"
3
+ __version__ = "2.3.3"
@@ -34,6 +34,13 @@ 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
+ project = actions.add_parser("project", help="discover, list or update workspace projects")
38
+ project.add_argument("--project-root", default=".")
39
+ mode = project.add_mutually_exclusive_group(required=True)
40
+ mode.add_argument("--discover", action="store_true")
41
+ mode.add_argument("--list", action="store_true")
42
+ mode.add_argument("--data")
43
+ project.add_argument("--json", action="store_true")
37
44
  category = actions.add_parser("category", help="list or update the project issue category vocabulary")
38
45
  category.add_argument("--project-root", default=".")
39
46
  mode = category.add_mutually_exclusive_group(required=True)
@@ -100,9 +107,18 @@ def register(subparsers):
100
107
  parser.add_argument("--issue-id", required=True)
101
108
  parser.add_argument("--file", required=True, help="root cause and repair/verification plan Markdown")
102
109
  parser.add_argument("--diagnosis-id")
110
+ parser.add_argument("--expected-revision", type=int)
103
111
 
104
112
 
105
113
  def execute(args):
114
+ if args.action == "project":
115
+ from agent_tune_kit.traces.projects import discover_projects, load_projects, save_project
116
+
117
+ if args.discover:
118
+ return discover_projects(args.project_root)
119
+ if args.list:
120
+ return load_projects(store.trace_root(args.project_root))
121
+ return save_project(args.project_root, read_json(args.data))
106
122
  if args.action == "init":
107
123
  text = Path(args.project_file).read_text(encoding="utf-8") if args.project_file else None
108
124
  return store.init_workspace(args.project_root, config=read_json(args.config), project_text=text)
@@ -162,6 +178,7 @@ def execute(args):
162
178
  args.issue_id,
163
179
  Path(args.file).read_text(encoding="utf-8"),
164
180
  diagnosis_id=args.diagnosis_id,
181
+ expected_revision=args.expected_revision,
165
182
  )
166
183
  data = read_json(args.data)
167
184
  if args.action == "issue":
@@ -19,7 +19,7 @@ def component(value):
19
19
  raise UserActionRequired("An explicit nonempty native ID is required.")
20
20
  name = quote(value, safe="")
21
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"}
22
+ reserved = {"project.md", "config.json", "categories.json", "projects.json", "index.html", "_export_summary"}
23
23
  if name.startswith(".") or name.casefold() in reserved:
24
24
  name = "%" + format(ord(name[0]), "02X") + name[1:]
25
25
  if len(name) > 200:
@@ -0,0 +1,201 @@
1
+ """Workspace project registry and evidence-backed attribution."""
2
+
3
+ import configparser
4
+ import hashlib
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from agent_tune_kit.errors import UserActionRequired
9
+ from agent_tune_kit.results.evidence import safe_path, write_manifest
10
+
11
+ from .store import ensure_workspace, read_json, session_write_lock, validate_ref
12
+
13
+
14
+ def load_projects(root):
15
+ path = safe_path(Path(root), "projects.json")
16
+ if not path.exists():
17
+ return {"schema_version": 1, "revision": 0, "projects": []}
18
+ value = read_json(path)
19
+ if (
20
+ value.get("schema_version") != 1
21
+ or type(value.get("revision")) is not int
22
+ or not isinstance(value.get("projects"), list)
23
+ ):
24
+ raise UserActionRequired("Invalid project registry.")
25
+ seen = set()
26
+ for item in value["projects"]:
27
+ if (
28
+ not isinstance(item, dict)
29
+ or any(not isinstance(item.get(k), str) or not item[k] for k in ("id", "name", "path"))
30
+ or item["id"] in seen
31
+ ):
32
+ raise UserActionRequired("Invalid project entry.")
33
+ seen.add(item["id"])
34
+ return value
35
+
36
+
37
+ def project_map(root):
38
+ try:
39
+ return {item["id"]: item for item in load_projects(root)["projects"]}
40
+ except (UserActionRequired, KeyError, TypeError):
41
+ return {}
42
+
43
+
44
+ def save_project(project_root, data):
45
+ if not isinstance(data, dict) or not isinstance(data.get("id"), str) or not data["id"].strip():
46
+ raise UserActionRequired("Project needs a stable id.")
47
+ root, _ = ensure_workspace(project_root)
48
+ with session_write_lock(root):
49
+ catalog = load_projects(root)
50
+ if type(data.get("revision", 0)) is not int or data.get("revision", 0) != catalog["revision"]:
51
+ raise UserActionRequired("Stale project registry revision.")
52
+ entries = {item["id"]: item for item in catalog["projects"]}
53
+ entry = {**entries.get(data["id"], {}), **{k: v for k, v in data.items() if k != "revision"}}
54
+ for key in ("name", "path"):
55
+ if not isinstance(entry.get(key), str) or not entry[key].strip():
56
+ raise UserActionRequired(f"Project needs {key}.")
57
+ base = Path(project_root).resolve()
58
+ target = (base / entry["path"]).resolve()
59
+ if Path(entry["path"]).is_absolute() or not target.is_relative_to(base):
60
+ raise UserActionRequired("Project path must stay within workspace.")
61
+ entry["path"] = target.relative_to(base).as_posix()
62
+ if entry.get("kind", "directory") not in {"repository", "submodule", "directory"}:
63
+ raise UserActionRequired("Invalid project kind.")
64
+ for key in ("abbreviation", "description"):
65
+ if key in entry and not isinstance(entry[key], str):
66
+ raise UserActionRequired(f"Project {key} must be text.")
67
+ for key in ("aliases", "entrypoints"):
68
+ if key in entry and (not isinstance(entry[key], list) or not all(isinstance(v, str) for v in entry[key])):
69
+ raise UserActionRequired(f"Project {key} must be a list of strings.")
70
+ if entry.get("parent_id") is not None and not isinstance(entry["parent_id"], str):
71
+ raise UserActionRequired("Invalid parent project.")
72
+ if "deprecated" in entry and not isinstance(entry["deprecated"], bool):
73
+ raise UserActionRequired("deprecated must be boolean.")
74
+ entries[entry["id"]] = entry
75
+ for item in entries.values():
76
+ seen = {item["id"]}
77
+ parent = item.get("parent_id")
78
+ while parent:
79
+ if parent in seen or parent not in entries:
80
+ raise UserActionRequired("Unknown or cyclic parent project.")
81
+ seen.add(parent)
82
+ parent = entries[parent].get("parent_id")
83
+ if len({e["path"] for e in entries.values()}) != len(entries):
84
+ raise UserActionRequired("Project path already registered.")
85
+ catalog.update(revision=catalog["revision"] + 1, projects=list(entries.values()))
86
+ write_manifest(root / "projects.json", catalog)
87
+ return catalog
88
+
89
+
90
+ def discover_projects(project_root):
91
+ base = Path(project_root).resolve()
92
+ found = []
93
+ visited = set()
94
+
95
+ def visit(path, parent=None):
96
+ if path in visited:
97
+ return
98
+ visited.add(path)
99
+ relative = path.relative_to(base).as_posix()
100
+ project_id = "project-" + hashlib.sha256(relative.encode()).hexdigest()[:16]
101
+ readable = path.is_dir() and (parent is None or (path / ".git").exists())
102
+ found.append(
103
+ {
104
+ "id": project_id,
105
+ "name": path.name,
106
+ "path": relative,
107
+ "parent_id": parent,
108
+ "kind": "submodule" if parent else "repository",
109
+ "readable": readable,
110
+ "entrypoints": [
111
+ str(f.relative_to(base))
112
+ for f in path.iterdir()
113
+ if f.is_file() and f.name.lower() in {"readme.md", "claude.md", "agents.md", "rules.md"}
114
+ ]
115
+ if readable
116
+ else [],
117
+ }
118
+ )
119
+ modules = path / ".gitmodules"
120
+ children = set()
121
+ if modules.is_file():
122
+ config = configparser.ConfigParser()
123
+ config.read(modules)
124
+ for section in config.sections():
125
+ child = (path / config.get(section, "path")).resolve()
126
+ if not child.is_relative_to(base) or child == path or path.is_relative_to(child):
127
+ raise UserActionRequired("Submodule path escapes project.")
128
+ children.add(child)
129
+
130
+ if readable:
131
+ result = subprocess.run(
132
+ ["git", "-C", str(path), "ls-files", "--stage", "-z"], capture_output=True, check=False
133
+ )
134
+ if result.returncode == 0:
135
+ for row in result.stdout.decode("utf-8", errors="replace").split("\0"):
136
+ if row.startswith("160000 ") and "\t" in row:
137
+ children.add((path / row.split("\t", 1)[1]).resolve())
138
+ for child in sorted(children):
139
+ if not child.is_relative_to(base) or child == path or path.is_relative_to(child):
140
+ raise UserActionRequired("Submodule path escapes project.")
141
+ visit(child, project_id)
142
+
143
+ visit(base)
144
+ return {"projects": found}
145
+
146
+
147
+ def validate_links(session_dir, links):
148
+ if not isinstance(links, list):
149
+ raise UserActionRequired("project_links must be a list.")
150
+ projects = project_map(Path(session_dir).parent)
151
+ seen = set()
152
+ for link in links:
153
+ if not isinstance(link, dict):
154
+ raise UserActionRequired("Invalid project link.")
155
+ key = (link.get("project_id"), link.get("role"))
156
+ if not all(isinstance(k, str) for k in key) or key[0] not in projects or key in seen:
157
+ raise UserActionRequired("Unknown or duplicate project link.")
158
+ seen.add(key)
159
+ if key[1] not in {"root_cause", "improvement"} or link.get("evidence_state") not in {"hypothesis", "supported"}:
160
+ raise UserActionRequired("Invalid project attribution state.")
161
+ if (
162
+ not isinstance(link.get("reason"), str)
163
+ or not link["reason"].strip()
164
+ or not isinstance(link.get("evidence"), list)
165
+ or not link["evidence"]
166
+ ):
167
+ raise UserActionRequired("Project attribution needs reason and retained evidence.")
168
+ for ref in link["evidence"]:
169
+ validate_ref(session_dir, ref)
170
+ if not isinstance(link.get("source_paths", []), list):
171
+ raise UserActionRequired("source_paths must be a list.")
172
+ for path in link.get("source_paths", []):
173
+ if not isinstance(path, str):
174
+ raise UserActionRequired("Source path must be workspace relative text.")
175
+ safe_path(Path(session_dir).parent.parent.parent, path)
176
+ return links
177
+
178
+
179
+ def root_projects(root, issue):
180
+ projects = project_map(root)
181
+ result = []
182
+ links = issue.get("project_links", [])
183
+ if not isinstance(links, list):
184
+ return result
185
+ for link in links:
186
+ if (
187
+ not isinstance(link, dict)
188
+ or not isinstance(link.get("project_id"), str)
189
+ or link.get("role") != "root_cause"
190
+ ):
191
+ continue
192
+ entry = projects.get(link["project_id"], {})
193
+ result.append(
194
+ {
195
+ **entry,
196
+ "id": link["project_id"],
197
+ "name": entry.get("abbreviation") or entry.get("name") or link["project_id"],
198
+ "pending": link.get("evidence_state") != "supported",
199
+ }
200
+ )
201
+ return result
@@ -10,6 +10,7 @@ from agent_tune_kit.results.evidence import json_bytes, now, safe_path, write_ma
10
10
  from agent_tune_kit.utils.files import write_text_atomic
11
11
 
12
12
  from .categories import check_issue_category
13
+ from .projects import load_projects, validate_links
13
14
  from .segments import resolve_segments, segments_for_storage
14
15
  from .store import (
15
16
  STATES,
@@ -173,6 +174,8 @@ def prepare_issue(session_dir, data, *, dry_run=False):
173
174
  "segments": [],
174
175
  }
175
176
  )
177
+ if "project_links" in data:
178
+ issue["project_links"] = validate_links(directory, data["project_links"])
176
179
  if "category" in data:
177
180
  data["category"] = check_issue_category(
178
181
  directory.parent, data["category"], previous.get("category") if previous else None
@@ -227,6 +230,7 @@ def prepare_issue(session_dir, data, *, dry_run=False):
227
230
  "description",
228
231
  "selection_reason",
229
232
  "category",
233
+ "project_links",
230
234
  "evidence",
231
235
  "segments",
232
236
  "status",
@@ -325,9 +329,11 @@ def save_detection(session_dir, data):
325
329
 
326
330
 
327
331
  @_locked_write
328
- def save_diagnosis(session_dir, issue_id, text, diagnosis_id=None):
332
+ def save_diagnosis(session_dir, issue_id, text, diagnosis_id=None, expected_revision=None):
329
333
  directory = Path(session_dir)
330
334
  issue = load_issue(directory, issue_id)
335
+ if expected_revision is not None and expected_revision != issue["revision"]:
336
+ raise UserActionRequired("Stale diagnosis issue revision.")
331
337
  if not isinstance(text, str) or not text.strip():
332
338
  raise UserActionRequired("Diagnosis must contain an explanation and a plan.")
333
339
  diagnosis_id = _id(diagnosis_id, "DIA")
@@ -343,9 +349,11 @@ def save_diagnosis(session_dir, issue_id, text, diagnosis_id=None):
343
349
  "segments": issue.get("segments", []),
344
350
  "created_at": now(),
345
351
  }
346
- for optional in ("selection_reason", "category"):
352
+ for optional in ("selection_reason", "category", "project_links"):
347
353
  if optional in issue:
348
354
  snapshot[optional] = issue[optional]
355
+ if issue.get("project_links"):
356
+ snapshot["project_registry"] = load_projects(directory.parent)
349
357
  _refs(directory, issue["evidence"])
350
358
  for segment in snapshot["segments"]:
351
359
  _refs(directory, segment["evidence"])
@@ -502,6 +510,19 @@ def save_change(session_dir, issue_id, data):
502
510
  if not previous and change["status"] != "running":
503
511
  raise UserActionRequired("Save a running change before applying the implementation.")
504
512
  _validate_change_summary(change)
513
+ for summary in change.get("change_summary", []):
514
+ if isinstance(summary, dict) and "project_id" in summary:
515
+ from .projects import project_map
516
+
517
+ entry = project_map(directory.parent).get(summary["project_id"])
518
+ if entry is None:
519
+ raise UserActionRequired("Unknown modification project.")
520
+ base = directory.parent.parent.parent.resolve()
521
+ project_path = (base / entry["path"]).resolve()
522
+ for file in summary.get("files", []):
523
+ target = safe_path(base, file)
524
+ if not target.resolve().is_relative_to(project_path):
525
+ raise UserActionRequired("Modification file is outside its project.")
505
526
  for field in ("verifications", "schema_version", "owner_issue", "created_at"):
506
527
  if field in data and (not previous or data[field] != previous.get(field)):
507
528
  raise UserActionRequired(f"Change {field} is managed by its dedicated operation.")
@@ -199,6 +199,7 @@ def load_session(session_dir):
199
199
  manifest = value.get("archive")
200
200
  if (
201
201
  directory.name != archive.component(value.get("session_id"))
202
+ and not (directory.name == "projects.json" and value.get("session_id") == "projects.json")
202
203
  or value.get("source") != normalize_source(value.get("source"))
203
204
  or not isinstance(manifest, dict)
204
205
  or not isinstance(manifest.get("files"), list)
@@ -21,6 +21,18 @@
21
21
  let investigation = null, scopeBefore = null, previewIssue = null, expandedFolds = new Set(), railFilter = null;
22
22
  const captureById = new Map(data.captures.map(c => [c.id, c]));
23
23
  const issueById = new Map(data.issues.map(item => [item.record.id, item]));
24
+
25
+ const projectSelection = new Set(new URLSearchParams(location.hash.slice(1)).getAll("project"));
26
+ const projectMatches = item => !projectSelection.size || [...projectSelection].some(id => {
27
+ const roots = item.root_projects || [];
28
+ if (id === "~unattributed") return !roots.length;
29
+ return roots.some(root => { let current = root.id; const seen = new Set(); while (current && !seen.has(current)) { if (current === id) return true; seen.add(current); current = data.projects?.[current]?.parent_id; } return false; });
30
+ });
31
+ const projectText = item => (item.root_projects || []).map(p => p.name + (p.pending ? "(待确认)" : "")).join("、") || "待归因";
32
+ for (const p of Object.values(data.projects || {})) { const option = el("option", p.abbreviation || p.name); option.value = p.id; $("project-filter").append(option); }
33
+ for (const option of $("project-filter").options) option.selected = projectSelection.has(option.value);
34
+ const projectOverview = el("p", `根因项目:${[...new Set(data.issues.map(projectText))].join("、") || "待归因"}`);
35
+ $("project-filter").parentElement.append(projectOverview);
24
36
  const filters = ["trace-filter", "type-filter", "trace-search", "record-mode"];
25
37
  const list = $("trace-list"), scroller = $("trace-scroll");
26
38
  const scope = () => $("trace-filter").value === "" ? null : Number($("trace-filter").value);
@@ -257,6 +269,7 @@
257
269
  return;
258
270
  }
259
271
  for (const [index, diagnosis] of item.diagnoses.slice().reverse().entries()) {
272
+ const snapshot = diagnosis.snapshot;
260
273
  const body = diagnosis.error ? el("p", diagnosis.error, "warning") : renderDiagnosis(diagnosis.text);
261
274
  const headings = [...body.children].filter(node => node.tagName === "H2").map(node => node.textContent);
262
275
  const structured = headings.join("|") === "异常描述|异常根因|最小修复方案"
@@ -275,6 +288,11 @@
275
288
  container.append(description);
276
289
  }
277
290
  const details = el("details", undefined, "detail-section");
291
+ if (snapshot?.project_links) {
292
+ const entries = new Map((snapshot.project_registry?.projects || []).map(p => [p.id, p]));
293
+ const names = snapshot.project_links.filter(p => p.role === "root_cause").map(p => { const entry = entries.get(p.project_id); return (entry?.abbreviation || entry?.name || p.project_id) + (p.evidence_state === "supported" ? "" : "(待确认)"); });
294
+ body.prepend(el("p", `诊断时的根因项目:${names.join("、") || "待归因"}`));
295
+ }
278
296
  const name = diagnosis.reference.path.split("/").pop();
279
297
  details.append(el("summary", `展开原诊断 · ${name}`), body);
280
298
  container.append(details);
@@ -373,7 +391,7 @@
373
391
  $("drawer-content").append(tabs);
374
392
  if (stage === "issue") {
375
393
  const description = section("问题描述");
376
- description.append(el("p", issue.description || issue.title));
394
+ description.append(el("p", `根因项目:${projectText(item)}`), el("p", issue.description || issue.title));
377
395
  $("drawer-content").append(description);
378
396
  if (typeof issue.selection_reason === "string" && issue.selection_reason.trim()) {
379
397
  const reason = section("保留理由");
@@ -969,6 +987,7 @@
969
987
  const text = $("issue-search").value.toLocaleLowerCase();
970
988
  const status = $("issue-status").value;
971
989
  const filtered = data.issues.filter(item => {
990
+ if (!projectMatches(item) && new URLSearchParams(location.hash.slice(1)).get("issue") !== item.record.id) return false;
972
991
  if (railFilter && !railFilter.ids.includes(item.record.id)) return false;
973
992
  const progress = progressOf(item).label;
974
993
  if (status && progress !== status) return false;
@@ -994,6 +1013,11 @@
994
1013
  const head = el("div", undefined, "issue-card-head");
995
1014
  head.append(el("span", issue.id, "issue-id-text"), el("span", undefined, `progress-dot ${progress.klass}`));
996
1015
  const body = el("div", undefined, "issue-card-body");
1016
+ const projects = el("p", `根因项目:${projectText(item)}`);
1017
+ if (data.dashboard.available) for (const project of item.root_projects || []) {
1018
+ const link = el("a", project.name); link.href = dashboardHash(`view=session&project=${encodeURIComponent(project.id)}`); projects.append(link);
1019
+ }
1020
+ body.append(projects);
997
1021
  body.append(el("h3", issue.title || "问题"), el("div", progress.label, "issue-progress"));
998
1022
  const tag = categoryTag(issue.category);
999
1023
  if (tag) body.append(tag);
@@ -1083,6 +1107,7 @@
1083
1107
  $("capture-filter").closest("label").hidden = Boolean(currentCapture?.current);
1084
1108
  options($("capture-filter"), data.captures.map(c => c.id));
1085
1109
  if (currentCapture) $("capture-filter").value = currentCapture.id;
1110
+ $("project-filter").onchange = () => { projectSelection.clear(); for (const option of $("project-filter").selectedOptions) projectSelection.add(option.value); const params = new URLSearchParams(location.hash.slice(1)); params.delete("project"); for (const id of projectSelection) params.append("project", id); history.replaceState(null, "", `#${params}`); renderIssueRail(); };
1086
1111
  $("capture-filter").onchange = () => { investigation = null; scopeBefore = null; railFilter = null; chooseCapture(); renderIssueRail(); renderTraces(); };
1087
1112
  $("trace-filter").onchange = () => focusTrace(scope());
1088
1113
  for (const id of ["trace-search","type-filter","record-mode"]) $(id).addEventListener(id === "trace-search" ? "input" : "change", () => renderTraces());
@@ -21,7 +21,8 @@
21
21
  </div>
22
22
  <div class="rail-filters">
23
23
  <label class="search-label"><span class="sr-only">搜索问题</span><input id="issue-search" type="search" placeholder="搜索问题"></label>
24
- <label><span class="sr-only">状态</span><select id="issue-status"></select></label>
24
+ <label>根因项目(含子项目)<select id="project-filter" multiple aria-label="根因项目"><option value="~unattributed">待归因</option></select></label>
25
+ <label><span class="sr-only">状态</span><select id="issue-status"></select></label>
25
26
  </div>
26
27
  <div id="issue-rail-list" class="rail-list"></div>
27
28
  </aside>