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